-
Notifications
You must be signed in to change notification settings - Fork 104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New Source File and Lock Specification Approach #316
base: main
Are you sure you want to change the base?
Changes from 1 commit
dafb50d
da18f01
0ad6f7b
0e9b2a6
48df28e
3fd7106
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,7 @@ | |
import typing | ||
|
||
from itertools import chain | ||
from typing import Dict, List, Optional, Tuple, Union | ||
from typing import AbstractSet, Dict, List, Optional, Sequence, Tuple, Union | ||
|
||
from pydantic import BaseModel, validator | ||
from typing_extensions import Literal | ||
|
@@ -17,6 +17,8 @@ | |
from conda_lock.virtual_package import FakeRepoData | ||
|
||
|
||
DEFAULT_PLATFORMS = ["osx-64", "linux-64", "win-64"] | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
|
@@ -136,3 +138,85 @@ def aggregate_lock_specs( | |
platforms=ordered_union(lock_spec.platforms or [] for lock_spec in lock_specs), | ||
sources=ordered_union(lock_spec.sources or [] for lock_spec in lock_specs), | ||
) | ||
|
||
|
||
def parse_source_files( | ||
src_files: List[pathlib.Path], | ||
platform_overrides: Optional[Sequence[str]], | ||
pip_support: bool = True, | ||
) -> List[LockSpecification]: | ||
""" | ||
Parse a sequence of dependency specifications from source files | ||
|
||
Parameters | ||
---------- | ||
src_files : | ||
Files to parse for dependencies | ||
platform_overrides : | ||
Target platforms to render environment.yaml and meta.yaml files for | ||
""" | ||
from conda_lock.src_parser.environment_yaml import parse_environment_file | ||
from conda_lock.src_parser.meta_yaml import parse_meta_yaml_file | ||
from conda_lock.src_parser.pyproject_toml import parse_pyproject_toml | ||
Comment on lines
+193
to
+195
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any particular reason to put these imports inside the function? For more standardized code, I'd prefer to have imports at the top of the file unless there's a good reason. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Those modules import There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, yes, circular dependencies in Python are really annoying.
Are they using them just for type hints? If so, then they are not true import cycles. In that case, you can do from typing import TYPE_CHECKING
if TYPE_CHECKING;
from ... import SourceDependency, ... and the types won't be imported at runtime, avoiding the cycle. (Very nice, I see that you already know this trick! 😄) If it's not just for type hints, then there may be some genuinely circular logic occurring. For instance, if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not just for type hints. I will move them to a new module. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I forgot to mention in my previous comment that Python does permit certain types of circular imports. Running So there is another strategy: rather than fix the cycles, try to import modules lazily, i.e. replace |
||
|
||
desired_envs: List[LockSpecification] = [] | ||
for src_file in src_files: | ||
if src_file.name == "meta.yaml": | ||
desired_envs.append( | ||
parse_meta_yaml_file( | ||
src_file, list(platform_overrides or DEFAULT_PLATFORMS) | ||
) | ||
) | ||
elif src_file.name == "pyproject.toml": | ||
desired_envs.append(parse_pyproject_toml(src_file)) | ||
else: | ||
desired_envs.append( | ||
parse_environment_file( | ||
src_file, | ||
platform_overrides, | ||
default_platforms=DEFAULT_PLATFORMS, | ||
pip_support=pip_support, | ||
) | ||
) | ||
return desired_envs | ||
|
||
|
||
def make_lock_spec( | ||
*, | ||
src_files: List[pathlib.Path], | ||
virtual_package_repo: FakeRepoData, | ||
channel_overrides: Optional[Sequence[str]] = None, | ||
platform_overrides: Optional[Sequence[str]] = None, | ||
required_categories: Optional[AbstractSet[str]] = None, | ||
pip_support: bool = True, | ||
) -> LockSpecification: | ||
"""Generate the lockfile specs from a set of input src_files. If required_categories is set filter out specs that do not match those""" | ||
lock_specs = parse_source_files( | ||
src_files=src_files, | ||
platform_overrides=platform_overrides, | ||
pip_support=pip_support, | ||
) | ||
|
||
lock_spec = aggregate_lock_specs(lock_specs) | ||
lock_spec.virtual_package_repo = virtual_package_repo | ||
lock_spec.channels = ( | ||
[Channel.from_string(co) for co in channel_overrides] | ||
if channel_overrides | ||
else lock_spec.channels | ||
) | ||
lock_spec.platforms = ( | ||
list(platform_overrides) if platform_overrides else lock_spec.platforms | ||
) or list(DEFAULT_PLATFORMS) | ||
|
||
if required_categories is not None: | ||
|
||
def dep_has_category(d: Dependency, categories: AbstractSet[str]) -> bool: | ||
return d.category in categories | ||
|
||
lock_spec.dependencies = [ | ||
d | ||
for d in lock_spec.dependencies | ||
if dep_has_category(d, categories=required_categories) | ||
] | ||
|
||
return lock_spec |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both
make_lock_spec
andparse_source_files
are specifically related to parsing source files, and don't have a big effect on the rest of the program. Thus, I moved them toconda_lock/src_parser/__init__.py
to reduce the amount of code in this file (since its about 1500 lines of code).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot for all this work!!! It would still really help for reviewing to have these refactor steps in separate commits so that I could view the substantive changes separately. (In general it's much easier to follow several smaller logical commits than one massive one.)
I'm sincerely very eager to see this through quickly, but my schedule looks difficult at the moment. I'll see what I can do, but apologies in advance if I'm slow to respond.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@maresb I tried to break this PR down into a couple of commits to make it a bit easier. I had some trouble breaking down the last couple of commits since the contents is very much tied together. But if it is still difficult to look through, let me know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, the additional commits! This is much better for review.
It could still be even better... The best would be one single logical change per commit. Please don't change this particular commit now, but to explain what I mean, your first commit "Move Function Sub PR" could be further broken down into:
because this is the level to which I need to deconstruct the changes to see what's going on. (Currently I have to diff each function removed from
conda_lock.py
with each function added to__init__.py
in order to see exactly what changed, so a verbatim cut-from-one-file and paste-in-the-other easier to process as a single logical change.)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, good point. I'm used to writing large PRs in general. In the future, I can definitely break down my commits even further. Would you like me to modify the last large commit of this PR? My concern with modifying that commit is that I'm not sure how to break it down without having some commit be broken. I normally try to ensure that every commit exposed to master is a somewhat-working impl of the app or library.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Large PRs are fine, it's just large commits which are difficult to understand.
Your commits don't need to be perfect, and I'm asking for a fairly high standard. I can explain how I try to write commits:
This is a good rule in general. But in some situations I think it's fine to break something in one commit and fix it in a subsequent one. (For example, in one commit I might remove functionality X, and then in the next commit I add functionality Y which replaces X. This way the new details of Y aren't confused with the old details of X.)
What also helps is to stage partial changes. For example, in the process of implementing X, I may modify some type hints in another part of the code. In this case, I can stage and commit the type hints in a separate commit, so that my implementation of X remains focused.
The most complicated technique is to rebase code you've already committed, but that is really a lot of work.
A few concrete suggestions for how you could break up the main commit:
ruamel
I think I can handle the large commit as-is, but I would need to find an uninterrupted block of time where I could work through the whole thing at once. If you manage to break up the commit, then I can probably finish reviewing it sooner.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you spent a lot of time looking at the last 2 commits:
Initial Version of SourceFile Approach
andAdding Test Yaml Files
. If not, I can try to split those further.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you already looked through the first 2 commits thoroughly, so I will leave them alone.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, in fact, if you create a separate PR for those I think we can already merge them since they are minor refactoring changes.
For the big commit I was thinking: since this is a major change, Marius will have to review it after me. Thus it may be worthwhile to invest extra time to make it readable.