morphir-python 0.0.1__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.
- morphir/__init__.py +14 -0
- morphir/ir/__init__.py +110 -0
- morphir/ir/access_controlled.py +15 -0
- morphir/ir/decorations.py +19 -0
- morphir/ir/diagnostics.py +105 -0
- morphir/ir/distribution.py +21 -0
- morphir/ir/document.py +37 -0
- morphir/ir/documented.py +11 -0
- morphir/ir/format_version.py +483 -0
- morphir/ir/fqname.py +9 -0
- morphir/ir/json.py +11 -0
- morphir/ir/literal.py +25 -0
- morphir/ir/meta.py +11 -0
- morphir/ir/migrate.py +1267 -0
- morphir/ir/module.py +17 -0
- morphir/ir/name.py +27 -0
- morphir/ir/package.py +15 -0
- morphir/ir/path.py +25 -0
- morphir/ir/qname.py +9 -0
- morphir/ir/ref.py +17 -0
- morphir/ir/type.py +37 -0
- morphir/ir/type_constraints.py +33 -0
- morphir/ir/type_def.py +19 -0
- morphir/ir/type_spec.py +25 -0
- morphir/ir/v3/__init__.py +64 -0
- morphir/ir/v3/_legacy_value.py +201 -0
- morphir/ir/v3/access_controlled.py +31 -0
- morphir/ir/v3/codec.py +1275 -0
- morphir/ir/v3/codec_v1.py +491 -0
- morphir/ir/v3/codec_v2.py +187 -0
- morphir/ir/v3/distribution.py +42 -0
- morphir/ir/v3/documented.py +13 -0
- morphir/ir/v3/fqname.py +30 -0
- morphir/ir/v3/literal.py +46 -0
- morphir/ir/v3/module.py +32 -0
- morphir/ir/v3/name.py +42 -0
- morphir/ir/v3/package.py +23 -0
- morphir/ir/v3/path.py +29 -0
- morphir/ir/v3/qname.py +16 -0
- morphir/ir/v3/type.py +126 -0
- morphir/ir/v3/value.py +235 -0
- morphir/ir/v4/__init__.py +160 -0
- morphir/ir/v4/access_controlled.py +17 -0
- morphir/ir/v4/annotation.py +75 -0
- morphir/ir/v4/codec.py +2161 -0
- morphir/ir/v4/decorations.py +88 -0
- morphir/ir/v4/distribution.py +56 -0
- morphir/ir/v4/document.py +69 -0
- morphir/ir/v4/documented.py +10 -0
- morphir/ir/v4/fqname.py +69 -0
- morphir/ir/v4/incompleteness.py +78 -0
- morphir/ir/v4/json.py +103 -0
- morphir/ir/v4/literal.py +51 -0
- morphir/ir/v4/meta.py +27 -0
- morphir/ir/v4/module.py +30 -0
- morphir/ir/v4/name.py +69 -0
- morphir/ir/v4/package.py +19 -0
- morphir/ir/v4/path.py +50 -0
- morphir/ir/v4/qname.py +34 -0
- morphir/ir/v4/ref.py +35 -0
- morphir/ir/v4/type.py +140 -0
- morphir/ir/v4/type_constraints.py +91 -0
- morphir/ir/v4/type_def.py +38 -0
- morphir/ir/v4/type_spec.py +64 -0
- morphir/ir/v4/value.py +480 -0
- morphir/ir/value.py +81 -0
- morphir/py.typed +0 -0
- morphir/sdk/__init__.py +110 -0
- morphir/sdk/_compare.py +187 -0
- morphir/sdk/aggregate.py +421 -0
- morphir/sdk/basics.py +504 -0
- morphir/sdk/char.py +110 -0
- morphir/sdk/decimal.py +311 -0
- morphir/sdk/dict.py +336 -0
- morphir/sdk/instant.py +118 -0
- morphir/sdk/int.py +86 -0
- morphir/sdk/key.py +522 -0
- morphir/sdk/list.py +356 -0
- morphir/sdk/local_date.py +436 -0
- morphir/sdk/local_time.py +267 -0
- morphir/sdk/maybe.py +135 -0
- morphir/sdk/number.py +237 -0
- morphir/sdk/regex.py +287 -0
- morphir/sdk/result.py +162 -0
- morphir/sdk/result_list.py +169 -0
- morphir/sdk/rule.py +103 -0
- morphir/sdk/set.py +214 -0
- morphir/sdk/stateful_app.py +49 -0
- morphir/sdk/string.py +427 -0
- morphir/sdk/tuple.py +43 -0
- morphir/sdk/uuid.py +209 -0
- morphir_python-0.0.1.dist-info/METADATA +269 -0
- morphir_python-0.0.1.dist-info/RECORD +99 -0
- morphir_python-0.0.1.dist-info/WHEEL +4 -0
- morphir_python-0.0.1.dist-info/entry_points.txt +2 -0
- morphir_python-0.0.1.dist-info/licenses/LICENSE +201 -0
- morphir_tools/__init__.py +13 -0
- morphir_tools/cli.py +223 -0
- morphir_tools/py.typed +0 -0
morphir/sdk/instant.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Morphir.SDK.Instant: a point on the timeline.
|
|
2
|
+
|
|
3
|
+
An `Instant` holds a whole number of milliseconds since 1970-01-01T00:00:00Z.
|
|
4
|
+
This is the form of the Elm runtime, where an `Instant` is a `Time.Posix`, and of
|
|
5
|
+
`morphir.sdk.local_time.LocalTime`. The number has no limit.
|
|
6
|
+
|
|
7
|
+
The type is its own class, not an alias of `datetime.datetime`. A `datetime` can
|
|
8
|
+
be naive (it can have no time zone), and a naive `datetime` does not identify a
|
|
9
|
+
point on the timeline. With a class that holds only the number, a value that is
|
|
10
|
+
not a point on the timeline cannot be made.
|
|
11
|
+
|
|
12
|
+
The IR specification has the type and no functions. All the functions here are
|
|
13
|
+
additions to the specification:
|
|
14
|
+
|
|
15
|
+
>>> from morphir.sdk import instant as Instant
|
|
16
|
+
>>> moment = Instant.from_milliseconds_since_epoch(1643374590000)
|
|
17
|
+
>>> Instant.to_datetime(moment).isoformat()
|
|
18
|
+
'2022-01-28T12:56:30+00:00'
|
|
19
|
+
>>> Instant.to_milliseconds_since_epoch(
|
|
20
|
+
... Instant.from_datetime(
|
|
21
|
+
... datetime.datetime(2022, 1, 28, 12, 56, 30, tzinfo=datetime.UTC)
|
|
22
|
+
... )
|
|
23
|
+
... )
|
|
24
|
+
1643374590000
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import datetime
|
|
28
|
+
from dataclasses import dataclass
|
|
29
|
+
|
|
30
|
+
from morphir.sdk._compare import Order
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"Instant",
|
|
34
|
+
"compare",
|
|
35
|
+
"from_datetime",
|
|
36
|
+
"from_milliseconds_since_epoch",
|
|
37
|
+
"to_datetime",
|
|
38
|
+
"to_milliseconds_since_epoch",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
_EPOCH = datetime.datetime(1970, 1, 1, tzinfo=datetime.UTC)
|
|
42
|
+
_MILLISECOND = datetime.timedelta(milliseconds=1)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass(frozen=True, slots=True, order=True)
|
|
46
|
+
class Instant:
|
|
47
|
+
"""A point on the timeline, as milliseconds since 1970-01-01T00:00:00Z.
|
|
48
|
+
|
|
49
|
+
Raises:
|
|
50
|
+
TypeError: If `milliseconds` is not an `int`. A `bool`, a `float` and a
|
|
51
|
+
`datetime` are rejected.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
milliseconds: int
|
|
55
|
+
|
|
56
|
+
def __post_init__(self) -> None:
|
|
57
|
+
value: object = self.milliseconds
|
|
58
|
+
if not isinstance(value, int) or isinstance(value, bool):
|
|
59
|
+
raise TypeError(
|
|
60
|
+
f"Instant: milliseconds must be an int, not {type(value).__name__}; "
|
|
61
|
+
f"use from_datetime to convert a datetime"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def from_milliseconds_since_epoch(millis: int) -> Instant:
|
|
66
|
+
"""Make an instant from a number of milliseconds since the epoch.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
millis: The number of milliseconds since 1970-01-01T00:00:00Z. It can be
|
|
70
|
+
negative.
|
|
71
|
+
|
|
72
|
+
Raises:
|
|
73
|
+
TypeError: If `millis` is not an `int`.
|
|
74
|
+
"""
|
|
75
|
+
return Instant(millis)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def to_milliseconds_since_epoch(instant: Instant) -> int:
|
|
79
|
+
"""Return the number of milliseconds from the epoch to an instant."""
|
|
80
|
+
return instant.milliseconds
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def compare(a: Instant, b: Instant) -> Order:
|
|
84
|
+
"""Compare two instants. The earlier instant is less."""
|
|
85
|
+
if a.milliseconds < b.milliseconds:
|
|
86
|
+
return Order.LT
|
|
87
|
+
if a.milliseconds > b.milliseconds:
|
|
88
|
+
return Order.GT
|
|
89
|
+
return Order.EQ
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def from_datetime(moment: datetime.datetime) -> Instant:
|
|
93
|
+
"""Make an instant from a `datetime` that has a time zone.
|
|
94
|
+
|
|
95
|
+
A part below one millisecond is rounded down.
|
|
96
|
+
|
|
97
|
+
Raises:
|
|
98
|
+
ValueError: If the `datetime` has no time zone. A naive `datetime` does
|
|
99
|
+
not identify a point on the timeline.
|
|
100
|
+
"""
|
|
101
|
+
if moment.utcoffset() is None:
|
|
102
|
+
raise ValueError("from_datetime: the datetime has no time zone")
|
|
103
|
+
return Instant((moment - _EPOCH) // _MILLISECOND)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def to_datetime(instant: Instant) -> datetime.datetime:
|
|
107
|
+
"""Return the instant as a `datetime` in UTC.
|
|
108
|
+
|
|
109
|
+
Raises:
|
|
110
|
+
OverflowError: If the instant is not in the years 1 to 9999, which is
|
|
111
|
+
the range of a `datetime`.
|
|
112
|
+
"""
|
|
113
|
+
try:
|
|
114
|
+
return _EPOCH + datetime.timedelta(milliseconds=instant.milliseconds)
|
|
115
|
+
except OverflowError:
|
|
116
|
+
raise
|
|
117
|
+
except (ValueError, OSError) as error:
|
|
118
|
+
raise OverflowError(str(error)) from error
|
morphir/sdk/int.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Morphir.SDK.Int: fixed-width integers.
|
|
2
|
+
|
|
3
|
+
`Int8`, `Int16`, `Int32` and `Int64` are `NewType` wrappers over `int`. They
|
|
4
|
+
have no run-time cost and a type checker keeps them apart. `to_int8` and the
|
|
5
|
+
other `to_` functions check the range; `from_int8` and the other `from_`
|
|
6
|
+
functions give the plain `int` back.
|
|
7
|
+
|
|
8
|
+
Python integers have no size limit, so `Int64` covers the full signed 64-bit
|
|
9
|
+
range.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import NewType
|
|
13
|
+
|
|
14
|
+
from morphir.sdk.maybe import Just, Maybe, Nothing
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"Int8",
|
|
18
|
+
"Int16",
|
|
19
|
+
"Int32",
|
|
20
|
+
"Int64",
|
|
21
|
+
"from_int8",
|
|
22
|
+
"from_int16",
|
|
23
|
+
"from_int32",
|
|
24
|
+
"from_int64",
|
|
25
|
+
"to_int8",
|
|
26
|
+
"to_int16",
|
|
27
|
+
"to_int32",
|
|
28
|
+
"to_int64",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
Int8 = NewType("Int8", int)
|
|
32
|
+
"""A signed 8-bit integer."""
|
|
33
|
+
|
|
34
|
+
Int16 = NewType("Int16", int)
|
|
35
|
+
"""A signed 16-bit integer."""
|
|
36
|
+
|
|
37
|
+
Int32 = NewType("Int32", int)
|
|
38
|
+
"""A signed 32-bit integer."""
|
|
39
|
+
|
|
40
|
+
Int64 = NewType("Int64", int)
|
|
41
|
+
"""A signed 64-bit integer."""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _fits(bits: int, n: int) -> bool:
|
|
45
|
+
limit = 1 << (bits - 1)
|
|
46
|
+
return isinstance(n, int) and not isinstance(n, bool) and -limit <= n < limit
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def from_int8(n: Int8) -> int:
|
|
50
|
+
"""Convert an `Int8` to an `int`."""
|
|
51
|
+
return int(n)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def to_int8(n: int) -> Maybe[Int8]:
|
|
55
|
+
"""Convert an `int` to an `Int8`, or `Nothing` when it is out of range."""
|
|
56
|
+
return Just(Int8(n)) if _fits(8, n) else Nothing()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def from_int16(n: Int16) -> int:
|
|
60
|
+
"""Convert an `Int16` to an `int`."""
|
|
61
|
+
return int(n)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def to_int16(n: int) -> Maybe[Int16]:
|
|
65
|
+
"""Convert an `int` to an `Int16`, or `Nothing` when it is out of range."""
|
|
66
|
+
return Just(Int16(n)) if _fits(16, n) else Nothing()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def from_int32(n: Int32) -> int:
|
|
70
|
+
"""Convert an `Int32` to an `int`."""
|
|
71
|
+
return int(n)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def to_int32(n: int) -> Maybe[Int32]:
|
|
75
|
+
"""Convert an `int` to an `Int32`, or `Nothing` when it is out of range."""
|
|
76
|
+
return Just(Int32(n)) if _fits(32, n) else Nothing()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def from_int64(n: Int64) -> int:
|
|
80
|
+
"""Convert an `Int64` to an `int`."""
|
|
81
|
+
return int(n)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def to_int64(n: int) -> Maybe[Int64]:
|
|
85
|
+
"""Convert an `int` to an `Int64`, or `Nothing` when it is out of range."""
|
|
86
|
+
return Just(Int64(n)) if _fits(64, n) else Nothing()
|