partis-pyproj 0.1.6__py3-none-any.whl → 0.1.8__py3-none-any.whl

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.
Files changed (39) hide show
  1. {partis_pyproj-0.1.6.data → partis_pyproj-0.1.8.data}/purelib/partis/pyproj/_nonprintable.py +4 -2
  2. {partis_pyproj-0.1.6.data → partis_pyproj-0.1.8.data}/purelib/partis/pyproj/backend.py +22 -19
  3. {partis_pyproj-0.1.6.data → partis_pyproj-0.1.8.data}/purelib/partis/pyproj/builder/__init__.py +1 -0
  4. {partis_pyproj-0.1.6.data → partis_pyproj-0.1.8.data}/purelib/partis/pyproj/builder/builder.py +39 -6
  5. {partis_pyproj-0.1.6.data → partis_pyproj-0.1.8.data}/purelib/partis/pyproj/builder/cmake.py +2 -2
  6. partis_pyproj-0.1.8.data/purelib/partis/pyproj/builder/download.py +189 -0
  7. {partis_pyproj-0.1.6.data → partis_pyproj-0.1.8.data}/purelib/partis/pyproj/load_module.py +11 -9
  8. {partis_pyproj-0.1.6.data → partis_pyproj-0.1.8.data}/purelib/partis/pyproj/pptoml.py +2 -1
  9. {partis_pyproj-0.1.6.data → partis_pyproj-0.1.8.data}/purelib/partis/pyproj/pyproj.py +1 -0
  10. {partis_pyproj-0.1.6.data → partis_pyproj-0.1.8.data}/purelib/partis/pyproj/template.py +21 -8
  11. {partis_pyproj-0.1.6.dist-info → partis_pyproj-0.1.8.dist-info}/METADATA +53 -20
  12. partis_pyproj-0.1.8.dist-info/RECORD +38 -0
  13. partis_pyproj-0.1.6.dist-info/RECORD +0 -37
  14. {partis_pyproj-0.1.6.data → partis_pyproj-0.1.8.data}/purelib/partis/pyproj/__init__.py +0 -0
  15. {partis_pyproj-0.1.6.data → partis_pyproj-0.1.8.data}/purelib/partis/pyproj/_legacy_setup.py +0 -0
  16. {partis_pyproj-0.1.6.data → partis_pyproj-0.1.8.data}/purelib/partis/pyproj/builder/cargo.py +0 -0
  17. {partis_pyproj-0.1.6.data → partis_pyproj-0.1.8.data}/purelib/partis/pyproj/builder/meson.py +0 -0
  18. {partis_pyproj-0.1.6.data → partis_pyproj-0.1.8.data}/purelib/partis/pyproj/builder/process.py +0 -0
  19. {partis_pyproj-0.1.6.data → partis_pyproj-0.1.8.data}/purelib/partis/pyproj/dist_file/__init__.py +0 -0
  20. {partis_pyproj-0.1.6.data → partis_pyproj-0.1.8.data}/purelib/partis/pyproj/dist_file/dist_base.py +0 -0
  21. {partis_pyproj-0.1.6.data → partis_pyproj-0.1.8.data}/purelib/partis/pyproj/dist_file/dist_binary.py +0 -0
  22. {partis_pyproj-0.1.6.data → partis_pyproj-0.1.8.data}/purelib/partis/pyproj/dist_file/dist_copy.py +0 -0
  23. {partis_pyproj-0.1.6.data → partis_pyproj-0.1.8.data}/purelib/partis/pyproj/dist_file/dist_source.py +0 -0
  24. {partis_pyproj-0.1.6.data → partis_pyproj-0.1.8.data}/purelib/partis/pyproj/dist_file/dist_targz.py +0 -0
  25. {partis_pyproj-0.1.6.data → partis_pyproj-0.1.8.data}/purelib/partis/pyproj/dist_file/dist_zip.py +0 -0
  26. {partis_pyproj-0.1.6.data → partis_pyproj-0.1.8.data}/purelib/partis/pyproj/file.py +0 -0
  27. {partis_pyproj-0.1.6.data → partis_pyproj-0.1.8.data}/purelib/partis/pyproj/legacy.py +0 -0
  28. {partis_pyproj-0.1.6.data → partis_pyproj-0.1.8.data}/purelib/partis/pyproj/norms.py +0 -0
  29. {partis_pyproj-0.1.6.data → partis_pyproj-0.1.8.data}/purelib/partis/pyproj/path/__init__.py +0 -0
  30. {partis_pyproj-0.1.6.data → partis_pyproj-0.1.8.data}/purelib/partis/pyproj/path/match.py +0 -0
  31. {partis_pyproj-0.1.6.data → partis_pyproj-0.1.8.data}/purelib/partis/pyproj/path/pattern.py +0 -0
  32. {partis_pyproj-0.1.6.data → partis_pyproj-0.1.8.data}/purelib/partis/pyproj/path/utils.py +0 -0
  33. {partis_pyproj-0.1.6.data → partis_pyproj-0.1.8.data}/purelib/partis/pyproj/pep.py +0 -0
  34. {partis_pyproj-0.1.6.data → partis_pyproj-0.1.8.data}/purelib/partis/pyproj/pkginfo.py +0 -0
  35. {partis_pyproj-0.1.6.data → partis_pyproj-0.1.8.data}/purelib/partis/pyproj/validate.py +0 -0
  36. {partis_pyproj-0.1.6.dist-info → partis_pyproj-0.1.8.dist-info}/LICENSE.txt +0 -0
  37. {partis_pyproj-0.1.6.dist-info → partis_pyproj-0.1.8.dist-info}/WHEEL +0 -0
  38. {partis_pyproj-0.1.6.dist-info → partis_pyproj-0.1.8.dist-info}/entry_points.txt +0 -0
  39. {partis_pyproj-0.1.6.dist-info → partis_pyproj-0.1.8.dist-info}/top_level.txt +0 -0
@@ -4,13 +4,13 @@ import re
4
4
 
5
5
  #===============================================================================
6
6
  def _gen_nonprintable():
7
- test = ''
7
+ test = []
8
8
 
9
9
  ns = [ [0,], ]
10
10
 
11
11
  for i in range(1, sys.maxunicode+1):
12
12
  c = chr(i)
13
- test += c
13
+ test.append(c)
14
14
 
15
15
  if not ( c.isprintable() or c in '\n\t' ):
16
16
  n = ns[-1]
@@ -23,6 +23,8 @@ def _gen_nonprintable():
23
23
  else:
24
24
  ns.append([i,])
25
25
 
26
+ test = ''.join(test)
27
+
26
28
  # print(len(test), test.isprintable())
27
29
  # print(len(ns))
28
30
  # print(ns)
@@ -1,8 +1,6 @@
1
1
  from __future__ import annotations
2
2
  import os
3
- import os.path as osp
4
- import sys
5
- import shutil
3
+ from copy import copy
6
4
  import logging
7
5
  from logging import (
8
6
  basicConfig,
@@ -22,6 +20,7 @@ from collections.abc import (
22
20
 
23
21
  from . import (
24
22
  valid_keys,
23
+ ValidationError,
25
24
  mapget,
26
25
  dist_build,
27
26
  PkgInfoReq,
@@ -34,7 +33,7 @@ def backend_init(
34
33
  root: str|Path = '',
35
34
  config_settings: dict|None = None,
36
35
  logger: Logger|None = None ):
37
- """Called to inialialize the backend upon a call to one of the hooks
36
+ """Called to initialize the backend upon a call to one of the hooks
38
37
 
39
38
  Parameters
40
39
  ----------
@@ -210,26 +209,30 @@ def build_wheel(
210
209
  * https://www.python.org/dev/peps/pep-0517/#build-wheel
211
210
  """
212
211
 
213
- pyproj = backend_init(config_settings = config_settings)
212
+ try:
213
+ pyproj = backend_init(config_settings = config_settings)
214
214
 
215
- pyproj.dist_prep()
215
+ pyproj.dist_prep()
216
+ pyproj.dist_binary_prep()
216
217
 
217
- pyproj.dist_binary_prep()
218
+ with dist_binary_wheel(
219
+ pkg_info = pyproj.pkg_info,
220
+ build = dist_build(
221
+ pyproj.binary.get('build_number', None),
222
+ pyproj.binary.get('build_suffix', None) ),
223
+ compat = pyproj.binary.compat_tags,
224
+ outdir = wheel_directory,
225
+ logger = pyproj.logger ) as dist:
218
226
 
219
- with dist_binary_wheel(
220
- pkg_info = pyproj.pkg_info,
221
- build = dist_build(
222
- pyproj.binary.get('build_number', None),
223
- pyproj.binary.get('build_suffix', None) ),
224
- compat = pyproj.binary.compat_tags,
225
- outdir = wheel_directory,
226
- logger = pyproj.logger ) as dist:
227
+ pyproj.dist_binary_copy(
228
+ dist = dist )
227
229
 
228
- pyproj.dist_binary_copy(
229
- dist = dist )
230
+ pyproj.logger.info(
231
+ f"Top level packages {dist.top_level}")
230
232
 
231
- pyproj.logger.info(
232
- f"Top level packages {dist.top_level}")
233
+ except ValidationError as e:
234
+ known_exception_type = copy(e)
235
+ raise known_exception_type from None
233
236
 
234
237
  return dist.outname
235
238
 
@@ -1,6 +1,7 @@
1
1
 
2
2
  from .builder import Builder
3
3
  from .process import process
4
+ from .download import download
4
5
  from .meson import meson
5
6
  from .cmake import cmake
6
7
  from .cargo import cargo
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
  import os
3
3
  import os.path as osp
4
- import sys
4
+ import tempfile
5
5
  import sysconfig
6
6
  import re
7
7
  from copy import copy
@@ -61,6 +61,7 @@ class Builder:
61
61
  self.targets = [copy(v) for v in targets]
62
62
  self.clean_dirs = [False]*len(self.targets)
63
63
  self.logger = logger
64
+ self.tmpdir = Path(tempfile.mkdtemp(prefix=f"build-{pyproj.project.name}-"))
64
65
  self.namespace = Namespace({
65
66
  'root': root,
66
67
  'pptoml': pyproj.pptoml,
@@ -69,8 +70,11 @@ class Builder:
69
70
  'config_settings': pyproj.config_settings,
70
71
  'targets': targets,
71
72
  'env': os.environ,
73
+ 'tmpdir': self.tmpdir,
72
74
  'config_vars': sysconfig.get_config_vars()},
73
- root=root)
75
+ root=root,
76
+ # better way for builders to whitelist templated directories?
77
+ dirs=[self.tmpdir, Path(tempfile.gettempdir())/'partis-pyproj-downloads'])
74
78
 
75
79
  #-----------------------------------------------------------------------------
76
80
  def __enter__(self):
@@ -85,11 +89,37 @@ class Builder:
85
89
 
86
90
  #-----------------------------------------------------------------------------
87
91
  def build_targets(self):
92
+ exclusive = {
93
+ target.exclusive: None
94
+ for target in self.targets
95
+ if target.exclusive}
96
+
97
+ if exclusive:
98
+ for i, target in enumerate(self.targets):
99
+ if not (group := target.exclusive):
100
+ continue
101
+
102
+ cur = exclusive[group]
103
+
104
+ if target.enabled:
105
+ if cur is None:
106
+ exclusive[group] = i
107
+
108
+
109
+ missing = [group for group, idx in exclusive.items() if idx is None]
110
+
111
+ if missing:
112
+ raise ValidationError(f"Exclusive group {missing} does not have an enabled target")
113
+
88
114
  for i, target in enumerate(self.targets):
89
115
  if not target.enabled:
90
116
  self.logger.info(f"Skipping targets[{i}], disabled for environment markers")
91
117
  continue
92
118
 
119
+ if (group := target.exclusive) and (group_idx := exclusive.get(group)) != i:
120
+ self.logger.warning(
121
+ f"Skipping targets[{i}], exclusive group {group!r} already satisfied by targets[{group_idx}]")
122
+
93
123
  # each target isolated (shallow) changes to namespace
94
124
  namespace = copy(self.namespace)
95
125
 
@@ -106,9 +136,9 @@ class Builder:
106
136
 
107
137
  abs_path = resolve(abs_path)
108
138
 
109
- if not subdir(self.root, abs_path, check=False):
139
+ if not (subdir(self.root, abs_path, check=False) or subdir(self.tmpdir, abs_path, check=False)):
110
140
  raise FileOutsideRootError(
111
- f"Must be within project root directory:"
141
+ f"Must be within project root directory or tmpdir:"
112
142
  f"file = \"{abs_path}\", root = \"{self.root}\"")
113
143
 
114
144
  if k in ('build_dir', 'prefix') and subdir(abs_path, self.root, check=False):
@@ -249,6 +279,8 @@ class Builder:
249
279
  self.logger.info(f"Removing build dir: {build_dir}")
250
280
  shutil.rmtree(build_dir)
251
281
 
282
+ shutil.rmtree(self.tmpdir)
283
+
252
284
  #===============================================================================
253
285
  class ProcessRunner:
254
286
  #-----------------------------------------------------------------------------
@@ -273,10 +305,11 @@ class ProcessRunner:
273
305
  cmd_exec_src = shutil.which(cmd_exec)
274
306
 
275
307
  if cmd_exec_src is None:
276
- raise ValueError(
308
+ raise ValidationError(
277
309
  f"Executable does not exist or has in-sufficient permissions: {cmd_exec}")
278
310
 
279
- cmd_exec_src = resolve(Path(cmd_exec_src))
311
+ # cmd_exec_src = resolve(Path(cmd_exec_src))
312
+ cmd_exec_src = Path(cmd_exec_src)
280
313
  cmd_name = cmd_exec_src.name
281
314
  args = [str(cmd_exec_src)]+args[1:]
282
315
 
@@ -70,9 +70,9 @@ def cmake(
70
70
 
71
71
  compile_args = [
72
72
  'cmake',
73
- *compile_args,
74
73
  '--build',
75
- str(build_dir) ]
74
+ str(build_dir),
75
+ *compile_args]
76
76
 
77
77
  install_args = [
78
78
  'cmake',
@@ -0,0 +1,189 @@
1
+ from __future__ import annotations
2
+ import sys
3
+ import os
4
+ import platform
5
+ import stat
6
+ import re
7
+ from pathlib import Path
8
+ import hashlib
9
+ from urllib.parse import urlsplit
10
+ import tarfile
11
+ import tempfile
12
+ from base64 import urlsafe_b64encode
13
+ import logging
14
+ from .builder import (
15
+ ProcessRunner)
16
+ from ..validate import (
17
+ ValidationError)
18
+ from ..norms import b64_nopad, nonempty_str
19
+
20
+ # replace runs of non-alphanumeric, dot, dash, or underscore
21
+ _filename_subs = re.compile(r'[^a-z0-9\.\-\_]+', re.I)
22
+
23
+ #===============================================================================
24
+ def download(
25
+ pyproj,
26
+ logger: logging.Logger,
27
+ options: dict,
28
+ work_dir: Path,
29
+ src_dir: Path,
30
+ build_dir: Path,
31
+ prefix: Path,
32
+ setup_args: list[str],
33
+ compile_args: list[str],
34
+ install_args: list[str],
35
+ build_clean: bool,
36
+ runner: ProcessRunner):
37
+ """Download a file
38
+ """
39
+ import requests
40
+
41
+ chunk_size = int(options.get('chunk_size', 2**16))
42
+
43
+ url = options.get('url')
44
+ executable = options.get('executable')
45
+
46
+ if not url:
47
+ raise ValidationError(
48
+ "Download 'url' required")
49
+
50
+ url = nonempty_str(url)
51
+
52
+ checksum = options.get('checksum')
53
+
54
+ if checksum is None:
55
+ raise ValidationError(
56
+ "Download 'checksum' required, or explicitly set 'checksum=false'")
57
+
58
+
59
+ filename = options.get('filename', url.split('/')[-1])
60
+ extract = options.get('extract', None)
61
+
62
+ cache_file = _cached_download(url, checksum)
63
+ out_file = build_dir/filename
64
+
65
+ if cache_file.exists():
66
+ logger.info(f"Using cache file: {cache_file}")
67
+
68
+ else:
69
+ # name unique to host/process as countermeasure for race condition
70
+ hostname = re.sub(r'[^a-zA-Z0-9]+', '_', str(platform.node()))
71
+ tmp_name = f"{cache_file.name}-{hostname}-{os.getpid():06d}.tmp"
72
+ tmp_file = cache_file.with_name(tmp_name)
73
+
74
+ if tmp_file.exists():
75
+ tmp_file.unlink()
76
+
77
+ if checksum:
78
+ checksum = checksum.lower()
79
+ alg, _, checksum = checksum.partition('=')
80
+
81
+ try:
82
+ hash = getattr(hashlib, alg)()
83
+
84
+ except AttributeError:
85
+ raise ValidationError(
86
+ f"Checksum algorithm must be one of {hashlib.algorithms_available}: got {alg}") from None
87
+
88
+ else:
89
+ hash = None
90
+
91
+ size = 0
92
+ last_size = 0
93
+
94
+ try:
95
+ logger.info(f"- downloading: {url} -> {tmp_file}")
96
+
97
+ with requests.get(url, stream=True) as req, tmp_file.open('wb') as fp:
98
+ for chunk in req.iter_content(chunk_size=chunk_size):
99
+ if chunk:
100
+ fp.write(chunk)
101
+ size += len(chunk)
102
+
103
+ if hash:
104
+ hash.update(chunk)
105
+
106
+ if size - last_size > 50e6:
107
+ logger.info(f"- {size/1e6:,.1f} MB")
108
+ last_size = size
109
+
110
+ logger.info(f"- complete {size/1e6:,.1f} MB")
111
+
112
+ if hash:
113
+ digest = hash.digest()
114
+
115
+ if checksum.endswith('='):
116
+ digest = urlsafe_b64encode(digest).decode("ascii")
117
+ elif checksum.startswith('x'):
118
+ digest = 'x'+digest.hex()
119
+ else:
120
+ digest = digest.hex()
121
+
122
+ checksum_ok = checksum == digest
123
+ logger.info(f"- checksum{' (OK)' if checksum_ok else ''}: {alg}={digest}")
124
+
125
+ if not checksum_ok:
126
+ raise ValidationError(f"Download checksum did not match: {digest} != {checksum}")
127
+
128
+ except Exception:
129
+ if tmp_file.exists():
130
+ tmp_file.unlink()
131
+
132
+ raise
133
+
134
+ tmp_file.replace(cache_file)
135
+
136
+
137
+ out_file.symlink_to(cache_file)
138
+
139
+ if extract:
140
+ logger.info(f"- extracting: {cache_file} -> {build_dir}")
141
+ with tarfile.open(cache_file, 'r:*') as fp:
142
+ if sys.version_info >= (3, 12):
143
+ # 'filter' argument added, controls behavior of extract
144
+ fp.extractall(
145
+ path=build_dir,
146
+ members=None,
147
+ numeric_owner=False,
148
+ filter='tar')
149
+ else:
150
+ fp.extractall(
151
+ path=build_dir,
152
+ members=None,
153
+ numeric_owner=False)
154
+
155
+ if executable:
156
+ logger.info("- setting executable permission")
157
+ out_file.chmod(out_file.stat().st_mode|stat.S_IXUSR)
158
+
159
+ #===============================================================================
160
+ def _cached_download(url: str, checksum: str) -> Path:
161
+ if not checksum:
162
+ checksum = '0'
163
+
164
+ cache_dir = Path(tempfile.gettempdir())/'partis-pyproj-downloads'
165
+ name = url.split('/')[-1]
166
+ _url = url
167
+
168
+ # hash of url + checksum used to prevent filename collision after url is sanitized
169
+ h = hashlib.sha256()
170
+ h.update(url.encode('utf-8') + checksum.encode('utf-8'))
171
+ # keep only 4 bytes (8 hex characters) worth of the hash
172
+ short = h.digest()[:4].hex()
173
+
174
+ if name != _url:
175
+ # possible for url without a path segment?
176
+ _url = _url.removesuffix('/'+name)
177
+
178
+ url_dirname = _filename_subs.sub('_', _url)
179
+ url_filename = f"{short}-" + _filename_subs.sub('_', name)
180
+
181
+ url_dir = cache_dir/url_dirname
182
+ url_dir.mkdir(exist_ok=True, parents=True)
183
+
184
+ file = url_dir/url_filename
185
+
186
+ info_file = file.with_name(file.name+'.info')
187
+ info_file.write_text(f"{url}\n{checksum}")
188
+
189
+ return file
@@ -24,7 +24,7 @@ from .validate import (
24
24
  mapget )
25
25
 
26
26
  #===============================================================================
27
- class EntryPointError(ValueError):
27
+ class EntryPointError(ValidationError):
28
28
  pass
29
29
 
30
30
  #===============================================================================
@@ -177,16 +177,18 @@ class EntryPoint:
177
177
 
178
178
  cwd = os.getcwd()
179
179
 
180
- try:
181
-
182
- with validating( file = f"{self.name} -> {self.entry}" ):
180
+ with validating( file = f"{self.name} -> {self.entry}" ):
181
+ try:
183
182
  self.func(
184
183
  self.pyproj,
185
184
  logger = self.logger,
186
- **kwargs )
185
+ **kwargs)
187
186
 
188
- except Exception as e:
189
- raise EntryPointError(f"failed to run '{self.entry}'") from e
187
+ except ValidationError:
188
+ raise
189
+
190
+ except Exception as e:
191
+ raise EntryPointError(f"failed to run '{self.entry}'") from e
190
192
 
191
- finally:
192
- os.chdir(cwd)
193
+ finally:
194
+ os.chdir(cwd)
@@ -49,7 +49,7 @@ from .pep import (
49
49
  norm_entry_point_ref,
50
50
  norm_dist_keyword,
51
51
  norm_dist_classifier,
52
- norm_dist_url )
52
+ norm_dist_url)
53
53
 
54
54
  #===============================================================================
55
55
  class dynamic(valid_list):
@@ -260,6 +260,7 @@ class pyproj_build_target(valid_dict):
260
260
  ('compile', 'enabled')]
261
261
  default = {
262
262
  'enabled': valid(True, marker_evaluated),
263
+ 'exclusive': valid('', norm_printable),
263
264
  # NOTE: default builder from backward compatibility
264
265
  'entry': valid('partis.pyproj.builder:meson', norm_entry_point_ref),
265
266
  'options': dict,
@@ -235,6 +235,7 @@ class PyProjBase:
235
235
  meson.pop('entry')
236
236
  meson.pop('work_dir')
237
237
  meson.pop('env')
238
+ meson.pop('exclusive')
238
239
  meson['compile'] = meson.pop('enabled')
239
240
  return pyproj_meson(meson)
240
241
 
@@ -71,12 +71,12 @@ class Template:
71
71
  return '$'
72
72
 
73
73
  if m.group('unterminated') is not None:
74
- raise TemplateError(f"Unterminated template substitution: {m.group()}")
74
+ raise TemplateError(f"Unterminated template substitution {m.group()!r}: {self.template!r}")
75
75
 
76
76
  name = m.group('braced').strip()
77
77
 
78
78
  if not _idpattern.fullmatch(name):
79
- raise TemplateError(f"Invalid template substitution: {name}")
79
+ raise TemplateError(f"Invalid template substitution {name!r}: {self.template!r}")
80
80
 
81
81
  return str(namespace[name])
82
82
 
@@ -95,13 +95,21 @@ class Namespace(Mapping):
95
95
  root:
96
96
  If given, absolute path to project root, used to resolve relative paths and ensure
97
97
  any derived paths are within this parent directory.
98
+ dirs:
99
+ Additional white-listed directories to allow paths
98
100
  """
99
- __slots__ = ['data', 'root']
101
+ __slots__ = ['data', 'root', 'dirs']
100
102
 
101
103
  #-----------------------------------------------------------------------------
102
- def __init__(self, data: Mapping, *, root: Path = None):
104
+ def __init__(self, data: Mapping, *, root: Path = None, dirs: list[Path]|None = None):
105
+ if dirs is None:
106
+ dirs = []
107
+ elif isinstance(dirs, Path):
108
+ dirs = [dirs]
109
+
103
110
  self.data = data
104
111
  self.root = root
112
+ self.dirs = dirs
105
113
 
106
114
  #-----------------------------------------------------------------------------
107
115
  def __iter__(self):
@@ -150,7 +158,10 @@ class Namespace(Mapping):
150
158
  # NOTE: ignored if root is a pure path
151
159
  out = resolve(out)
152
160
 
153
- if not subdir(root, out, check = False):
161
+ if any(subdir(v, out, check = False) for v in self.dirs):
162
+ ...
163
+
164
+ elif not subdir(root, out, check = False):
154
165
  raise FileOutsideRootError(
155
166
  f"Must be within project root directory:"
156
167
  f"\n file = \"{out}\"\n root = \"{root}\"")
@@ -163,6 +174,7 @@ class Namespace(Mapping):
163
174
  obj = cls.__new__(cls)
164
175
  obj.data = copy(self.data)
165
176
  obj.root = self.root
177
+ obj.dirs = self.dirs
166
178
  return obj
167
179
 
168
180
  #-----------------------------------------------------------------------------
@@ -210,9 +222,10 @@ def template_substitute(
210
222
  return cls(Template(value).substitute(namespace))
211
223
 
212
224
  if isinstance(value, Path):
213
- return cls(*(
214
- Template(v).substitute(namespace)
215
- for v in value.parts))
225
+ return cls(Template(str(value)).substitute(namespace))
226
+ # return cls(*(
227
+ # Template(v).substitute(namespace)
228
+ # for v in value.parts))
216
229
 
217
230
  if isinstance(value, Mapping):
218
231
  return cls({
@@ -1,27 +1,28 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: partis-pyproj
3
- Version: 0.1.6
3
+ Version: 0.1.8
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
  Classifier: Programming Language :: Python
10
- Classifier: Programming Language :: Python :: 3
11
- Classifier: Operating System :: Microsoft :: Windows
12
- Classifier: Intended Audience :: Developers
10
+ Classifier: Development Status :: 4 - Beta
13
11
  Classifier: Topic :: Software Development :: Build Tools
14
12
  Classifier: License :: OSI Approved :: BSD License
13
+ Classifier: Programming Language :: Python :: 3
15
14
  Classifier: Operating System :: POSIX :: Linux
16
- Classifier: Development Status :: 4 - Beta
15
+ Classifier: Operating System :: Microsoft :: Windows
16
+ Classifier: Intended Audience :: Developers
17
17
  Provides-Extra: meson
18
18
  Provides-Extra: cmake
19
19
  Requires-Dist: packaging>=24.2
20
20
  Requires-Dist: tomli>=2.0.1
21
+ Requires-Dist: requests>=2.32.3
21
22
  Requires-Dist: ninja>=1.10.2.3; extra == "meson"
22
23
  Requires-Dist: meson>=0.61.3; extra == "meson"
23
- Requires-Dist: ninja>=1.10.2.3; extra == "cmake"
24
24
  Requires-Dist: cmake>=3.24.3; extra == "cmake"
25
+ Requires-Dist: ninja>=1.10.2.3; extra == "cmake"
25
26
  Description-Content-Type: text/markdown
26
27
 
27
28
  [![tests](https://github.com/kcdodd/partis-pyproj/actions/workflows/tests.yaml/badge.svg)](https://github.com/kcdodd/partis-pyproj/actions/workflows/tests.yaml)
@@ -278,6 +279,7 @@ setup_args: array{STRING} # 3-stage build
278
279
  compile_args: array{STRING} # 3-stage build
279
280
  install_args: array{STRING} # 3-stage build
280
281
  options: table{STRING|BOOL}? # options passed to builder from pyproject.toml
282
+ env: table{STRING|STRING}? # environment variables to set
281
283
  build_clean: BOOL? # control cleanup (ie for development builds)
282
284
  enabled: (BOOL|MARKER)? # environment marker
283
285
  ```
@@ -287,11 +289,28 @@ There are several entry points available as-is:
287
289
  - `partis.pyproj.builder:meson` - Support for [Meson Build system](https://mesonbuild.com/) with the 'extra' ``partis-pyproj[meson]``
288
290
  - `partis.pyproj.builder:cmake` - Support for [CMake](https://cmake.org/) with the 'extra' ``partis-pyproj[cmake]``
289
291
  - `partis.pyproj.builder:process` - Support for running arbitrary command line executable
292
+ - `partis.pyproj.builder:download` - Support for downloading a file to `build_dir`
293
+
294
+
295
+ Options for `partis.pyproj.builder:download`:
296
+
297
+ ```
298
+ [tool.pyproj.targets.options]
299
+ url: URL
300
+ checksum: ALG=HEX # expected checksum
301
+ filename: STRING? # rename in build_dir, defaults to mangled version of url
302
+ extract: BOOL? # extract/decompress as a tar file
303
+ executable: BOOL? # set execute permission
304
+ ```
305
+
306
+ Checksum `ALG` can be `sha256`, `md5`, or another algorithm in [hashlib](https://docs.python.org/3/library/hashlib.html)
307
+
308
+ **Example**
290
309
 
291
310
  In this example, the source directory must contain appropriate `meson.build` files,
292
311
  since the 'pyproject.toml' configuration only provides a way of running
293
312
  ``meson setup`` and ``meson compile``.
294
- For example:
313
+
295
314
 
296
315
  ```toml
297
316
  # pyproject.toml
@@ -358,7 +377,7 @@ Python [Template string](https://docs.python.org/3/library/string.html#template-
358
377
  - `$$` is an escape; it is replaced with a single `$`.
359
378
  - `${identifier}` names a substitution placeholder matching a mapping key of "identifier".
360
379
 
361
- However, `$identifier` (without braces) is not supported, instead allowing more expressive substitutions.
380
+ However, `$identifier` (without braces) is *not supported*, but this restriction allows more expressive substitutions.
362
381
 
363
382
  ```
364
383
  substitution: "${" (variable|literal|SEP)+ "}"
@@ -367,16 +386,30 @@ SEP: "/"
367
386
  literal: "'" CHAR+ "'"
368
387
  IDENTIFIER: < python identifier >
369
388
  INTEGER: < integer >
370
- CHAR: < ascii letter, number (not leading), or underscore >
389
+ CHAR: < ascii alpha-numeric, dot ".", dash "-", underscore "_" >
371
390
  ```
372
391
 
373
- Variable names can reference most of the content of the original 'pyproject.toml',
374
- as well as values already substituted in the build target or earlier targets.
392
+ Top-level template variable identifiers can reference the content of the original 'pyproject.toml', config. settings, environment variables, and values already substituted in the build target or earlier targets.
375
393
  If the substitution contains any separators the result is interpreted as a path, converted to platform-specific filesystem format, and resolved to project directory.
376
-
377
- **Example**
378
-
379
- The value of `options.some_option` in the example below would be substituted with a filesystem equivalent path for `{root}/build/something/my_pkg/xyz/abc.so`:
394
+ The template namespace contains the following keys:
395
+
396
+ - `root`: Absolute path to project root directory
397
+ - `tmpdir`: A temporary directory created and shared by all build targets.
398
+ This directory is removed before the distribution is created, so any needed files must be copied back to a location within the project tree by one of the targets
399
+ (eg. the "install" step of 3-stage builds with a `prefix` within the project).
400
+ - `pptoml`: Top-level of parsed `pyproject.toml`
401
+ - `project`: The `project` section, including `name`, `version`, etc.
402
+ - `pyproj`: The `tool.pyproj` section.
403
+ - `config_settings`: A mapping from the `config_settings` passed to backend per PEP-517
404
+ after defaults applied from `tool.pyproj.config` (described below).
405
+ - `targets`': List from `tool.pyproj.targets`, updated as targets are processed.
406
+ - `work_dir`, `src_dir`, `build_dir`, `prefix`: Per-target values (if processed before the substitution)
407
+ - `env`: Defaults to `os.environ`, or per-target value from `tool.pyproj.targets.env`
408
+ (if processed before the substitution).
409
+ - `options`: Per-target value from `tool.pyproj.targets.options` (if processed before the substitution)
410
+
411
+ Template substitutions are processed (once) in the *order in which they appear* from the `pyproject.toml`, no static analysis is performed. It is up to the developer to put them in the needed order if one template references a value resulting from another template.
412
+ In the example below, the value of `options.some_option` would be substituted with a filesystem equivalent path for `{root}/build/something/my_pkg/xyz/abc.so`:
380
413
 
381
414
 
382
415
  ```toml
@@ -397,14 +430,14 @@ location according to a local installation scheme
397
430
  these can be specified within sub-tables.
398
431
  Available install scheme keys, and **example** corresponding install locations, are:
399
432
 
400
- - `purelib` ("pure" library Python path): ``{prefix}/lib/python{X}.{Y}/site-packages/``
401
- - `platlib` (platform specific Python path): ``{prefix}/lib{platform}/python{X}.{Y}/site-packages/``
433
+ - `purelib` ("pure" library Python path): ``{venv}/lib/python{X}.{Y}/site-packages/``
434
+ - `platlib` (platform specific Python path): ``{venv}/lib{platform}/python{X}.{Y}/site-packages/``
402
435
  Both `purelib` and `platlib` install to the base 'site-packages'
403
436
  directory, so any files copied to these paths should be placed within a
404
437
  desired top-level package directory.
405
438
 
406
- - `headers` (INCLUDE search paths): ``{prefix}/include/{site}/python{X}.{Y}{abiflags}/{distname}/``
407
- - `scripts` (executable search path): ``{prefix}/bin/``
439
+ - `headers` (INCLUDE search paths): ``{venv}/include/{site}/python{X}.{Y}{abiflags}/{distname}/``
440
+ - `scripts` (executable search path): ``{venv}/bin/``
408
441
  Even though any files added to the `scripts` path will be installed to
409
442
  the `bin` directory, there is often an issue with the 'execute' permission
410
443
  being set correctly by the installer (e.g. `pip`).
@@ -412,7 +445,7 @@ Available install scheme keys, and **example** corresponding install locations,
412
445
  use the ``[project.scripts]`` section to add an entry point that will then
413
446
  run the desired executable as a sub-process.
414
447
 
415
- - `data` (generic data path): ``{prefix}/``
448
+ - `data` (generic data path): ``{venv}/``
416
449
 
417
450
  ```toml
418
451
  # pyproject.toml
@@ -0,0 +1,38 @@
1
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/file.py,sha256=cU327vzXGfuMWlqY-LdfGqGs7LOhVd0MOUOIdAL5od4,1581
2
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/__init__.py,sha256=-CibR9jSfwRxNn4gHe-7_ZrQnH3FY4JYTi3Mz-Q66cU,1560
3
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/validate.py,sha256=ZYzaiHaWWkw9RLL-AgVfch-3gacNg5s1Vl0yy6gX4-U,32168
4
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/_legacy_setup.py,sha256=tAE0xlD9RVhhMn1kcUPSccbDFE1ReuH99jfrw8q8MiM,5301
5
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/norms.py,sha256=uyF3Pi9NbptleTabdZlMkaKJB5ymfDOSrcpD6bygQeM,10909
6
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/builder/cargo.py,sha256=z9yj7QNZqCV_hFRBOsBBWo1GgOWZF0Oqag0kLpfN-2I,1144
7
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/builder/__init__.py,sha256=IRQk2gA5mK6BjewVHQ1IcVLVENV2vBMrwS4JO8wGPYc,164
8
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/builder/meson.py,sha256=V39hN5bJ4kc9JBTXwT0XIc9uMtnKtrYczWC28jqu-GU,1939
9
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/builder/process.py,sha256=8tUuFQrnks6Q-0G6Ow8G7LOycc2Z5olqLBzGhRCAulg,1002
10
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/builder/download.py,sha256=RfDeAsGXMCAYct5REXroF7QwncQdrliYhMzm8ePxE94,4981
11
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/builder/cmake.py,sha256=soKtbHkLttRq96NdLTD8dlD5SgzxrFYqJadC9V8HSjw,1915
12
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/builder/builder.py,sha256=pOIBUG5iEVsjh9SglIky41kgWLcAyHdEDAWzym2CM2g,11467
13
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/pyproj.py,sha256=Ra7DOj2Pq5peC_pT82xQoAxCDX3sHukpbKgNSqNeI8c,12758
14
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/pptoml.py,sha256=uThSRs6EMA51nFo38XufFCVEWifC83prM85oCKqxliQ,13565
15
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/legacy.py,sha256=JXSg-5Y65PUOtlEgkIgNj3vwZtJFi9CVsRvnpar2xKs,1749
16
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/template.py,sha256=1OmPPAEOLz0kquypFEb_c2aLysTGOwC2zqw1_eXYutY,6908
17
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/pkginfo.py,sha256=6rz06s19MX7zQIWt2wE-HEg-1WA2LZogk9kdqKFFcVE,15148
18
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/_nonprintable.py,sha256=2-JeNY6prFM4cfZKeFuv-aZW7ZvPI8FYO7dJ0CL3RD8,1893
19
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/pep.py,sha256=XzZYnYKaB4V3wOXUsrGNav9H_ziMoaQb9wGEKlciOms,32694
20
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/load_module.py,sha256=6y3oMqCvycS4EhcOn1NtuXWyix0smCnogEH6W0bWiPg,4396
21
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/backend.py,sha256=sKubLkwD30n_nwFnIqQ-T4Dh5UFZUXf5r5yrl5oNiXs,7004
22
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/path/__init__.py,sha256=458RSB1Lr63sY3CP_uqxaHkEUQMgeqZgNXFzpUIbsn4,236
23
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/path/utils.py,sha256=lJUyTGecZDMoTphSItr99_4b_yp_4zMRd8IAFiXgsI4,2364
24
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/path/match.py,sha256=zyeGJtVQ-gWLGx1NBlDhJ8cWa6mJyU0JBWFUdhz7yik,11812
25
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/path/pattern.py,sha256=d3SO_bencbojbf3zkvaR6bxzT83VFrMpI-wEfc26Qk0,16107
26
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/dist_file/__init__.py,sha256=7SK7yeLehYJCbeHgnRFb4tPi2bEP2_ouA-qNxh_ofJI,379
27
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/dist_file/dist_targz.py,sha256=csb8NckhfScIUNY6LnQlUM31Jio69RwndBLuGD45Kls,3867
28
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/dist_file/dist_base.py,sha256=0LbWUitfuunpU0azUMD9xvapfn3_waUzZJVLwHxhD3E,11997
29
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/dist_file/dist_copy.py,sha256=MXUfoOXhH2eTetgkkz-5linJkqzX7_q4gIvCLLlB-7c,3519
30
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/dist_file/dist_binary.py,sha256=COfEivLDvHk2ycX9xTSnNnIsc5EyXRTznaJPWOrx4Os,7749
31
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/dist_file/dist_source.py,sha256=_TzLbzLSn6TIwO98zwdvllkGCJgvr6BKnjNM-URKiCw,4362
32
+ partis_pyproj-0.1.8.data/purelib/partis/pyproj/dist_file/dist_zip.py,sha256=ILAIz1myBgAR4xFRNgcJ1b2otcdUscyRViEbbArzL1M,3810
33
+ partis_pyproj-0.1.8.dist-info/METADATA,sha256=5VkYF6-weoj2QFLR2MPHR8Quv6F4m2DCSUsSgW9nTy0,22034
34
+ partis_pyproj-0.1.8.dist-info/LICENSE.txt,sha256=6LYtpan0H8F0reCrEa1ZUf_Nr9Vu93A3bqunX2J5Et0,3320
35
+ partis_pyproj-0.1.8.dist-info/top_level.txt,sha256=T72LF-bUK7nMDf1GQgbzvgZ2YrJElwwRd9h7HwVQBIQ,6
36
+ partis_pyproj-0.1.8.dist-info/entry_points.txt,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
+ partis_pyproj-0.1.8.dist-info/WHEEL,sha256=4YeLUbbRU8D0xzIlG3rm8IFLwHKcY8YaY_wiNxswKJg,126
38
+ partis_pyproj-0.1.8.dist-info/RECORD,,
@@ -1,37 +0,0 @@
1
- partis_pyproj-0.1.6.data/purelib/partis/pyproj/file.py,sha256=cU327vzXGfuMWlqY-LdfGqGs7LOhVd0MOUOIdAL5od4,1581
2
- partis_pyproj-0.1.6.data/purelib/partis/pyproj/__init__.py,sha256=-CibR9jSfwRxNn4gHe-7_ZrQnH3FY4JYTi3Mz-Q66cU,1560
3
- partis_pyproj-0.1.6.data/purelib/partis/pyproj/validate.py,sha256=ZYzaiHaWWkw9RLL-AgVfch-3gacNg5s1Vl0yy6gX4-U,32168
4
- partis_pyproj-0.1.6.data/purelib/partis/pyproj/_legacy_setup.py,sha256=tAE0xlD9RVhhMn1kcUPSccbDFE1ReuH99jfrw8q8MiM,5301
5
- partis_pyproj-0.1.6.data/purelib/partis/pyproj/norms.py,sha256=uyF3Pi9NbptleTabdZlMkaKJB5ymfDOSrcpD6bygQeM,10909
6
- partis_pyproj-0.1.6.data/purelib/partis/pyproj/builder/cargo.py,sha256=z9yj7QNZqCV_hFRBOsBBWo1GgOWZF0Oqag0kLpfN-2I,1144
7
- partis_pyproj-0.1.6.data/purelib/partis/pyproj/builder/__init__.py,sha256=1GSva5IvHTDozdjR9G7g30zKIBshciBYHIfVodjTeLI,133
8
- partis_pyproj-0.1.6.data/purelib/partis/pyproj/builder/meson.py,sha256=V39hN5bJ4kc9JBTXwT0XIc9uMtnKtrYczWC28jqu-GU,1939
9
- partis_pyproj-0.1.6.data/purelib/partis/pyproj/builder/process.py,sha256=8tUuFQrnks6Q-0G6Ow8G7LOycc2Z5olqLBzGhRCAulg,1002
10
- partis_pyproj-0.1.6.data/purelib/partis/pyproj/builder/cmake.py,sha256=0YzfQ6Oj09-9wf8tUVG5Wp7X76t5zdKPAAXMa8Cy6wg,1916
11
- partis_pyproj-0.1.6.data/purelib/partis/pyproj/builder/builder.py,sha256=XXCM3K3_tF2YUEzZrI_D89V_IQIVn0dyhhUhBztVkdI,10305
12
- partis_pyproj-0.1.6.data/purelib/partis/pyproj/pyproj.py,sha256=S8L9K_E32iWk1Lx-jNe7lYFZUg-2CsPCUfS7UG-BAEs,12731
13
- partis_pyproj-0.1.6.data/purelib/partis/pyproj/pptoml.py,sha256=Q-YC8dz11HM5NgYjgqUiNt_dSKqwoMQ1yXS8SFTVprg,13522
14
- partis_pyproj-0.1.6.data/purelib/partis/pyproj/legacy.py,sha256=JXSg-5Y65PUOtlEgkIgNj3vwZtJFi9CVsRvnpar2xKs,1749
15
- partis_pyproj-0.1.6.data/purelib/partis/pyproj/template.py,sha256=WZVlVU1gdrJtMtjw8MAHGRCnU4UC59qZr6uyeuFo5og,6486
16
- partis_pyproj-0.1.6.data/purelib/partis/pyproj/pkginfo.py,sha256=6rz06s19MX7zQIWt2wE-HEg-1WA2LZogk9kdqKFFcVE,15148
17
- partis_pyproj-0.1.6.data/purelib/partis/pyproj/_nonprintable.py,sha256=VOYmAa1jumOLlcCSC5daapsSp3swzC_nxXVTFsHFVcQ,1864
18
- partis_pyproj-0.1.6.data/purelib/partis/pyproj/pep.py,sha256=XzZYnYKaB4V3wOXUsrGNav9H_ziMoaQb9wGEKlciOms,32694
19
- partis_pyproj-0.1.6.data/purelib/partis/pyproj/load_module.py,sha256=-PNUeAzS0PQF96Sm8OgSX5fZtsPqmd208BNi3ksZUbc,4340
20
- partis_pyproj-0.1.6.data/purelib/partis/pyproj/backend.py,sha256=YzwxqWw9Ka5lDB1YaHmtpbdcMQ4PkXuXfltlgby16Gw,6867
21
- partis_pyproj-0.1.6.data/purelib/partis/pyproj/path/__init__.py,sha256=458RSB1Lr63sY3CP_uqxaHkEUQMgeqZgNXFzpUIbsn4,236
22
- partis_pyproj-0.1.6.data/purelib/partis/pyproj/path/utils.py,sha256=lJUyTGecZDMoTphSItr99_4b_yp_4zMRd8IAFiXgsI4,2364
23
- partis_pyproj-0.1.6.data/purelib/partis/pyproj/path/match.py,sha256=zyeGJtVQ-gWLGx1NBlDhJ8cWa6mJyU0JBWFUdhz7yik,11812
24
- partis_pyproj-0.1.6.data/purelib/partis/pyproj/path/pattern.py,sha256=d3SO_bencbojbf3zkvaR6bxzT83VFrMpI-wEfc26Qk0,16107
25
- partis_pyproj-0.1.6.data/purelib/partis/pyproj/dist_file/__init__.py,sha256=7SK7yeLehYJCbeHgnRFb4tPi2bEP2_ouA-qNxh_ofJI,379
26
- partis_pyproj-0.1.6.data/purelib/partis/pyproj/dist_file/dist_targz.py,sha256=csb8NckhfScIUNY6LnQlUM31Jio69RwndBLuGD45Kls,3867
27
- partis_pyproj-0.1.6.data/purelib/partis/pyproj/dist_file/dist_base.py,sha256=0LbWUitfuunpU0azUMD9xvapfn3_waUzZJVLwHxhD3E,11997
28
- partis_pyproj-0.1.6.data/purelib/partis/pyproj/dist_file/dist_copy.py,sha256=MXUfoOXhH2eTetgkkz-5linJkqzX7_q4gIvCLLlB-7c,3519
29
- partis_pyproj-0.1.6.data/purelib/partis/pyproj/dist_file/dist_binary.py,sha256=COfEivLDvHk2ycX9xTSnNnIsc5EyXRTznaJPWOrx4Os,7749
30
- partis_pyproj-0.1.6.data/purelib/partis/pyproj/dist_file/dist_source.py,sha256=_TzLbzLSn6TIwO98zwdvllkGCJgvr6BKnjNM-URKiCw,4362
31
- partis_pyproj-0.1.6.data/purelib/partis/pyproj/dist_file/dist_zip.py,sha256=ILAIz1myBgAR4xFRNgcJ1b2otcdUscyRViEbbArzL1M,3810
32
- partis_pyproj-0.1.6.dist-info/METADATA,sha256=Rzj6OvaC-5alhVkuLLOMhMN8MTvWEjPQl8ztzOX70HY,19944
33
- partis_pyproj-0.1.6.dist-info/LICENSE.txt,sha256=6LYtpan0H8F0reCrEa1ZUf_Nr9Vu93A3bqunX2J5Et0,3320
34
- partis_pyproj-0.1.6.dist-info/top_level.txt,sha256=T72LF-bUK7nMDf1GQgbzvgZ2YrJElwwRd9h7HwVQBIQ,6
35
- partis_pyproj-0.1.6.dist-info/entry_points.txt,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
- partis_pyproj-0.1.6.dist-info/WHEEL,sha256=4YeLUbbRU8D0xzIlG3rm8IFLwHKcY8YaY_wiNxswKJg,126
37
- partis_pyproj-0.1.6.dist-info/RECORD,,