88
99Pre-release handling (--pre flag):
1010 Follows pip's PEP 440 pre-release behavior:
11-
11+
1212 Without --pre:
1313 - Pre-release versions (dev, alpha, beta, rc, etc.) are excluded
1414 - Example: ">=26.0.0" matches only 26.0.0, 26.0.1, etc.
1515 - Example: ">=26.0.0" does NOT match 26.0.0.dev0 (pre-releases come before releases)
16-
16+
1717 With --pre:
1818 - Pre-release versions are included in version resolution
1919 - Example: ">=26.0.0.dev0" matches 26.0.0.dev0, 26.0.0.dev1, 26.0.0, 26.0.1, etc.
2020 - Example: ">=26.0.0" matches 26.0.0, 26.0.1, 26.1.0.dev0, etc.
2121 - Note: 26.0.0.dev0 < 26.0.0 in PEP 440 (pre-releases sort before releases)
22-
22+
2323Version ordering (PEP 440):
2424 - 25.0.0 < 26.0.0.dev0 < 26.0.0.dev1 < 26.0.0 < 26.0.1
2525
4646except ModuleNotFoundError :
4747 import tomli as tomllib # type: ignore[import]
4848
49- from packaging .version import Version , InvalidVersion
5049from packaging .specifiers import SpecifierSet
50+ from packaging .version import InvalidVersion , Version
5151
5252
5353def _remove_readonly (func , path , exc_info ):
@@ -58,12 +58,12 @@ def _remove_readonly(func, path, exc_info):
5858
5959def _normalize_tag (tag ):
6060 """Normalize a git tag to a PEP 440 compliant version string.
61-
61+
6262 Removes 'v' or 'V' prefix to match PEP 440 format.
63-
63+
6464 Args:
6565 tag: Git tag string (e.g., "v26.0.0", "26.0.0.dev3")
66-
66+
6767 Returns:
6868 Normalized version string without 'v' prefix (e.g., "26.0.0", "26.0.0.dev3")
6969 """
@@ -72,49 +72,49 @@ def _normalize_tag(tag):
7272
7373def _parse_dependency (dep_string ):
7474 """Parse dependency string with PEP 440 version specifier.
75-
75+
7676 Supports the same specifiers as pip install:
7777 owner/repo==1.2.3 - exact version
7878 owner/repo>=1.2.3 - minimum version
7979 owner/repo<2.0.0 - maximum version
8080 owner/repo~=1.2.3 - compatible release (>=1.2.3, <1.3.0)
81-
81+
8282 Args:
8383 dep_string: Dependency string
84-
84+
8585 Returns:
8686 tuple: (repo, specifier, version) where specifier is one of '==', '>=', '<', '~='
8787 Returns (None, None, None) if parsing fails
8888 """
8989 # Try each specifier in order of length (longest first to avoid conflicts)
90- specifiers = ['~=' , '>=' , '==' , '<' ]
91-
90+ specifiers = ["~=" , ">=" , "==" , "<" ]
91+
9292 for spec in specifiers :
9393 if spec in dep_string :
9494 parts = dep_string .split (spec , 1 )
9595 if len (parts ) == 2 :
9696 return parts [0 ].strip (), spec , parts [1 ].strip ()
97-
97+
9898 return None , None , None
9999
100100
101101def _filter_tags_by_specifier (tags , specifier , version , allow_prerelease = False ):
102102 """Filter tags based on version specifier using PEP 440.
103-
103+
104104 Uses packaging library for PEP 440 compliance, matching pip install behavior.
105-
105+
106106 Args:
107107 tags: List of tag names
108108 specifier: Version specifier ('==', '>=', '<', '~=')
109109 version: Version string
110110 allow_prerelease: Include pre-release versions (equivalent to pip --pre)
111-
111+
112112 Returns:
113113 Best matching tag, or None if no match
114114 """
115115 # Create specifier set (PEP 440)
116116 spec_set = SpecifierSet (f"{ specifier } { version } " , prereleases = allow_prerelease )
117-
117+
118118 # Filter tags that match the specifier
119119 matching_tags = []
120120 for tag in tags :
@@ -126,10 +126,10 @@ def _filter_tags_by_specifier(tags, specifier, version, allow_prerelease=False):
126126 except InvalidVersion :
127127 # Skip tags that aren't valid PEP 440 versions
128128 continue
129-
129+
130130 if not matching_tags :
131131 return None
132-
132+
133133 # Sort by version (highest first) and return the best match
134134 matching_tags .sort (reverse = True , key = lambda x : x [0 ])
135135 return matching_tags [0 ][1 ]
@@ -205,29 +205,31 @@ def _clone_repo_at_tag(repo, tag_or_spec, base_dir, delete_allowed=False, allow_
205205 tag = tag_or_spec
206206 specifier = None
207207 version = None
208-
208+
209209 # Check for version specifiers
210- for spec in ['~=' , '>=' , '==' , '<' ]:
210+ for spec in ["~=" , ">=" , "==" , "<" ]:
211211 if tag_or_spec .startswith (spec ):
212212 specifier = spec
213- version = tag_or_spec [len (spec ):]
213+ version = tag_or_spec [len (spec ) :]
214214 break
215-
215+
216216 # Resolve version if specifier is used (or if --latest flag forces "latest")
217217 if specifier or tag_or_spec .lower () == "latest" :
218218 if specifier :
219219 print (f"Resolving version { specifier } { version } for { repo } ..." )
220220 else :
221221 print (f"Resolving latest version for { repo } ..." )
222-
222+
223223 try :
224224 all_tags = _get_all_tags (repo_url , allow_prerelease )
225225 if not all_tags :
226226 print (f" [FAIL] No tags found in repository { repo } " )
227227 return False
228228 else :
229229 if specifier :
230- matched_tag = _filter_tags_by_specifier (all_tags , specifier , version , allow_prerelease )
230+ matched_tag = _filter_tags_by_specifier (
231+ all_tags , specifier , version , allow_prerelease
232+ )
231233 else :
232234 # "latest" - get the latest tag
233235 valid_tags = []
@@ -238,7 +240,7 @@ def _clone_repo_at_tag(repo, tag_or_spec, base_dir, delete_allowed=False, allow_
238240 continue
239241 valid_tags .sort (reverse = True , key = lambda x : x [0 ])
240242 matched_tag = valid_tags [0 ][1 ] if valid_tags else None
241-
243+
242244 if matched_tag :
243245 print (f" [INFO] Resolved to tag: { matched_tag } " )
244246 tag = matched_tag
@@ -376,19 +378,23 @@ def install_dependencies(delete_allowed=False, allow_prerelease=False, use_lates
376378 for dep_string in dependencies :
377379 # Parse the dependency string
378380 repo , specifier , version = _parse_dependency (dep_string )
379-
381+
380382 if repo is None :
381383 print (f"Warning: Invalid dependency format: { dep_string } " )
382384 print (f" Expected format: owner/repo==version or owner/repo>=version" )
383385 continue
384-
386+
387+ if specifier is None or version is None :
388+ print (f"Warning: Invalid dependency version specifier: { dep_string } " )
389+ continue
390+
385391 # Build the tag/version string for cloning
386392 tag_or_spec = specifier + version
387-
393+
388394 # Override with "latest" if --latest flag is used
389395 if use_latest :
390396 tag_or_spec = "latest"
391-
397+
392398 total_count += 1
393399
394400 if _clone_repo_at_tag (repo , tag_or_spec , deps_dir , delete_allowed , allow_prerelease ):
0 commit comments