opensysml 0.3.0__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.
- opensysml/__init__.py +355 -0
- opensysml/_dist.py +90 -0
- opensysml/_version.py +9 -0
- opensysml/binary.py +555 -0
- opensysml/capabilities.py +192 -0
- opensysml/connection.py +1710 -0
- opensysml/conversion.py +137 -0
- opensysml/diagnostic.py +80 -0
- opensysml/edit.py +448 -0
- opensysml/enumeration.py +30 -0
- opensysml/errors.py +515 -0
- opensysml/generate.py +678 -0
- opensysml/instance.py +196 -0
- opensysml/model.py +587 -0
- opensysml/proto/__init__.py +0 -0
- opensysml/proto/sysml_pb2.py +187 -0
- opensysml/proto/sysml_pb2.pyi +772 -0
- opensysml/proto/sysml_pb2_grpc.py +725 -0
- opensysml/py.typed +0 -0
- opensysml/query.py +238 -0
- opensysml/symbol.py +385 -0
- opensysml/typed.py +250 -0
- opensysml/typefacts.py +149 -0
- opensysml/values.py +450 -0
- opensysml/verdict.py +185 -0
- opensysml-0.3.0.dist-info/METADATA +240 -0
- opensysml-0.3.0.dist-info/RECORD +31 -0
- opensysml-0.3.0.dist-info/WHEEL +5 -0
- opensysml-0.3.0.dist-info/entry_points.txt +2 -0
- opensysml-0.3.0.dist-info/licenses/LICENSE +202 -0
- opensysml-0.3.0.dist-info/top_level.txt +1 -0
opensysml/__init__.py
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
"""opensysml - Python client library for OpenSysML SysML v2 parser."""
|
|
2
|
+
|
|
3
|
+
import warnings
|
|
4
|
+
|
|
5
|
+
from opensysml._version import VERSION as _declared_version
|
|
6
|
+
from opensysml.connection import Connection, DEFAULT_PORT, split_target
|
|
7
|
+
from opensysml.model import Model
|
|
8
|
+
from opensysml.symbol import Symbol
|
|
9
|
+
from opensysml.diagnostic import Diagnostic
|
|
10
|
+
from opensysml.enumeration import EnumLiteral
|
|
11
|
+
from opensysml.instance import Instance
|
|
12
|
+
from opensysml.typed import TypedObject
|
|
13
|
+
from opensysml.typefacts import (
|
|
14
|
+
AttributeFacts,
|
|
15
|
+
Multiplicity,
|
|
16
|
+
Specialization,
|
|
17
|
+
SymbolFacts,
|
|
18
|
+
TypeFacts,
|
|
19
|
+
)
|
|
20
|
+
from opensysml.capabilities import MissingCapabilityError, ServerInfo
|
|
21
|
+
from opensysml.values import UNSET, UnsetType
|
|
22
|
+
from opensysml.verdict import CalcResult, Verdict
|
|
23
|
+
from opensysml.query import QueryElement, QueryError
|
|
24
|
+
from opensysml.conversion import (
|
|
25
|
+
FORMAT_SYSML, FORMAT_TURTLE, Conversion, ExperimentalFeatureWarning,
|
|
26
|
+
format_of_path, is_experimental,
|
|
27
|
+
)
|
|
28
|
+
from opensysml.edit import AppliedEdit, EditResult, Editor
|
|
29
|
+
from opensysml.errors import (
|
|
30
|
+
OpenSysMLError, ChecksumMismatchError, ConnectionError, ConversionError,
|
|
31
|
+
EditError, EditResultError, EditTargetError, ExecutionError,
|
|
32
|
+
FeatureValueError, InvalidEditError, NoEditsError, OverlappingEditsError,
|
|
33
|
+
RenameReferencedError,
|
|
34
|
+
OwnerNotFoundError, OwnerNotNamespaceError, IllegalMemberKindError,
|
|
35
|
+
MemberNameTakenError, DeleteReferencedError,
|
|
36
|
+
InstanceTypeError, InvalidRequestError, ModelError,
|
|
37
|
+
ModelFileNotFoundError, ModelNotFoundError, ServiceError,
|
|
38
|
+
ServiceTimeoutError, StaleServiceError, SymbolNotFoundError,
|
|
39
|
+
TypeMismatchError, UnpinnedReleaseError, UnsupportedOperationError,
|
|
40
|
+
UnsupportedValueError, WrongKindError,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
__all__ = [
|
|
44
|
+
"Connection", "Model", "Symbol", "Diagnostic", "EnumLiteral", "Instance",
|
|
45
|
+
"TypedObject", "TypeFacts", "Multiplicity", "Specialization", "SymbolFacts",
|
|
46
|
+
"AttributeFacts",
|
|
47
|
+
"ServerInfo",
|
|
48
|
+
"UNSET", "UnsetType",
|
|
49
|
+
"Conversion", "FORMAT_SYSML", "FORMAT_TURTLE", "format_of_path",
|
|
50
|
+
"ExperimentalFeatureWarning", "is_experimental",
|
|
51
|
+
"Editor", "EditResult", "AppliedEdit",
|
|
52
|
+
"Verdict", "CalcResult",
|
|
53
|
+
"QueryElement", "QueryError",
|
|
54
|
+
"OpenSysMLError", "ChecksumMismatchError", "ConnectionError",
|
|
55
|
+
"ConversionError", "ExecutionError", "FeatureValueError",
|
|
56
|
+
"EditError", "NoEditsError", "EditTargetError", "InvalidEditError",
|
|
57
|
+
"RenameReferencedError", "OverlappingEditsError", "EditResultError",
|
|
58
|
+
"OwnerNotFoundError", "OwnerNotNamespaceError", "IllegalMemberKindError",
|
|
59
|
+
"MemberNameTakenError", "DeleteReferencedError",
|
|
60
|
+
"InstanceTypeError", "InvalidRequestError", "MissingCapabilityError",
|
|
61
|
+
"ModelError", "ModelFileNotFoundError", "ModelNotFoundError",
|
|
62
|
+
"ServiceError", "ServiceTimeoutError", "StaleServiceError",
|
|
63
|
+
"SymbolNotFoundError",
|
|
64
|
+
"TypeMismatchError", "UnpinnedReleaseError",
|
|
65
|
+
"UnsupportedOperationError", "UnsupportedValueError",
|
|
66
|
+
"WrongKindError",
|
|
67
|
+
"load", "loads", "connect", "convert",
|
|
68
|
+
# "eval" is deprecated in favour of "evaluate", so it is not exported.
|
|
69
|
+
"evaluate", "instantiate",
|
|
70
|
+
"DEFAULT_PORT", "split_target",
|
|
71
|
+
"__version__"
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
# The declaration ships beside this module, so this is the version of the code
|
|
75
|
+
# being imported: right for an editable install, whose dist-info metadata is
|
|
76
|
+
# frozen at install time, and the same value a wheel's metadata is built from.
|
|
77
|
+
__version__ = _declared_version
|
|
78
|
+
|
|
79
|
+
# Module-level default connection (lazy singleton)
|
|
80
|
+
_default_connection = None
|
|
81
|
+
_default_connection_params = None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _get_default_connection(host='localhost', port=None):
|
|
85
|
+
"""Get or create the default module-level connection.
|
|
86
|
+
|
|
87
|
+
If called with different host/port than last time, creates new connection.
|
|
88
|
+
A ``host:port`` address written as the host names the same service as the
|
|
89
|
+
two given separately, so it reuses the same connection.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
host (str): Service hostname, or a ``host:port`` address
|
|
93
|
+
port (int, optional): Service port (default: 50051)
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Connection: Singleton default connection
|
|
97
|
+
|
|
98
|
+
Raises:
|
|
99
|
+
ValueError: If host names a port that is unreadable or disagrees with port
|
|
100
|
+
"""
|
|
101
|
+
global _default_connection, _default_connection_params
|
|
102
|
+
host, port = split_target(host, port)
|
|
103
|
+
params = (host, port)
|
|
104
|
+
|
|
105
|
+
# Create new connection if params changed or no connection exists
|
|
106
|
+
if _default_connection is None or _default_connection_params != params:
|
|
107
|
+
# Close old connection to avoid refcount leak
|
|
108
|
+
if _default_connection is not None:
|
|
109
|
+
_default_connection.close()
|
|
110
|
+
_default_connection = Connection(host, port, auto_start=True)
|
|
111
|
+
_default_connection_params = params
|
|
112
|
+
|
|
113
|
+
return _default_connection
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def loads(content, host='localhost', port=None, language=None, strict=False,
|
|
117
|
+
strict_conformance=False):
|
|
118
|
+
"""Parse inline SysML or KerML content using the default connection."""
|
|
119
|
+
connection = (
|
|
120
|
+
_get_default_connection()
|
|
121
|
+
if host == 'localhost' and port is None
|
|
122
|
+
else _get_default_connection(host, port)
|
|
123
|
+
)
|
|
124
|
+
return connection.load_from_content(
|
|
125
|
+
content, strict=strict, language=language,
|
|
126
|
+
strict_conformance=strict_conformance
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def load(file_path, host='localhost', port=None, strict=False,
|
|
131
|
+
strict_conformance=False):
|
|
132
|
+
"""Load a SysML model from file using the default connection.
|
|
133
|
+
|
|
134
|
+
Convenience function that uses a module-level singleton connection.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
file_path (str): Path to .sysml file
|
|
138
|
+
host (str): Service hostname, or a ``host:port`` address
|
|
139
|
+
port (int, optional): Service port (default: 50051)
|
|
140
|
+
strict (bool): Refuse a model the service reported errors for, rather
|
|
141
|
+
than returning one whose lookups fail later
|
|
142
|
+
strict_conformance (bool): Ask whether the file is conforming SysML v2:
|
|
143
|
+
notation only OpenSysML accepts is an error, not a warning
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Model: Parsed model object
|
|
147
|
+
|
|
148
|
+
Raises:
|
|
149
|
+
ModelFileNotFoundError: If the service cannot read file_path
|
|
150
|
+
ModelError: If strict and the model has error diagnostics
|
|
151
|
+
ConnectionError: If the service is unreachable
|
|
152
|
+
ValueError: If host names a port that is unreadable or disagrees with port
|
|
153
|
+
"""
|
|
154
|
+
conn = _get_default_connection(host, port)
|
|
155
|
+
return conn.load(file_path, strict=strict,
|
|
156
|
+
strict_conformance=strict_conformance)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def connect(host='localhost', port=None, auto_start=True, version=None,
|
|
160
|
+
require_capabilities=None):
|
|
161
|
+
"""Create a new connection to sysml-grpc service.
|
|
162
|
+
|
|
163
|
+
Convenience function that creates a new Connection instance.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
host (str): Service hostname, or a ``host:port`` address, whose port is
|
|
167
|
+
used when no separate port is given (default: 'localhost')
|
|
168
|
+
port (int, optional): Service port (default: 50051)
|
|
169
|
+
auto_start (bool): If True, automatically start service if not running (default: True)
|
|
170
|
+
version (str, optional): Release tag the service must report, or
|
|
171
|
+
'latest'; defaults to $OPENSYSML_GRPC_VERSION. Checked whether the
|
|
172
|
+
service is started here or managed by the caller
|
|
173
|
+
require_capabilities (iterable, optional): Capability names the service
|
|
174
|
+
must report, checked at connect time
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
Connection: New connection instance
|
|
178
|
+
|
|
179
|
+
Raises:
|
|
180
|
+
ValueError: If host names a port that is unreadable or disagrees with port
|
|
181
|
+
StaleServiceError: If another release is already listening on the
|
|
182
|
+
address and this client may not stop it
|
|
183
|
+
MissingCapabilityError: If the service lacks a required capability
|
|
184
|
+
|
|
185
|
+
Example:
|
|
186
|
+
>>> conn = opensysml.connect("localhost:50123")
|
|
187
|
+
>>> conn.port
|
|
188
|
+
50123
|
|
189
|
+
"""
|
|
190
|
+
host, port = split_target(host, port)
|
|
191
|
+
return Connection(
|
|
192
|
+
host, port, auto_start=auto_start, version=version,
|
|
193
|
+
require_capabilities=require_capabilities,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def convert(to_format, file_path=None, content=None, model_hash=None,
|
|
198
|
+
from_format='', tolerate_syntax_errors=False, host='localhost',
|
|
199
|
+
port=None):
|
|
200
|
+
"""Write a model out in another format (module-level convenience).
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
to_format (str): 'sysml', 'kerml', 'text', 'ttl', 'turtle' or 'rdf'
|
|
204
|
+
file_path (str, optional): Path the service reads the source from
|
|
205
|
+
content (str, optional): Source carried inline
|
|
206
|
+
model_hash (str, optional): Hash of a loaded model, whose parsed source
|
|
207
|
+
is converted
|
|
208
|
+
from_format (str, optional): Format to read the source as; inferred from
|
|
209
|
+
file_path's extension when omitted, notation for a model_hash, and
|
|
210
|
+
required for inline content
|
|
211
|
+
tolerate_syntax_errors (bool): Write notation back out even when the
|
|
212
|
+
parser could not read all of it
|
|
213
|
+
host (str): Service hostname, or a ``host:port`` address
|
|
214
|
+
port (int, optional): Service port (default: 50051)
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Conversion: The converted model; ``str()`` of it is the text
|
|
218
|
+
|
|
219
|
+
Warns:
|
|
220
|
+
ExperimentalFeatureWarning: If either format is RDF, whose mapping is
|
|
221
|
+
experimental — see ``docs/reference/rdf-mapping.md``
|
|
222
|
+
|
|
223
|
+
Example:
|
|
224
|
+
>>> import opensysml
|
|
225
|
+
>>> turtle = opensysml.convert("ttl", file_path="model.sysml")
|
|
226
|
+
>>> turtle.write("model.ttl")
|
|
227
|
+
'model.ttl'
|
|
228
|
+
"""
|
|
229
|
+
conn = _get_default_connection(host, port)
|
|
230
|
+
return conn.convert(
|
|
231
|
+
to_format,
|
|
232
|
+
file_path=file_path,
|
|
233
|
+
content=content,
|
|
234
|
+
model_hash=model_hash,
|
|
235
|
+
from_format=from_format,
|
|
236
|
+
tolerate_syntax_errors=tolerate_syntax_errors,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def evaluate(expression, file_path=None, model_hash=None, context_symbol_id=None,
|
|
241
|
+
host='localhost', port=None, subject=None):
|
|
242
|
+
"""Evaluate a SysML expression (module-level convenience).
|
|
243
|
+
|
|
244
|
+
A model in hand has :meth:`Model.eval`, which needs neither the hash nor the
|
|
245
|
+
connection; this form is for an expression evaluated against a file.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
expression (str): SysML expression
|
|
249
|
+
file_path (str, optional): Parse this file first, get model_hash
|
|
250
|
+
model_hash (str, optional): Use existing model hash
|
|
251
|
+
context_symbol_id (str, optional): Context for evaluation
|
|
252
|
+
host (str): Service hostname, or a ``host:port`` address
|
|
253
|
+
port (int, optional): Service port (default: 50051)
|
|
254
|
+
subject (str, optional): FQN of a part/usage to instantiate and evaluate
|
|
255
|
+
against, so a feature reads that object's value rather than the
|
|
256
|
+
declared default. Last, so a positional call written before it
|
|
257
|
+
still binds the address it meant
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
Evaluated value
|
|
261
|
+
|
|
262
|
+
Raises:
|
|
263
|
+
ValueError: If neither file_path nor model_hash provided, if both are,
|
|
264
|
+
or if host names a port that is unreadable or disagrees with port
|
|
265
|
+
ExecutionError: If evaluation fails
|
|
266
|
+
|
|
267
|
+
Example:
|
|
268
|
+
>>> import opensysml
|
|
269
|
+
>>> result = opensysml.evaluate("2 + 2", file_path="test.sysml")
|
|
270
|
+
>>> print(result) # 4
|
|
271
|
+
"""
|
|
272
|
+
conn = _get_default_connection(host, port)
|
|
273
|
+
|
|
274
|
+
# Validate params: exactly one of file_path or model_hash required
|
|
275
|
+
if file_path and model_hash:
|
|
276
|
+
raise ValueError("Provide either file_path or model_hash, not both")
|
|
277
|
+
|
|
278
|
+
if not file_path and not model_hash:
|
|
279
|
+
raise ValueError("Must provide either file_path or model_hash")
|
|
280
|
+
|
|
281
|
+
if file_path:
|
|
282
|
+
model = conn.load(file_path)
|
|
283
|
+
model_hash = model.hash
|
|
284
|
+
|
|
285
|
+
return conn.eval(
|
|
286
|
+
expression,
|
|
287
|
+
model_hash,
|
|
288
|
+
context_symbol_id=context_symbol_id,
|
|
289
|
+
subject_symbol_id=subject,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def instantiate(symbol_id, file_path=None, model_hash=None, host='localhost',
|
|
294
|
+
port=None):
|
|
295
|
+
"""Instantiate a part/usage (module-level convenience).
|
|
296
|
+
|
|
297
|
+
A model in hand has :meth:`Model.instantiate`, which needs neither the hash
|
|
298
|
+
nor the connection; this form is for instantiating out of a file.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
symbol_id (str): FQN of symbol to instantiate
|
|
302
|
+
file_path (str, optional): Parse this file first
|
|
303
|
+
model_hash (str, optional): Use existing model hash
|
|
304
|
+
host (str): Service hostname, or a ``host:port`` address
|
|
305
|
+
port (int, optional): Service port (default: 50051)
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
Instance: Instance object
|
|
309
|
+
|
|
310
|
+
Raises:
|
|
311
|
+
ValueError: If neither file_path nor model_hash provided, if both are,
|
|
312
|
+
or if host names a port that is unreadable or disagrees with port
|
|
313
|
+
ExecutionError: If instantiation fails
|
|
314
|
+
|
|
315
|
+
Example:
|
|
316
|
+
>>> import opensysml
|
|
317
|
+
>>> instance = opensysml.instantiate("SPACECRAFT_WET", file_path="A1.sysml")
|
|
318
|
+
>>> print(instance.id)
|
|
319
|
+
"""
|
|
320
|
+
conn = _get_default_connection(host, port)
|
|
321
|
+
|
|
322
|
+
# Validate params: exactly one of file_path or model_hash required
|
|
323
|
+
if file_path and model_hash:
|
|
324
|
+
raise ValueError("Provide either file_path or model_hash, not both")
|
|
325
|
+
|
|
326
|
+
if not file_path and not model_hash:
|
|
327
|
+
raise ValueError("Must provide either file_path or model_hash")
|
|
328
|
+
|
|
329
|
+
if file_path:
|
|
330
|
+
model = conn.load(file_path)
|
|
331
|
+
model_hash = model.hash
|
|
332
|
+
|
|
333
|
+
return conn.instantiate(symbol_id, model_hash)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
#: Names that shadowed a built-in, and what each is called now. Served through
|
|
337
|
+
#: __getattr__ so a star-import no longer binds over the built-in.
|
|
338
|
+
_RENAMED_NAMES = {"eval": "evaluate", "RuntimeError": "ExecutionError"}
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def __getattr__(name):
|
|
342
|
+
"""Serve a renamed name with the object it became, warning about its use.
|
|
343
|
+
|
|
344
|
+
``opensysml.eval`` is :func:`evaluate` and ``opensysml.RuntimeError`` is
|
|
345
|
+
:class:`~opensysml.errors.ExecutionError`, so existing snippets keep working.
|
|
346
|
+
"""
|
|
347
|
+
replacement = _RENAMED_NAMES.get(name)
|
|
348
|
+
if replacement is None:
|
|
349
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
350
|
+
warnings.warn(
|
|
351
|
+
f"opensysml.{name} is deprecated; use opensysml.{replacement} instead",
|
|
352
|
+
DeprecationWarning,
|
|
353
|
+
stacklevel=2,
|
|
354
|
+
)
|
|
355
|
+
return globals()[replacement]
|
opensysml/_dist.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Where the installed opensysml distribution lives, and how it was installed.
|
|
2
|
+
|
|
3
|
+
An editable install keeps its dist-info in site-packages while its modules stay
|
|
4
|
+
in the checkout, so `Distribution.locate_file` answers with a site-packages path
|
|
5
|
+
that holds no `opensysml/` at all. Resolution therefore reads the PEP 610 record
|
|
6
|
+
the installer wrote, which names the directory the install was made from.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
from importlib.metadata import PackageNotFoundError, distribution
|
|
12
|
+
from urllib.parse import urlparse
|
|
13
|
+
from urllib.request import url2pathname
|
|
14
|
+
|
|
15
|
+
#: Layouts a project directory may hold its packages in.
|
|
16
|
+
_PACKAGE_ROOTS = ('', 'src')
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def installed_distribution():
|
|
20
|
+
"""The installed opensysml distribution.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
importlib.metadata.Distribution or None: None when the source tree is
|
|
24
|
+
not installed at all
|
|
25
|
+
"""
|
|
26
|
+
try:
|
|
27
|
+
return distribution('opensysml')
|
|
28
|
+
except PackageNotFoundError:
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def editable_install(dist):
|
|
33
|
+
"""Whether a distribution's modules are a checkout rather than copies of one.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
dist (importlib.metadata.Distribution): The installed distribution
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
bool: True for a PEP 660 editable install
|
|
40
|
+
"""
|
|
41
|
+
return _direct_url_info(dist).get('dir_info', {}).get('editable') is True
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def project_directory(dist):
|
|
45
|
+
"""The directory an editable install was made from.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
dist (importlib.metadata.Distribution): The installed distribution
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
str or None: The directory, or None when the install records no local
|
|
52
|
+
one (a wheel from an index records no directory)
|
|
53
|
+
"""
|
|
54
|
+
url = _direct_url_info(dist).get('url', '')
|
|
55
|
+
if not url.startswith('file:'):
|
|
56
|
+
return None
|
|
57
|
+
return os.path.realpath(url2pathname(urlparse(url).path))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def package_location(dist):
|
|
61
|
+
"""Path of the ``opensysml/__init__.py`` a distribution installed.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
dist (importlib.metadata.Distribution): The installed distribution
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
str: The path, resolved through the install's own record for an editable
|
|
68
|
+
install and through the dist-info's directory otherwise. The path is
|
|
69
|
+
not guaranteed to exist: a distribution whose files are gone is
|
|
70
|
+
reported as it stands rather than guessed at.
|
|
71
|
+
"""
|
|
72
|
+
project = project_directory(dist) if editable_install(dist) else None
|
|
73
|
+
if project is not None:
|
|
74
|
+
for root in _PACKAGE_ROOTS:
|
|
75
|
+
candidate = os.path.join(project, root, 'opensysml', '__init__.py')
|
|
76
|
+
if os.path.isfile(candidate):
|
|
77
|
+
return os.path.realpath(candidate)
|
|
78
|
+
return os.path.realpath(str(dist.locate_file('opensysml/__init__.py')))
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _direct_url_info(dist):
|
|
82
|
+
"""The PEP 610 record of how a distribution was installed, as a dict."""
|
|
83
|
+
recorded = dist.read_text('direct_url.json')
|
|
84
|
+
if not recorded:
|
|
85
|
+
return {}
|
|
86
|
+
try:
|
|
87
|
+
info = json.loads(recorded)
|
|
88
|
+
except json.JSONDecodeError:
|
|
89
|
+
return {}
|
|
90
|
+
return info if isinstance(info, dict) else {}
|
opensysml/_version.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""The one place the opensysml version is written.
|
|
2
|
+
|
|
3
|
+
`pyproject.toml` reads `VERSION` from here as the distribution's version, and
|
|
4
|
+
`opensysml.__version__` reports the version of the *installed* distribution, so
|
|
5
|
+
neither is a second copy that can drift. `scripts/check_version.py` compares
|
|
6
|
+
this value with the release tag before anything is uploaded.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
VERSION = "0.3.0"
|