partis-pyproj 0.1.9__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/PKG-INFO +15 -10
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/README.md +8 -3
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/pyproject.toml +2 -2
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/src/pyproj/backend.py +1 -1
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/src/pyproj/builder/builder.py +19 -1
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/src/pyproj/builder/download.py +31 -6
- partis_pyproj-0.2.0/src/pyproj/dist_file/dist_copy.py +187 -0
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/src/pyproj/pep.py +74 -36
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/src/pyproj/pkginfo.py +2 -1
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/src/pyproj/pptoml.py +3 -1
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/src/pyproj/pyproj.py +3 -3
- partis_pyproj-0.1.9/src/pyproj/dist_file/dist_copy.py +0 -139
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/LICENSE.txt +0 -0
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/src/pyproj/__init__.py +0 -0
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/src/pyproj/_legacy_setup.py +0 -0
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/src/pyproj/_nonprintable.py +0 -0
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/src/pyproj/builder/__init__.py +0 -0
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/src/pyproj/builder/cargo.py +0 -0
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/src/pyproj/builder/cmake.py +0 -0
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/src/pyproj/builder/meson.py +0 -0
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/src/pyproj/builder/process.py +0 -0
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/src/pyproj/dist_file/__init__.py +0 -0
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/src/pyproj/dist_file/dist_base.py +0 -0
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/src/pyproj/dist_file/dist_binary.py +0 -0
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/src/pyproj/dist_file/dist_source.py +0 -0
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/src/pyproj/dist_file/dist_targz.py +0 -0
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/src/pyproj/dist_file/dist_zip.py +0 -0
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/src/pyproj/file.py +0 -0
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/src/pyproj/legacy.py +0 -0
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/src/pyproj/load_module.py +0 -0
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/src/pyproj/norms.py +0 -0
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/src/pyproj/path/__init__.py +0 -0
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/src/pyproj/path/match.py +0 -0
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/src/pyproj/path/pattern.py +0 -0
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/src/pyproj/path/utils.py +0 -0
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/src/pyproj/template.py +0 -0
- {partis_pyproj-0.1.9 → partis_pyproj-0.2.0}/src/pyproj/validate.py +0 -0
@@ -1,27 +1,27 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: partis-pyproj
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.2.0
|
4
4
|
Requires-Python: >=3.8
|
5
5
|
Author-email: "Nanohmics Inc." <software.support@nanohmics.com>
|
6
6
|
Maintainer-email: "Nanohmics Inc." <software.support@nanohmics.com>
|
7
7
|
Summary: Minimal set of Python project utilities (PEP-517/621)
|
8
8
|
License-File: LICENSE.txt
|
9
9
|
Project-URL: homepage, https://github.com/kcdodd/partis-pyproj/
|
10
|
+
Classifier: Operating System :: POSIX :: Linux
|
10
11
|
Classifier: License :: OSI Approved :: BSD License
|
11
12
|
Classifier: Operating System :: Microsoft :: Windows
|
12
13
|
Classifier: Intended Audience :: Developers
|
13
|
-
Classifier: Programming Language :: Python
|
14
|
-
Classifier: Development Status :: 4 - Beta
|
15
14
|
Classifier: Topic :: Software Development :: Build Tools
|
15
|
+
Classifier: Development Status :: 4 - Beta
|
16
|
+
Classifier: Programming Language :: Python
|
16
17
|
Classifier: Programming Language :: Python :: 3
|
17
|
-
Classifier: Operating System :: POSIX :: Linux
|
18
18
|
Provides-Extra: meson
|
19
19
|
Provides-Extra: cmake
|
20
|
-
Requires-Dist: packaging>=24.2
|
21
|
-
Requires-Dist: tomli>=2.0.1
|
22
20
|
Requires-Dist: requests>=2.32.3
|
23
|
-
Requires-Dist:
|
21
|
+
Requires-Dist: tomli>=2.0.1
|
22
|
+
Requires-Dist: packaging>=24.2
|
24
23
|
Requires-Dist: ninja>=1.10.2.3; extra == "meson"
|
24
|
+
Requires-Dist: meson>=0.61.3; extra == "meson"
|
25
25
|
Requires-Dist: ninja>=1.10.2.3; extra == "cmake"
|
26
26
|
Requires-Dist: cmake>=3.24.3; extra == "cmake"
|
27
27
|
Description-Content-Type: text/markdown
|
@@ -88,10 +88,15 @@ formats and behaviors.
|
|
88
88
|
|
89
89
|
* An `include` list is used to filter files or directories to be copied, expanded
|
90
90
|
to zero or more matches relative to `src`.
|
91
|
-
* `glob` follows the format of [Path.glob](https://docs.python.org/3/library/pathlib.html#pathlib.Path.glob)
|
92
|
-
|
93
|
-
|
91
|
+
* `glob` follows the format of [Path.glob](https://docs.python.org/3/library/pathlib.html#pathlib.Path.glob).
|
92
|
+
If recursive pattern `**` is used, the glob will *not* match directories,
|
93
|
+
since the resulting `copytree` would end up copying all files in the directory.
|
94
|
+
* `rematch` may further discriminate filenames (already matched by `glob`) using [Regular Expression Syntax](https://docs.python.org/3/library/re.html#regular-expression-syntax). Directories are *not* considered by `rematch`.
|
95
|
+
* `replace` can change destination *filenames* using
|
94
96
|
[Format String Syntax](https://docs.python.org/3/library/string.html#format-string-syntax), with values supplied by any groups defined in `rematch`.
|
97
|
+
This cannot rename directories.
|
98
|
+
* `strip` can remove (up to) the given number of path components from the relative
|
99
|
+
`src` path.
|
95
100
|
|
96
101
|
**Ignore patterns**
|
97
102
|
|
@@ -60,10 +60,15 @@ formats and behaviors.
|
|
60
60
|
|
61
61
|
* An `include` list is used to filter files or directories to be copied, expanded
|
62
62
|
to zero or more matches relative to `src`.
|
63
|
-
* `glob` follows the format of [Path.glob](https://docs.python.org/3/library/pathlib.html#pathlib.Path.glob)
|
64
|
-
|
65
|
-
|
63
|
+
* `glob` follows the format of [Path.glob](https://docs.python.org/3/library/pathlib.html#pathlib.Path.glob).
|
64
|
+
If recursive pattern `**` is used, the glob will *not* match directories,
|
65
|
+
since the resulting `copytree` would end up copying all files in the directory.
|
66
|
+
* `rematch` may further discriminate filenames (already matched by `glob`) using [Regular Expression Syntax](https://docs.python.org/3/library/re.html#regular-expression-syntax). Directories are *not* considered by `rematch`.
|
67
|
+
* `replace` can change destination *filenames* using
|
66
68
|
[Format String Syntax](https://docs.python.org/3/library/string.html#format-string-syntax), with values supplied by any groups defined in `rematch`.
|
69
|
+
This cannot rename directories.
|
70
|
+
* `strip` can remove (up to) the given number of path components from the relative
|
71
|
+
`src` path.
|
67
72
|
|
68
73
|
**Ignore patterns**
|
69
74
|
|
@@ -26,7 +26,7 @@
|
|
26
26
|
|
27
27
|
[project]
|
28
28
|
name = "partis-pyproj"
|
29
|
-
version = "0.
|
29
|
+
version = "0.2.0"
|
30
30
|
description = "Minimal set of Python project utilities (PEP-517/621)"
|
31
31
|
maintainers = [
|
32
32
|
{ name = "Nanohmics Inc.", email = "software.support@nanohmics.com" } ]
|
@@ -119,7 +119,7 @@ copy = [
|
|
119
119
|
#===============================================================================
|
120
120
|
[tool.noxfile]
|
121
121
|
python = [
|
122
|
-
"3.14",
|
122
|
+
# "3.14",
|
123
123
|
"3.13",
|
124
124
|
"3.12",
|
125
125
|
"3.11",
|
@@ -1,6 +1,7 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
import os
|
3
3
|
import os.path as osp
|
4
|
+
import sys
|
4
5
|
import tempfile
|
5
6
|
import sysconfig
|
6
7
|
import re
|
@@ -30,6 +31,23 @@ from ..pptoml import pyproj_targets
|
|
30
31
|
|
31
32
|
ERROR_REC = re.compile(r"error:", re.I)
|
32
33
|
|
34
|
+
pyexe = sys.executable
|
35
|
+
|
36
|
+
try:
|
37
|
+
pyexe = osp.realpath(pyexe)
|
38
|
+
except Exception:
|
39
|
+
...
|
40
|
+
|
41
|
+
# fallback for commonly needed config. variables, but sometimes are not set
|
42
|
+
_sysconfig_vars_alt = {
|
43
|
+
'LIBDEST': sysconfig.get_path('stdlib'),
|
44
|
+
'BINLIBDEST': sysconfig.get_path('platstdlib'),
|
45
|
+
'INCLUDEPY': sysconfig.get_path('include'),
|
46
|
+
'EXENAME': pyexe,
|
47
|
+
'BINDIR': osp.dirname(pyexe)}
|
48
|
+
|
49
|
+
_sysconfig_vars = _sysconfig_vars_alt|sysconfig.get_config_vars()
|
50
|
+
|
33
51
|
#===============================================================================
|
34
52
|
class BuildCommandError(ValidationError):
|
35
53
|
pass
|
@@ -71,7 +89,7 @@ class Builder:
|
|
71
89
|
'targets': targets,
|
72
90
|
'env': os.environ,
|
73
91
|
'tmpdir': self.tmpdir,
|
74
|
-
'config_vars':
|
92
|
+
'config_vars': _sysconfig_vars},
|
75
93
|
root=root,
|
76
94
|
# better way for builders to whitelist templated directories?
|
77
95
|
dirs=[self.tmpdir, Path(tempfile.gettempdir())/'partis-pyproj-downloads'])
|
@@ -20,6 +20,18 @@ from ..norms import b64_nopad, nonempty_str
|
|
20
20
|
# replace runs of non-alphanumeric, dot, dash, or underscore
|
21
21
|
_filename_subs = re.compile(r'[^a-z0-9\.\-\_]+', re.I)
|
22
22
|
|
23
|
+
try:
|
24
|
+
# prefer user home directory to avoid clashing in global "tmp" directory
|
25
|
+
CACHE_DIR = Path.home()/'.cache'/'partis-pyproj'
|
26
|
+
|
27
|
+
except RuntimeError:
|
28
|
+
# use global temporary directory, suffixed by username to try to avoid conficts
|
29
|
+
# between users
|
30
|
+
import getpass
|
31
|
+
username = getpass.getuser()
|
32
|
+
tmp_dir = tempfile.gettempdir()
|
33
|
+
CACHE_DIR = Path(tmp_dir)/f'.cache-partis-pyproj-{username}'
|
34
|
+
|
23
35
|
#===============================================================================
|
24
36
|
def download(
|
25
37
|
pyproj,
|
@@ -94,7 +106,12 @@ def download(
|
|
94
106
|
try:
|
95
107
|
logger.info(f"- downloading: {url} -> {tmp_file}")
|
96
108
|
|
97
|
-
|
109
|
+
req = requests.get(url, stream=True)
|
110
|
+
|
111
|
+
if not req.ok:
|
112
|
+
req.raise_for_status()
|
113
|
+
|
114
|
+
with req, tmp_file.open('wb') as fp:
|
98
115
|
for chunk in req.iter_content(chunk_size=chunk_size):
|
99
116
|
if chunk:
|
100
117
|
fp.write(chunk)
|
@@ -107,6 +124,9 @@ def download(
|
|
107
124
|
logger.info(f"- {size/1e6:,.1f} MB")
|
108
125
|
last_size = size
|
109
126
|
|
127
|
+
if size == 0:
|
128
|
+
raise ValidationError(f"Downloaded file had zero size: {url}")
|
129
|
+
|
110
130
|
logger.info(f"- complete {size/1e6:,.1f} MB")
|
111
131
|
|
112
132
|
if hash:
|
@@ -137,18 +157,24 @@ def download(
|
|
137
157
|
out_file.symlink_to(cache_file)
|
138
158
|
|
139
159
|
if extract:
|
140
|
-
|
160
|
+
out_dir = build_dir
|
161
|
+
|
162
|
+
if isinstance(extract, (str,Path)):
|
163
|
+
out_dir = extract
|
164
|
+
|
165
|
+
logger.info(f"- extracting: {cache_file} -> {out_dir}")
|
166
|
+
|
141
167
|
with tarfile.open(cache_file, 'r:*') as fp:
|
142
168
|
if sys.version_info >= (3, 12):
|
143
169
|
# 'filter' argument added, controls behavior of extract
|
144
170
|
fp.extractall(
|
145
|
-
path=
|
171
|
+
path=out_dir,
|
146
172
|
members=None,
|
147
173
|
numeric_owner=False,
|
148
174
|
filter='tar')
|
149
175
|
else:
|
150
176
|
fp.extractall(
|
151
|
-
path=
|
177
|
+
path=out_dir,
|
152
178
|
members=None,
|
153
179
|
numeric_owner=False)
|
154
180
|
|
@@ -161,7 +187,6 @@ def _cached_download(url: str, checksum: str) -> Path:
|
|
161
187
|
if not checksum:
|
162
188
|
checksum = '0'
|
163
189
|
|
164
|
-
cache_dir = Path(tempfile.gettempdir())/'partis-pyproj-downloads'
|
165
190
|
name = url.split('/')[-1]
|
166
191
|
_url = url
|
167
192
|
|
@@ -178,7 +203,7 @@ def _cached_download(url: str, checksum: str) -> Path:
|
|
178
203
|
url_dirname = _filename_subs.sub('_', _url)
|
179
204
|
url_filename = f"{short}-" + _filename_subs.sub('_', name)
|
180
205
|
|
181
|
-
url_dir =
|
206
|
+
url_dir = CACHE_DIR/url_dirname
|
182
207
|
url_dir.mkdir(exist_ok=True, parents=True)
|
183
208
|
|
184
209
|
file = url_dir/url_filename
|
@@ -0,0 +1,187 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
import os
|
3
|
+
import glob
|
4
|
+
from pathlib import (
|
5
|
+
Path)
|
6
|
+
import logging
|
7
|
+
from ..validate import (
|
8
|
+
FileOutsideRootError,
|
9
|
+
ValidationError,
|
10
|
+
validating )
|
11
|
+
from ..path import (
|
12
|
+
PathFilter,
|
13
|
+
subdir,
|
14
|
+
combine_ignore_patterns,
|
15
|
+
resolve)
|
16
|
+
from ..pptoml import (
|
17
|
+
pyproj_dist_copy)
|
18
|
+
|
19
|
+
#===============================================================================
|
20
|
+
def _rglob(pattern: str, *, root_dir: Path) -> list[Path]:
|
21
|
+
# detect if glob will be recursive by finding '**' in the pattern
|
22
|
+
recursive = '**' in pattern
|
23
|
+
|
24
|
+
# NOTE: root_dir added in Python 3.10
|
25
|
+
cwd = Path.cwd()
|
26
|
+
|
27
|
+
try:
|
28
|
+
os.chdir(root_dir)
|
29
|
+
matches = glob.glob(pattern, recursive = recursive)
|
30
|
+
|
31
|
+
matches = [Path(m) for m in matches]
|
32
|
+
|
33
|
+
if recursive:
|
34
|
+
# don't match directories in recursive mode, since copying a parent
|
35
|
+
# directory negates the need to recurse
|
36
|
+
matches = [m for m in matches if not m.is_dir()]
|
37
|
+
|
38
|
+
finally:
|
39
|
+
os.chdir(cwd)
|
40
|
+
|
41
|
+
return matches
|
42
|
+
|
43
|
+
#===============================================================================
|
44
|
+
def dist_iter(*,
|
45
|
+
copy_items: list[pyproj_dist_copy],
|
46
|
+
ignore: list[str],
|
47
|
+
root: Path,
|
48
|
+
logger: logging.Logger):
|
49
|
+
|
50
|
+
patterns = PathFilter(
|
51
|
+
patterns = ignore )
|
52
|
+
|
53
|
+
|
54
|
+
|
55
|
+
for i, incl in enumerate(copy_items):
|
56
|
+
src = incl.src
|
57
|
+
dst = incl.dst
|
58
|
+
_ignore = incl.ignore
|
59
|
+
|
60
|
+
_ignore_patterns = combine_ignore_patterns(
|
61
|
+
patterns,
|
62
|
+
PathFilter(
|
63
|
+
patterns = _ignore,
|
64
|
+
start = src ) )
|
65
|
+
|
66
|
+
if not incl.include:
|
67
|
+
# each copy specifies a single path
|
68
|
+
# logger.debug(f" - from: {src}\n - to: {dst}")
|
69
|
+
yield ( i, src, dst, _ignore_patterns, True )
|
70
|
+
|
71
|
+
else:
|
72
|
+
# each copy can result in many paths
|
73
|
+
for incl_pattern in incl.include:
|
74
|
+
# produce list of possible copies by glob pattern, relative to 'src'
|
75
|
+
matches = _rglob(incl_pattern.glob, root_dir=src)
|
76
|
+
# logger.debug(f"- glob: {len(matches)} matches with pattern {incl_pattern.glob!r} in {str(src)!r}")
|
77
|
+
|
78
|
+
if not matches:
|
79
|
+
logger.warning(f"Copy pattern did not yield any files: {incl_pattern.glob!r}")
|
80
|
+
continue
|
81
|
+
|
82
|
+
for i, match in enumerate(matches):
|
83
|
+
parent = match.parent
|
84
|
+
src_filename = match.name
|
85
|
+
|
86
|
+
if _ignore_patterns(src/parent, [src_filename]):
|
87
|
+
# Only filter by ignore pattern if this path was part of a glob
|
88
|
+
# logger.debug(f" - ignored: {match}")
|
89
|
+
continue
|
90
|
+
|
91
|
+
# logger.debug(f" - match: {match}")
|
92
|
+
|
93
|
+
if incl_pattern.strip:
|
94
|
+
# remove leading path components
|
95
|
+
dst_parent = type(parent)(*parent.parts[incl_pattern.strip:])
|
96
|
+
# logger.debug(f" - stripped: {parent.parts[:incl_pattern.strip]}")
|
97
|
+
else:
|
98
|
+
dst_parent = parent
|
99
|
+
|
100
|
+
# match to regular expression
|
101
|
+
m = incl_pattern.rematch.fullmatch(src_filename)
|
102
|
+
|
103
|
+
if not m:
|
104
|
+
# logger.debug(f" - !rematch: {src_filename!r} (pattern = {incl_pattern.rematch})")
|
105
|
+
continue
|
106
|
+
|
107
|
+
# apply replacement
|
108
|
+
if incl_pattern.replace == '{0}':
|
109
|
+
dst_filename = src_filename
|
110
|
+
|
111
|
+
else:
|
112
|
+
args = (m.group(0), *m.groups())
|
113
|
+
kwargs = m.groupdict()
|
114
|
+
|
115
|
+
try:
|
116
|
+
dst_filename = incl_pattern.replace.format(*args, **kwargs)
|
117
|
+
# logger.debug(f" - renamed: {dst_filename!r} (template = {incl_pattern.replace!r})")
|
118
|
+
|
119
|
+
except (IndexError, KeyError) as e:
|
120
|
+
raise ValidationError(
|
121
|
+
f"Replacement '{incl_pattern.replace}' failed for"
|
122
|
+
f" '{incl_pattern.rematch.pattern}':"
|
123
|
+
f" {args}, {kwargs}") from None
|
124
|
+
|
125
|
+
_src = src/parent/src_filename
|
126
|
+
# re-base the dst path, (path relative to src) == (path relative to dst)
|
127
|
+
_dst = dst/dst_parent/dst_filename
|
128
|
+
|
129
|
+
# logger.debug(f" - from: {str(_src)!r}\n - to: {str(_dst)!r}")
|
130
|
+
|
131
|
+
yield (i, _src, _dst, _ignore_patterns, False)
|
132
|
+
|
133
|
+
|
134
|
+
#===============================================================================
|
135
|
+
def dist_copy(*,
|
136
|
+
base_path: Path,
|
137
|
+
copy_items: list[pyproj_dist_copy],
|
138
|
+
ignore,
|
139
|
+
dist,
|
140
|
+
root = None,
|
141
|
+
logger = None ):
|
142
|
+
|
143
|
+
if len(copy_items) == 0:
|
144
|
+
return
|
145
|
+
|
146
|
+
logger = logger or logging.getLogger( __name__ )
|
147
|
+
|
148
|
+
history: dict[Path, Path] = {}
|
149
|
+
|
150
|
+
with validating(key = 'copy'):
|
151
|
+
|
152
|
+
for i, src, dst, ignore_patterns, individual in dist_iter(
|
153
|
+
copy_items = copy_items,
|
154
|
+
ignore = ignore,
|
155
|
+
root = root,
|
156
|
+
logger = logger):
|
157
|
+
|
158
|
+
with validating(key = i):
|
159
|
+
|
160
|
+
dst = base_path.joinpath(dst)
|
161
|
+
src_abs = resolve(src)
|
162
|
+
|
163
|
+
if root and not subdir(root, src_abs, check = False):
|
164
|
+
raise FileOutsideRootError(
|
165
|
+
f"Must have common path with root:\n file = \"{src_abs}\"\n root = \"{root}\"")
|
166
|
+
|
167
|
+
_src = history.get(dst)
|
168
|
+
|
169
|
+
if _src == src:
|
170
|
+
continue
|
171
|
+
|
172
|
+
if _src is not None:
|
173
|
+
raise ValidationError(
|
174
|
+
f"Overwriting destination {str(dst)!r} from {str(_src)!r} with {str(src)!r}")
|
175
|
+
|
176
|
+
history[dst] = src
|
177
|
+
|
178
|
+
if src.is_dir():
|
179
|
+
dist.copytree(
|
180
|
+
src = src,
|
181
|
+
dst = dst,
|
182
|
+
ignore = ignore_patterns )
|
183
|
+
|
184
|
+
else:
|
185
|
+
dist.copyfile(
|
186
|
+
src = src,
|
187
|
+
dst = dst )
|
@@ -1,42 +1,18 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
import sys
|
3
|
-
import os
|
4
|
-
import os.path as osp
|
5
|
-
import io
|
6
|
-
import warnings
|
7
|
-
import stat
|
8
3
|
import re
|
9
|
-
import pathlib
|
10
4
|
import inspect
|
11
|
-
from copy import copy
|
12
|
-
from collections.abc import (
|
13
|
-
Mapping,
|
14
|
-
Sequence,
|
15
|
-
Iterable )
|
16
5
|
|
17
6
|
from collections import namedtuple
|
18
|
-
import hashlib
|
19
|
-
from base64 import urlsafe_b64encode
|
20
|
-
from email.message import Message
|
21
|
-
from email.generator import BytesGenerator
|
22
7
|
from email.utils import parseaddr, formataddr
|
23
8
|
from urllib.parse import urlparse
|
24
9
|
import keyword
|
25
10
|
|
26
11
|
from packaging.tags import sys_tags
|
27
|
-
from packaging.requirements import Requirement
|
28
|
-
from packaging.specifiers import SpecifierSet
|
29
|
-
from packaging.version import VERSION_PATTERN
|
30
12
|
|
31
13
|
from .validate import (
|
32
14
|
ValidationError,
|
33
|
-
validating
|
34
|
-
valid_type,
|
35
|
-
valid_keys,
|
36
|
-
valid_dict,
|
37
|
-
valid_list,
|
38
|
-
mapget,
|
39
|
-
as_list)
|
15
|
+
validating)
|
40
16
|
|
41
17
|
#===============================================================================
|
42
18
|
CompatibilityTags = namedtuple('CompatibilityTags', ['py_tag', 'abi_tag', 'plat_tag'])
|
@@ -152,7 +128,7 @@ def norm_dist_name( name ):
|
|
152
128
|
|
153
129
|
# > The name should be lowercased with all runs of the
|
154
130
|
# > characters ., -, or _ replaced with a single - character.
|
155
|
-
name =
|
131
|
+
name = pep_503_name_norm.sub('-', name)
|
156
132
|
|
157
133
|
return name
|
158
134
|
|
@@ -197,6 +173,12 @@ def join_dist_filename( parts ):
|
|
197
173
|
def norm_dist_version( version ):
|
198
174
|
"""Checks for valid distribution version (:pep:`440`)
|
199
175
|
|
176
|
+
.. versionchanged:: 0.1.9
|
177
|
+
|
178
|
+
Allow local version identifiers ``<public version identifier>[+<local version label>]``,
|
179
|
+
in addition to public versions.
|
180
|
+
Version pattern now uses :ref:`~packaging.version.VERSION_PATTERN`.
|
181
|
+
|
200
182
|
See Also
|
201
183
|
--------
|
202
184
|
* https://www.python.org/dev/peps/pep-0440/#version-scheme
|
@@ -393,16 +375,30 @@ def norm_dist_url( label, url ):
|
|
393
375
|
def norm_dist_extra( extra ):
|
394
376
|
"""Normalize distribution 'extra' requirement
|
395
377
|
|
378
|
+
.. versionchanged:: 0.2.0
|
379
|
+
|
380
|
+
Extra names are normalized according to PEP-685 and validated according to
|
381
|
+
Core Metadata 2.3.
|
382
|
+
Previously, extra names "must be a valid Python identifier" (Core Metadata 2.1)
|
383
|
+
|
384
|
+
|
396
385
|
Note
|
397
386
|
----
|
398
|
-
*
|
387
|
+
* MUST write out extra names in their normalized form.
|
388
|
+
* This applies to the Provides-Extra field and the extra marker when used
|
389
|
+
in the Requires-Dist field.
|
390
|
+
|
391
|
+
See Also
|
392
|
+
--------
|
393
|
+
* https://peps.python.org/pep-0685/#specification
|
399
394
|
"""
|
400
395
|
|
401
|
-
extra = norm_printable(
|
396
|
+
extra = norm_printable(extra).lower()
|
397
|
+
extra = pep_503_name_norm.sub('-', extra)
|
402
398
|
|
403
|
-
if not
|
399
|
+
if not pep_685_extra.fullmatch(extra):
|
404
400
|
raise PEPValidationError(
|
405
|
-
pep =
|
401
|
+
pep = 685,
|
406
402
|
msg = "Invalid extra",
|
407
403
|
val = extra )
|
408
404
|
|
@@ -593,6 +589,8 @@ def norm_entry_point_name( name ):
|
|
593
589
|
See Also
|
594
590
|
--------
|
595
591
|
* https://packaging.python.org/en/latest/specifications/entry-points/
|
592
|
+
* The name may contain any characters except =, but it cannot start or end with
|
593
|
+
any whitespace character, or start with [
|
596
594
|
"""
|
597
595
|
|
598
596
|
name = norm_printable( name )
|
@@ -638,15 +636,49 @@ def norm_entry_point_ref( ref ):
|
|
638
636
|
or 'importable.module:object.attr': {ref}""") from e
|
639
637
|
|
640
638
|
#===============================================================================
|
639
|
+
# https://packaging.python.org/en/latest/specifications/name-normalization/#name-format
|
641
640
|
pep426_dist_name = re.compile(
|
642
641
|
r'^([A-Z0-9]|[A-Z0-9][A-Z0-9._\-]*[A-Z0-9])$',
|
643
642
|
re.IGNORECASE )
|
644
643
|
|
644
|
+
# https://packaging.python.org/en/latest/specifications/name-normalization/#name-normalization
|
645
|
+
# > runs of characters ., -, or _ replaced with a single - character.
|
646
|
+
pep_503_name_norm = re.compile(r'[\-\_\.]+', re.IGNORECASE)
|
647
|
+
|
648
|
+
# value of packaging.version.VERSION_PATTERN, as of 'packaging == 25.0'
|
649
|
+
# just in case the variable is ever deprecated
|
650
|
+
VERSION_PATTERN = r"""
|
651
|
+
v?
|
652
|
+
(?:
|
653
|
+
(?:(?P<epoch>[0-9]+)!)? # epoch
|
654
|
+
(?P<release>[0-9]+(?:\.[0-9]+)*) # release segment
|
655
|
+
(?P<pre> # pre-release
|
656
|
+
[-_\.]?
|
657
|
+
(?P<pre_l>alpha|a|beta|b|preview|pre|c|rc)
|
658
|
+
[-_\.]?
|
659
|
+
(?P<pre_n>[0-9]+)?
|
660
|
+
)?
|
661
|
+
(?P<post> # post release
|
662
|
+
(?:-(?P<post_n1>[0-9]+))
|
663
|
+
|
|
664
|
+
(?:
|
665
|
+
[-_\.]?
|
666
|
+
(?P<post_l>post|rev|r)
|
667
|
+
[-_\.]?
|
668
|
+
(?P<post_n2>[0-9]+)?
|
669
|
+
)
|
670
|
+
)?
|
671
|
+
(?P<dev> # dev release
|
672
|
+
[-_\.]?
|
673
|
+
(?P<dev_l>dev)
|
674
|
+
[-_\.]?
|
675
|
+
(?P<dev_n>[0-9]+)?
|
676
|
+
)?
|
677
|
+
)
|
678
|
+
(?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
|
679
|
+
"""
|
680
|
+
|
645
681
|
pep440_version = re.compile(VERSION_PATTERN, re.VERBOSE | re.IGNORECASE)
|
646
|
-
# pep440_version = re.compile(
|
647
|
-
# r'^([1-9][0-9]*!)?(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))*'
|
648
|
-
# r'((a|b|rc)(0|[1-9][0-9]*))?'
|
649
|
-
# r'(\.post(0|[1-9][0-9]*))?(\.dev(0|[1-9][0-9]*))?$' )
|
650
682
|
|
651
683
|
# NOTE: PEP 427 does not specify any constraints on the string following the
|
652
684
|
# digits, but given the form it is used in the filenames it really cannot
|
@@ -720,16 +752,22 @@ pep_301_classifier = re.compile(
|
|
720
752
|
pep_621_keyword = re.compile( r'^[^\s\,]+$' )
|
721
753
|
|
722
754
|
#===============================================================================
|
723
|
-
|
755
|
+
# https://packaging.python.org/en/latest/specifications/core-metadata/#core-metadata-provides-extra
|
756
|
+
pep_685_extra = re.compile( r'^[a-z0-9]+(-[a-z0-9]+)*$', re.IGNORECASE)
|
757
|
+
|
724
758
|
|
725
759
|
#===============================================================================
|
726
760
|
# https://packaging.python.org/en/latest/specifications/entry-points/
|
727
761
|
# Group names must be one or more groups of letters, numbers and underscores,
|
728
762
|
# separated by dots
|
729
763
|
entry_point_group = re.compile( r'^[A-Z0-9_]+(\.[A-Z0-9_]+)*$', re.IGNORECASE )
|
764
|
+
|
765
|
+
# The name may contain any characters except =, but it cannot start or end with
|
766
|
+
# any whitespace character, or start with [
|
730
767
|
# For new entry points (names), it is recommended to use only letters, numbers,
|
731
768
|
# underscores, dots and dashes
|
732
|
-
entry_point_name = re.compile(
|
769
|
+
# entry_point_name = re.compile(r'^([A-Z0-9_\.\-]+)?$', re.IGNORECASE)
|
770
|
+
entry_point_name = re.compile(r'^([^\[\]\=\s]+)?$', re.IGNORECASE)
|
733
771
|
|
734
772
|
#===============================================================================
|
735
773
|
py_keyword = re.compile( '^(' + '|'.join(keyword.kwlist) + ')$' )
|
@@ -121,9 +121,10 @@ class PkgInfoReq:
|
|
121
121
|
self.req = Requirement( norm_printable(req) )
|
122
122
|
|
123
123
|
marker = str( self.req.marker ) if self.req.marker else ''
|
124
|
-
extra = norm_dist_extra(extra)
|
125
124
|
|
126
125
|
if extra:
|
126
|
+
extra = norm_dist_extra(extra)
|
127
|
+
|
127
128
|
if marker:
|
128
129
|
self.req.marker = Marker(f'extra == "{extra}" and ( {marker} )')
|
129
130
|
else:
|
@@ -362,7 +362,8 @@ class include(valid_dict):
|
|
362
362
|
default = {
|
363
363
|
'glob': valid(r'**', nonempty_str),
|
364
364
|
'rematch': valid(r'.*', nonempty_str, re.compile),
|
365
|
-
'replace': valid('{0}', nonempty_str)
|
365
|
+
'replace': valid('{0}', nonempty_str),
|
366
|
+
'strip': valid(int)}
|
366
367
|
|
367
368
|
#===============================================================================
|
368
369
|
class include_list(valid_list):
|
@@ -463,4 +464,5 @@ class pptoml(valid_dict):
|
|
463
464
|
default = {
|
464
465
|
'project': valid(REQUIRED, project),
|
465
466
|
'build-system': valid(REQUIRED, build_system),
|
467
|
+
'dependency-groups': valid(OPTIONAL, dependency_groups),
|
466
468
|
'tool': valid(OPTIONAL, tool) }
|
@@ -364,7 +364,7 @@ class PyProjBase:
|
|
364
364
|
with validating( key = 'tool.pyproj.dist.source'):
|
365
365
|
dist_copy(
|
366
366
|
base_path = dist.named_dirs['root'],
|
367
|
-
|
367
|
+
copy_items = self.source.copy,
|
368
368
|
ignore = self.dist.ignore + self.source.ignore,
|
369
369
|
dist = dist,
|
370
370
|
root = self.root,
|
@@ -413,7 +413,7 @@ class PyProjBase:
|
|
413
413
|
|
414
414
|
dist_copy(
|
415
415
|
base_path = dist.named_dirs['root'],
|
416
|
-
|
416
|
+
copy_items = self.binary.copy,
|
417
417
|
ignore = ignore,
|
418
418
|
dist = dist,
|
419
419
|
root = self.root,
|
@@ -437,7 +437,7 @@ class PyProjBase:
|
|
437
437
|
with validating( key = k ):
|
438
438
|
dist_copy(
|
439
439
|
base_path = dist.named_dirs[k],
|
440
|
-
|
440
|
+
copy_items = _include,
|
441
441
|
ignore = _ignore,
|
442
442
|
dist = dist,
|
443
443
|
root = self.root,
|
@@ -1,139 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
import os
|
3
|
-
import glob
|
4
|
-
from pathlib import (
|
5
|
-
Path)
|
6
|
-
import logging
|
7
|
-
from ..validate import (
|
8
|
-
FileOutsideRootError,
|
9
|
-
validating )
|
10
|
-
from ..path import (
|
11
|
-
PathFilter,
|
12
|
-
subdir,
|
13
|
-
combine_ignore_patterns,
|
14
|
-
resolve)
|
15
|
-
|
16
|
-
# #===============================================================================
|
17
|
-
# def rematch_replace(rematch, replace, name):
|
18
|
-
|
19
|
-
# m = rematch.fullmatch(path)
|
20
|
-
# if not m:
|
21
|
-
# continue
|
22
|
-
|
23
|
-
# args = (m.group(0), *m.groups())
|
24
|
-
# kwargs = m.groupdict()
|
25
|
-
# try:
|
26
|
-
# replace.format(*args, **kwargs))
|
27
|
-
# except (IndexError, KeyError) as e:
|
28
|
-
# raise ValueError(
|
29
|
-
# f"Replacement '{replace}' failed to format match '{m.group(0)}': "
|
30
|
-
# f"{args}, {kwargs}") from None
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
#===============================================================================
|
35
|
-
def dist_iter(*,
|
36
|
-
include,
|
37
|
-
ignore,
|
38
|
-
root ):
|
39
|
-
|
40
|
-
patterns = PathFilter(
|
41
|
-
patterns = ignore )
|
42
|
-
|
43
|
-
for i, incl in enumerate(include):
|
44
|
-
src = incl.src
|
45
|
-
dst = incl.dst
|
46
|
-
_ignore = incl.ignore
|
47
|
-
|
48
|
-
_ignore_patterns = combine_ignore_patterns(
|
49
|
-
patterns,
|
50
|
-
PathFilter(
|
51
|
-
patterns = _ignore,
|
52
|
-
start = src ) )
|
53
|
-
|
54
|
-
if not incl.include:
|
55
|
-
yield ( i, src, dst, _ignore_patterns, True )
|
56
|
-
else:
|
57
|
-
for incl_pattern in incl.include:
|
58
|
-
cwd = Path.cwd()
|
59
|
-
try:
|
60
|
-
# TODO: better way of globing *relative* to src directory
|
61
|
-
# root_dir added in Python 3.10
|
62
|
-
os.chdir(src)
|
63
|
-
matches = glob.glob(incl_pattern.glob, recursive = True)
|
64
|
-
finally:
|
65
|
-
os.chdir(cwd)
|
66
|
-
|
67
|
-
for match in matches:
|
68
|
-
match = Path(match)
|
69
|
-
basename = match.parent
|
70
|
-
src_filename = match.name
|
71
|
-
|
72
|
-
m = incl_pattern.rematch.fullmatch(src_filename)
|
73
|
-
if not m:
|
74
|
-
continue
|
75
|
-
|
76
|
-
args = (m.group(0), *m.groups())
|
77
|
-
kwargs = m.groupdict()
|
78
|
-
try:
|
79
|
-
dst_filename = incl_pattern.replace.format(*args, **kwargs)
|
80
|
-
except (IndexError, KeyError) as e:
|
81
|
-
raise ValueError(
|
82
|
-
f"Replacement '{incl_pattern.replace}' failed for"
|
83
|
-
f" '{incl_pattern.rematch.pattern}':"
|
84
|
-
f" {args}, {kwargs}") from None
|
85
|
-
|
86
|
-
_src = src/basename/src_filename
|
87
|
-
# re-base the dst path, path relative to src == path relative to dst
|
88
|
-
_dst = dst/basename/dst_filename
|
89
|
-
|
90
|
-
yield (i, _src, _dst, _ignore_patterns, False)
|
91
|
-
|
92
|
-
|
93
|
-
#===============================================================================
|
94
|
-
def dist_copy(*,
|
95
|
-
base_path,
|
96
|
-
include,
|
97
|
-
ignore,
|
98
|
-
dist,
|
99
|
-
root = None,
|
100
|
-
logger = None ):
|
101
|
-
|
102
|
-
if len(include) == 0:
|
103
|
-
return
|
104
|
-
|
105
|
-
logger = logger or logging.getLogger( __name__ )
|
106
|
-
|
107
|
-
with validating(key = 'copy'):
|
108
|
-
|
109
|
-
for i, src, dst, ignore_patterns, individual in dist_iter(
|
110
|
-
include = include,
|
111
|
-
ignore = ignore,
|
112
|
-
root = root ):
|
113
|
-
|
114
|
-
with validating(key = i):
|
115
|
-
|
116
|
-
dst = base_path.joinpath(dst)
|
117
|
-
|
118
|
-
if not individual and ignore_patterns( src.parent, [src.name]):
|
119
|
-
logger.debug( f'ignoring: {src}' )
|
120
|
-
continue
|
121
|
-
|
122
|
-
src_abs = resolve(src)
|
123
|
-
|
124
|
-
if root and not subdir(root, src_abs, check = False):
|
125
|
-
raise FileOutsideRootError(
|
126
|
-
f"Must have common path with root:\n file = \"{src_abs}\"\n root = \"{root}\"")
|
127
|
-
|
128
|
-
logger.debug(f"dist copy: {src} -> {dst}")
|
129
|
-
|
130
|
-
if src.is_dir():
|
131
|
-
dist.copytree(
|
132
|
-
src = src,
|
133
|
-
dst = dst,
|
134
|
-
ignore = ignore_patterns )
|
135
|
-
|
136
|
-
else:
|
137
|
-
dist.copyfile(
|
138
|
-
src = src,
|
139
|
-
dst = dst )
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|