@@ -46,7 +46,7 @@ class InvalidName(ValueError):
4646
4747class InvalidFilename (ValueError ):
4848 """
49- .
49+ An invalid filename was found, users should refer to the packaging user guide .
5050 """
5151
5252
@@ -69,6 +69,7 @@ class InvalidSdistFilename(InvalidFilename):
6969_normalized_regex = re .compile (r"[a-z0-9]|[a-z0-9]([a-z0-9-](?!--))*[a-z0-9]" , re .ASCII )
7070# PEP 427: The build number must start with a digit.
7171_build_tag_regex = re .compile (r"(\d+)(.*)" , re .ASCII )
72+ _project_name_regex = re .compile (r"^[\w\d._]*$" , re .UNICODE )
7273
7374
7475def canonicalize_name (
@@ -182,6 +183,14 @@ def _compress_tag_set(tags: Iterable[Tag]) -> str:
182183
183184
184185class WheelFilename :
186+ """Represents a wheel filename and its parsed components.
187+
188+ Instances preserve the original name and version strings for round-tripping,
189+ while exposing normalized and validated views through properties.
190+
191+ .. versionadded:: 26.1
192+ """
193+
185194 __slots__ = ("build_tag" , "original_name" , "original_version" , "tags" )
186195
187196 def __init__ (
@@ -191,17 +200,29 @@ def __init__(
191200 build_tag : BuildTag = (),
192201 tags : Iterable [Tag ] = (),
193202 ) -> None :
203+ """Create a wheel filename from its component parts.
204+
205+ :param name: The project name (in original, filename-style form).
206+ :param version: The version string (in original form).
207+ :param build_tag: Optional wheel build tag.
208+ :param tags: The wheel tag set.
209+ """
194210 self .original_name = name
195211 self .original_version = version
196212 self .build_tag = build_tag
197213 self .tags = frozenset (tags )
198214
199215 @property
200216 def name (self ) -> NormalizedName :
217+ """The normalized project name."""
201218 return canonicalize_name (self .original_name )
202219
203220 @property
204221 def version (self ) -> Version :
222+ """The parsed project version.
223+
224+ :raises InvalidWheelFilename: If the stored version is not valid.
225+ """
205226 try :
206227 return Version (self .original_version )
207228 except InvalidVersion as e :
@@ -210,10 +231,32 @@ def version(self) -> Version:
210231
211232 @property
212233 def compressed_tags (self ) -> str :
234+ """The compressed and sorted wheel tag string (interpreter-abi-platform).
235+
236+ >>> from packaging.filenames import WheelFilename
237+ >>> from packaging.tags import Tag
238+ >>> wf = WheelFilename("foo", "1.0", tags={Tag("py3", "none", "any")})
239+ >>> wf.compressed_tags
240+ 'py3-none-any'
241+ >>> tags = {Tag("py3", "none", "any"), Tag("cp314", "cp314", "win_amd64")}
242+ >>> wf = WheelFilename("foo", "1.0", tags=tags)
243+ >>> wf.compressed_tags
244+ 'cp314.py3-cp314.none-any.win_amd64'
245+ """
213246 return _compress_tag_set (self .tags )
214247
215248 @property
216249 def build_str (self ) -> str :
250+ """The build tag as a string, or an empty string when absent.
251+
252+ >>> from packaging.filenames import WheelFilename
253+ >>> wf = WheelFilename("foo", "1.0")
254+ >>> wf.build_str
255+ ''
256+ >>> wf = WheelFilename("foo", "1.0", build_tag=(1, "abc"))
257+ >>> wf.build_str
258+ '1abc'
259+ """
217260 return "" .join (map (str , self .build_tag ))
218261
219262 def __repr__ (self ) -> str :
@@ -239,12 +282,10 @@ def to_filename(self) -> str:
239282 The tags is set of tags that will be compressed into a wheel
240283 tag string.
241284
242- >>> from packaging.utils import compose_wheel_filename
285+ >>> from packaging.filenames import WheelFilename
243286 >>> from packaging.tags import Tag
244- >>> from packaging.version import Version
245- >>> version = Version("1.0")
246287 >>> tags = {Tag("py3", "none", "any")}
247- >>> compose_wheel_filename ("foo-bar", version , None, tags)
288+ >>> WheelFilename ("foo-bar", "1.0" , None, tags).to_filename( )
248289 'foo_bar-1.0-py3-none-any.whl'
249290
250291 .. versionadded:: 26.1
@@ -307,7 +348,7 @@ def from_filename(cls, filename: str, /, *, strict: bool) -> WheelFilename:
307348 tags = parse_tag (parts [- 1 ])
308349
309350 # See PEP 427 for the rules on escaping the project name.
310- if "__" in name or re .match (r"^[\w\d._]*$" , name , re . UNICODE ) is None :
351+ if "__" in name or _project_name_regex .match (name ) is None :
311352 inner = f"invalid project name: { name !r} "
312353 msg = f"Invalid wheel filename ({ inner } ): { filename !r} "
313354 raise InvalidWheelFilename (msg )
@@ -351,19 +392,37 @@ def from_filename(cls, filename: str, /, *, strict: bool) -> WheelFilename:
351392
352393
353394class SourceFilename :
395+ """Represents a source distribution filename and its parsed components.
396+
397+ Instances preserve the original name and version strings for round-tripping,
398+ while exposing normalized and validated views through properties.
399+
400+ .. versionadded:: 26.1
401+ """
402+
354403 __slots__ = ("original_name" , "original_version" )
355404
356405 def __init__ (self , name : str , version : str ) -> None :
406+ """Create a source distribution filename from name and version.
407+
408+ :param str name: The project name (in original, filename-style form).
409+ :param str version: The version string (in original form).
410+ """
357411 # Store the values that were originally passed for use externally
358412 self .original_name = name
359413 self .original_version = version
360414
361415 @property
362416 def name (self ) -> NormalizedName :
417+ """The normalized project name."""
363418 return canonicalize_name (self .original_name )
364419
365420 @property
366421 def version (self ) -> Version :
422+ """The parsed project version.
423+
424+ :raises InvalidSdistFilename: If the stored version is not valid.
425+ """
367426 try :
368427 return Version (self .original_version )
369428 except InvalidVersion as e :
@@ -378,10 +437,9 @@ def to_filename(self) -> str:
378437 characters are replaced with ``_`` and characters are lower cased. The
379438 version is an instance of :class:`~packaging.version.Version`.
380439
381- >>> from packaging.utils import compose_sdist_filename
382- >>> from packaging.version import Version
383- >>> "foo_bar-1.0.tar.gz" == compose_sdist_filename("foo-bar", Version("1.0"))
384- True
440+ >>> from packaging.filenames import SourceFilename
441+ >>> SourceFilename("foo-bar", "1.0").to_filename()
442+ 'foo_bar-1.0.tar.gz'
385443
386444 .. versionadded:: 26.1
387445 """
@@ -412,13 +470,12 @@ def from_filename(cls, filename: str, /, *, strict: bool) -> SourceFilename:
412470 with an sdist extension (``.zip`` or ``.tar.gz``), or if it does not
413471 contain a dash separating the name and the version of the distribution.
414472
415- >>> from packaging.utils import parse_sdist_filename
416- >>> from packaging.version import Version
417- >>> name, ver = parse_sdist_filename("foo-1.0.tar.gz")
418- >>> name
473+ >>> from packaging.filenames import SourceFilename
474+ >>> fn = SourceFilename.from_filename("foo-1.0.tar.gz", strict=True)
475+ >>> fn.name
419476 'foo'
420- >>> ver == Version('1.0')
421- True
477+ >>> fn.version
478+ <Version('1.0')>
422479
423480 .. _Source distribution format: https://packaging.python.org/specifications/source-distribution-format/#source-distribution-file-name
424481 """
0 commit comments