Skip to content

Commit 0e8b7b5

Browse files
committed
docs: add filenames
Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
1 parent 567b81c commit 0e8b7b5

4 files changed

Lines changed: 87 additions & 17 deletions

File tree

docs/filenames.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Filenames
2+
=========
3+
4+
Tools to work with filenames for SDists and wheels.
5+
6+
Reference
7+
---------
8+
9+
.. automodule:: packaging.filenames
10+
:members:

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ The ``packaging`` library uses calendar-based versioning (``YY.N``).
3131
metadata
3232
tags
3333
pylock
34+
filenames
3435
utils
3536

3637
.. toctree::

docs/utils.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
Utilities
22
=========
33

4-
A set of small, helper utilities for dealing with Python packages.
4+
A set of small, helper utilities for dealing with Python packages. Most of these
5+
functions are now in the :mod:`packaging.filenames` module, and re-exported
6+
here.
57

68
Reference
79
---------

src/packaging/filenames.py

Lines changed: 73 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class InvalidName(ValueError):
4646

4747
class 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

7475
def canonicalize_name(
@@ -182,6 +183,14 @@ def _compress_tag_set(tags: Iterable[Tag]) -> str:
182183

183184

184185
class 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

353394
class 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

Comments
 (0)