python-ulid 2.1.0__tar.gz → 2.3.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.
- {python_ulid-2.1.0 → python_ulid-2.3.0}/.github/workflows/lint-and-test.yml +19 -6
- {python_ulid-2.1.0 → python_ulid-2.3.0}/.github/workflows/publish.yml +1 -1
- {python_ulid-2.1.0 → python_ulid-2.3.0}/.gitignore +1 -1
- python_ulid-2.3.0/.pre-commit-config.yaml +10 -0
- {python_ulid-2.1.0 → python_ulid-2.3.0}/.readthedocs.yml +3 -3
- {python_ulid-2.1.0 → python_ulid-2.3.0}/CHANGELOG.rst +32 -0
- {python_ulid-2.1.0 → python_ulid-2.3.0}/PKG-INFO +71 -8
- {python_ulid-2.1.0 → python_ulid-2.3.0}/README.rst +66 -6
- {python_ulid-2.1.0 → python_ulid-2.3.0}/docs/Makefile +2 -2
- python_ulid-2.3.0/docs/requirements.txt +2 -0
- python_ulid-2.3.0/docs/source/changelog.rst +1 -0
- {python_ulid-2.1.0/docs → python_ulid-2.3.0/docs/source}/conf.py +5 -11
- {python_ulid-2.1.0/docs → python_ulid-2.3.0/docs/source}/index.rst +8 -4
- {python_ulid-2.1.0 → python_ulid-2.3.0}/hatch.toml +13 -9
- {python_ulid-2.1.0 → python_ulid-2.3.0}/pyproject.toml +11 -10
- python_ulid-2.3.0/tests/test_cli.py +77 -0
- {python_ulid-2.1.0 → python_ulid-2.3.0}/tests/test_ulid.py +23 -1
- {python_ulid-2.1.0 → python_ulid-2.3.0}/ulid/__init__.py +55 -9
- {python_ulid-2.1.0 → python_ulid-2.3.0}/ulid/__main__.py +47 -42
- {python_ulid-2.1.0 → python_ulid-2.3.0}/ulid/constants.py +1 -0
- python_ulid-2.1.0/.pre-commit-config.yaml +0 -18
- python_ulid-2.1.0/docs/changelog.rst +0 -1
- python_ulid-2.1.0/docs/requirements.txt +0 -1
- {python_ulid-2.1.0 → python_ulid-2.3.0}/LICENSE +0 -0
- {python_ulid-2.1.0/docs → python_ulid-2.3.0/docs/source}/api.rst +0 -0
- {python_ulid-2.1.0 → python_ulid-2.3.0}/logo.png +0 -0
- {python_ulid-2.1.0 → python_ulid-2.3.0}/tests/__init__.py +0 -0
- {python_ulid-2.1.0 → python_ulid-2.3.0}/tests/test_base32.py +0 -0
- {python_ulid-2.1.0 → python_ulid-2.3.0}/ulid/base32.py +0 -0
- {python_ulid-2.1.0 → python_ulid-2.3.0}/ulid/py.typed +0 -0
|
@@ -9,12 +9,13 @@ on:
|
|
|
9
9
|
jobs:
|
|
10
10
|
lint-code:
|
|
11
11
|
runs-on: ubuntu-latest
|
|
12
|
+
name: Lint code
|
|
12
13
|
steps:
|
|
13
14
|
- uses: actions/checkout@v4
|
|
14
15
|
- name: Set up Python
|
|
15
|
-
uses: actions/setup-python@
|
|
16
|
+
uses: actions/setup-python@v5
|
|
16
17
|
with:
|
|
17
|
-
python-version: "3.
|
|
18
|
+
python-version: "3.12"
|
|
18
19
|
- name: Install dependencies
|
|
19
20
|
run: |
|
|
20
21
|
pip install hatch
|
|
@@ -25,12 +26,13 @@ jobs:
|
|
|
25
26
|
|
|
26
27
|
lint-typing:
|
|
27
28
|
runs-on: ubuntu-latest
|
|
29
|
+
name: Lint type annotations
|
|
28
30
|
steps:
|
|
29
31
|
- uses: actions/checkout@v4
|
|
30
32
|
- name: Set up Python
|
|
31
|
-
uses: actions/setup-python@
|
|
33
|
+
uses: actions/setup-python@v5
|
|
32
34
|
with:
|
|
33
|
-
python-version: "3.
|
|
35
|
+
python-version: "3.12"
|
|
34
36
|
- name: Install dependencies
|
|
35
37
|
run: |
|
|
36
38
|
pip install hatch
|
|
@@ -41,13 +43,14 @@ jobs:
|
|
|
41
43
|
|
|
42
44
|
test:
|
|
43
45
|
runs-on: ubuntu-latest
|
|
46
|
+
name: Run tests
|
|
44
47
|
strategy:
|
|
45
48
|
matrix:
|
|
46
|
-
python-version: ["3.9", "3.10", "3.11"]
|
|
49
|
+
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
|
47
50
|
steps:
|
|
48
51
|
- uses: actions/checkout@v4
|
|
49
52
|
- name: Set up Python ${{ matrix.python-version }}
|
|
50
|
-
uses: actions/setup-python@
|
|
53
|
+
uses: actions/setup-python@v5
|
|
51
54
|
with:
|
|
52
55
|
python-version: ${{ matrix.python-version }}
|
|
53
56
|
- name: Install dependencies
|
|
@@ -57,3 +60,13 @@ jobs:
|
|
|
57
60
|
pip install hatch-fancy-pypi-readme
|
|
58
61
|
- name: Test
|
|
59
62
|
run: hatch run cov-test
|
|
63
|
+
- uses: codecov/codecov-action@v4
|
|
64
|
+
with:
|
|
65
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
66
|
+
|
|
67
|
+
package:
|
|
68
|
+
name: Build & verify package
|
|
69
|
+
runs-on: ubuntu-latest
|
|
70
|
+
steps:
|
|
71
|
+
- uses: actions/checkout@v4
|
|
72
|
+
- uses: hynek/build-and-inspect-python-package@v2
|
|
@@ -5,6 +5,36 @@ Changelog
|
|
|
5
5
|
|
|
6
6
|
Versions follow `Semantic Versioning <http://www.semver.org>`_
|
|
7
7
|
|
|
8
|
+
`2.3.0`_ - 2023-09-21
|
|
9
|
+
---------------------
|
|
10
|
+
|
|
11
|
+
Added
|
|
12
|
+
~~~~~
|
|
13
|
+
* :class:`.ULID` objects can now be converted to bytes with ``bytes(ulid)``.
|
|
14
|
+
* The Pydantic v2 protocol is now supported, so that the :class:`.ULID` class can be directly used
|
|
15
|
+
as type annotations in `Pydantic models <https://docs.pydantic.dev/latest/concepts/models/#basic-model-usage>`_
|
|
16
|
+
|
|
17
|
+
Changed
|
|
18
|
+
~~~~~~~
|
|
19
|
+
* The type annotations have been adapted, so that the classmethod constructors properly reflect the
|
|
20
|
+
type for :class:`.ULID` subclasses. Thanks to `@johnpaulett <https://github.com/johnpaulett>`_
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
`2.2.0`_ - 2023-09-21
|
|
24
|
+
---------------------
|
|
25
|
+
|
|
26
|
+
Added
|
|
27
|
+
~~~~~
|
|
28
|
+
* Added a new flag ``--uuid4`` to the CLI ``show`` command, that converts the provided ``ULID``
|
|
29
|
+
into an RFC 4122 compliant ``UUID``.
|
|
30
|
+
* The `ulid build` command allows the use of the special value ``-`` for all options to read its
|
|
31
|
+
inputs from ``stdin``. E.g.
|
|
32
|
+
|
|
33
|
+
.. code-block:: bash
|
|
34
|
+
|
|
35
|
+
$ date --iso-8601 | python -m ulid build --from-datetime -
|
|
36
|
+
01HAT9PVR02T3S13XB48S7GEHE
|
|
37
|
+
|
|
8
38
|
`2.1.0`_ - 2023-09-21
|
|
9
39
|
---------------------
|
|
10
40
|
|
|
@@ -113,6 +143,8 @@ Changed
|
|
|
113
143
|
* The package now has no external dependencies.
|
|
114
144
|
* The test-coverage has been raised to 100%.
|
|
115
145
|
|
|
146
|
+
.. _2.3.0: https://github.com/mdomke/python-ulid/compare/2.2.0...2.3.0
|
|
147
|
+
.. _2.2.0: https://github.com/mdomke/python-ulid/compare/2.1.0...2.2.0
|
|
116
148
|
.. _2.1.0: https://github.com/mdomke/python-ulid/compare/2.0.0...2.1.0
|
|
117
149
|
.. _2.0.0: https://github.com/mdomke/python-ulid/compare/1.1.0...2.0.0
|
|
118
150
|
.. _1.1.0: https://github.com/mdomke/python-ulid/compare/1.0.3...1.1.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: python-ulid
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: Universally unique lexicographically sortable identifier
|
|
5
5
|
Author-email: Martin Domke <mail@martindomke.net>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -15,8 +15,11 @@ Classifier: Programming Language :: Python
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.9
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
19
|
Classifier: Topic :: Software Development :: Libraries
|
|
19
20
|
Requires-Python: >=3.9
|
|
21
|
+
Provides-Extra: pydantic
|
|
22
|
+
Requires-Dist: pydantic>=2.0; extra == 'pydantic'
|
|
20
23
|
Description-Content-Type: text/x-rst
|
|
21
24
|
|
|
22
25
|
|
|
@@ -57,6 +60,12 @@ Use ``pip`` to install the library
|
|
|
57
60
|
|
|
58
61
|
$ pip install python-ulid
|
|
59
62
|
|
|
63
|
+
to include Pydantic support install the optional dependency like so
|
|
64
|
+
|
|
65
|
+
.. code-block:: bash
|
|
66
|
+
|
|
67
|
+
$ pip install python-ulid[pydantic]
|
|
68
|
+
|
|
60
69
|
.. installation-end
|
|
61
70
|
|
|
62
71
|
.. usage-begin
|
|
@@ -82,8 +91,8 @@ or use one of the named constructors
|
|
|
82
91
|
>>> ULID.from_datetime(datetime.datetime.now())
|
|
83
92
|
ULID(01E75J2XBK390V2XRH44EHC10X)
|
|
84
93
|
|
|
85
|
-
There are several options for encoding the ``ULID`` object
|
|
86
|
-
|
|
94
|
+
There are several options for encoding the ``ULID`` object
|
|
95
|
+
(e.g. string, hex, int, bytes, UUID):
|
|
87
96
|
|
|
88
97
|
.. code-block:: pycon
|
|
89
98
|
|
|
@@ -91,22 +100,54 @@ as well as to access the timestamp attribute in different formats:
|
|
|
91
100
|
'01BTGNYV6HRNK8K8VKZASZCFPE'
|
|
92
101
|
>>> ulid.hex
|
|
93
102
|
'015ea15f6cd1c56689a373fab3f63ece'
|
|
103
|
+
>>> int(ulid)
|
|
104
|
+
1820576928786795198723644692628913870
|
|
105
|
+
>>> bytes(ulid)
|
|
106
|
+
b'\x01^\xa1_l\xd1\xc5f\x89\xa3s\xfa\xb3\xf6>\xce'
|
|
107
|
+
>>> ulid.to_uuid()
|
|
108
|
+
UUID('015ea15f-6cd1-c566-89a3-73fab3f63ece')
|
|
109
|
+
|
|
110
|
+
It is also possible to directly access the timestamp component of a ``ULID``,
|
|
111
|
+
either in UNIX epoch or as ``datetime.datetime``
|
|
112
|
+
|
|
113
|
+
.. code-block:: pycon
|
|
114
|
+
|
|
94
115
|
>>> ulid.timestamp
|
|
95
116
|
1505945939.153
|
|
96
117
|
>>> ulid.datetime
|
|
97
118
|
datetime.datetime(2017, 9, 20, 22, 18, 59, 153000, tzinfo=datetime.timezone.utc)
|
|
98
|
-
>>> ulid.to_uuid()
|
|
99
|
-
UUID('015ea15f-6cd1-c566-89a3-73fab3f63ece')
|
|
100
119
|
|
|
101
120
|
.. usage-end
|
|
102
121
|
|
|
122
|
+
.. pydantic-begin
|
|
123
|
+
|
|
124
|
+
Pydantic integration
|
|
125
|
+
---------------------
|
|
126
|
+
|
|
127
|
+
The ``ULID`` class can be directly used for the popular data validation library
|
|
128
|
+
`Pydantic <https://docs.pydantic.dev/latest/>`_ like so
|
|
129
|
+
|
|
130
|
+
.. code-block:: python
|
|
131
|
+
|
|
132
|
+
from pydantic import BaseModel
|
|
133
|
+
from ulid import ULID
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class Model(BaseModel):
|
|
137
|
+
ulid: ULID
|
|
138
|
+
|
|
139
|
+
model = Model(ulid="DX89370400440532013000") # OK
|
|
140
|
+
model = Model(ulid="not-a-ulid") # Raises ValidationError
|
|
141
|
+
|
|
142
|
+
.. pydantic-end
|
|
143
|
+
|
|
103
144
|
.. cli-begin
|
|
104
145
|
|
|
105
146
|
Command line interface
|
|
106
147
|
-----------------------
|
|
107
148
|
|
|
108
149
|
The package comes with a CLI interface that can be invoked either by the script name
|
|
109
|
-
|
|
150
|
+
``ulid`` or as python module ``python -m ulid``. The CLI allows you to generate, inspect
|
|
110
151
|
and convert ULIDs, e.g.
|
|
111
152
|
|
|
112
153
|
.. code-block:: bash
|
|
@@ -124,8 +165,26 @@ and convert ULIDs, e.g.
|
|
|
124
165
|
Timestamp: 1695219822.248
|
|
125
166
|
Datetime: 2023-09-20 14:23:42.248000+00:00
|
|
126
167
|
|
|
168
|
+
There are several flags to select specific output formats for the ``show`` command, e.g.
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
.. code-block:: bash
|
|
172
|
+
|
|
173
|
+
$ ulid show --datetime 01HASFKBN8SKZTSVVS03K5AMMS
|
|
174
|
+
2023-09-20 14:23:42.248000+00:00
|
|
175
|
+
|
|
176
|
+
The special character ``-`` allows to read values from ``stdin`` so that they can be piped. E.g.
|
|
177
|
+
|
|
178
|
+
.. code-block:: bash
|
|
179
|
+
|
|
127
180
|
$ echo 01HASFKBN8SKZTSVVS03K5AMMS | ulid show --uuid -
|
|
128
|
-
018ab2f9-aea8-
|
|
181
|
+
018ab2f9-aea8-4cff-acef-7900e6555299
|
|
182
|
+
|
|
183
|
+
$ date --iso-8601 | python -m ulid build --from-datetime -
|
|
184
|
+
01HAT9PVR02T3S13XB48S7GEHE
|
|
185
|
+
|
|
186
|
+
For a full overview of flags for the ``build`` and ``show`` commands use the ``--help`` option
|
|
187
|
+
(e.g. ``ulid show --help``).
|
|
129
188
|
|
|
130
189
|
.. cli-end
|
|
131
190
|
|
|
@@ -135,3 +194,7 @@ Other implementations
|
|
|
135
194
|
* `ahawker/ulid <https://github.com/ahawker/ulid>`_
|
|
136
195
|
* `valohai/ulid2 <https://github.com/valohai/ulid2>`_
|
|
137
196
|
* `mdipierro/ulid <https://github.com/mdipierro/ulid>`_
|
|
197
|
+
* `oklog/ulid <https://github.com/oklog/ulid>`_
|
|
198
|
+
* `ulid/javascript <https://github.com/ulid/javascript>`_
|
|
199
|
+
* `RobThree/NUlid <https://github.com/RobThree/NUlid>`_
|
|
200
|
+
* `imdario/go-ulid <https://github.com/imdario/go-ulid>`_
|
|
@@ -69,6 +69,12 @@ Use ``pip`` to install the library
|
|
|
69
69
|
|
|
70
70
|
$ pip install python-ulid
|
|
71
71
|
|
|
72
|
+
to include Pydantic support install the optional dependency like so
|
|
73
|
+
|
|
74
|
+
.. code-block:: bash
|
|
75
|
+
|
|
76
|
+
$ pip install python-ulid[pydantic]
|
|
77
|
+
|
|
72
78
|
.. installation-end
|
|
73
79
|
|
|
74
80
|
.. usage-begin
|
|
@@ -94,8 +100,8 @@ or use one of the named constructors
|
|
|
94
100
|
>>> ULID.from_datetime(datetime.datetime.now())
|
|
95
101
|
ULID(01E75J2XBK390V2XRH44EHC10X)
|
|
96
102
|
|
|
97
|
-
There are several options for encoding the ``ULID`` object
|
|
98
|
-
|
|
103
|
+
There are several options for encoding the ``ULID`` object
|
|
104
|
+
(e.g. string, hex, int, bytes, UUID):
|
|
99
105
|
|
|
100
106
|
.. code-block:: pycon
|
|
101
107
|
|
|
@@ -103,22 +109,54 @@ as well as to access the timestamp attribute in different formats:
|
|
|
103
109
|
'01BTGNYV6HRNK8K8VKZASZCFPE'
|
|
104
110
|
>>> ulid.hex
|
|
105
111
|
'015ea15f6cd1c56689a373fab3f63ece'
|
|
112
|
+
>>> int(ulid)
|
|
113
|
+
1820576928786795198723644692628913870
|
|
114
|
+
>>> bytes(ulid)
|
|
115
|
+
b'\x01^\xa1_l\xd1\xc5f\x89\xa3s\xfa\xb3\xf6>\xce'
|
|
116
|
+
>>> ulid.to_uuid()
|
|
117
|
+
UUID('015ea15f-6cd1-c566-89a3-73fab3f63ece')
|
|
118
|
+
|
|
119
|
+
It is also possible to directly access the timestamp component of a ``ULID``,
|
|
120
|
+
either in UNIX epoch or as ``datetime.datetime``
|
|
121
|
+
|
|
122
|
+
.. code-block:: pycon
|
|
123
|
+
|
|
106
124
|
>>> ulid.timestamp
|
|
107
125
|
1505945939.153
|
|
108
126
|
>>> ulid.datetime
|
|
109
127
|
datetime.datetime(2017, 9, 20, 22, 18, 59, 153000, tzinfo=datetime.timezone.utc)
|
|
110
|
-
>>> ulid.to_uuid()
|
|
111
|
-
UUID('015ea15f-6cd1-c566-89a3-73fab3f63ece')
|
|
112
128
|
|
|
113
129
|
.. usage-end
|
|
114
130
|
|
|
131
|
+
.. pydantic-begin
|
|
132
|
+
|
|
133
|
+
Pydantic integration
|
|
134
|
+
---------------------
|
|
135
|
+
|
|
136
|
+
The ``ULID`` class can be directly used for the popular data validation library
|
|
137
|
+
`Pydantic <https://docs.pydantic.dev/latest/>`_ like so
|
|
138
|
+
|
|
139
|
+
.. code-block:: python
|
|
140
|
+
|
|
141
|
+
from pydantic import BaseModel
|
|
142
|
+
from ulid import ULID
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class Model(BaseModel):
|
|
146
|
+
ulid: ULID
|
|
147
|
+
|
|
148
|
+
model = Model(ulid="DX89370400440532013000") # OK
|
|
149
|
+
model = Model(ulid="not-a-ulid") # Raises ValidationError
|
|
150
|
+
|
|
151
|
+
.. pydantic-end
|
|
152
|
+
|
|
115
153
|
.. cli-begin
|
|
116
154
|
|
|
117
155
|
Command line interface
|
|
118
156
|
-----------------------
|
|
119
157
|
|
|
120
158
|
The package comes with a CLI interface that can be invoked either by the script name
|
|
121
|
-
|
|
159
|
+
``ulid`` or as python module ``python -m ulid``. The CLI allows you to generate, inspect
|
|
122
160
|
and convert ULIDs, e.g.
|
|
123
161
|
|
|
124
162
|
.. code-block:: bash
|
|
@@ -136,8 +174,26 @@ and convert ULIDs, e.g.
|
|
|
136
174
|
Timestamp: 1695219822.248
|
|
137
175
|
Datetime: 2023-09-20 14:23:42.248000+00:00
|
|
138
176
|
|
|
177
|
+
There are several flags to select specific output formats for the ``show`` command, e.g.
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
.. code-block:: bash
|
|
181
|
+
|
|
182
|
+
$ ulid show --datetime 01HASFKBN8SKZTSVVS03K5AMMS
|
|
183
|
+
2023-09-20 14:23:42.248000+00:00
|
|
184
|
+
|
|
185
|
+
The special character ``-`` allows to read values from ``stdin`` so that they can be piped. E.g.
|
|
186
|
+
|
|
187
|
+
.. code-block:: bash
|
|
188
|
+
|
|
139
189
|
$ echo 01HASFKBN8SKZTSVVS03K5AMMS | ulid show --uuid -
|
|
140
|
-
018ab2f9-aea8-
|
|
190
|
+
018ab2f9-aea8-4cff-acef-7900e6555299
|
|
191
|
+
|
|
192
|
+
$ date --iso-8601 | python -m ulid build --from-datetime -
|
|
193
|
+
01HAT9PVR02T3S13XB48S7GEHE
|
|
194
|
+
|
|
195
|
+
For a full overview of flags for the ``build`` and ``show`` commands use the ``--help`` option
|
|
196
|
+
(e.g. ``ulid show --help``).
|
|
141
197
|
|
|
142
198
|
.. cli-end
|
|
143
199
|
|
|
@@ -147,3 +203,7 @@ Other implementations
|
|
|
147
203
|
* `ahawker/ulid <https://github.com/ahawker/ulid>`_
|
|
148
204
|
* `valohai/ulid2 <https://github.com/valohai/ulid2>`_
|
|
149
205
|
* `mdipierro/ulid <https://github.com/mdipierro/ulid>`_
|
|
206
|
+
* `oklog/ulid <https://github.com/oklog/ulid>`_
|
|
207
|
+
* `ulid/javascript <https://github.com/ulid/javascript>`_
|
|
208
|
+
* `RobThree/NUlid <https://github.com/RobThree/NUlid>`_
|
|
209
|
+
* `imdario/go-ulid <https://github.com/imdario/go-ulid>`_
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.. include:: ../../CHANGELOG.rst
|
|
@@ -3,9 +3,9 @@ import sys
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
sys.path.insert(0, os.path.abspath("
|
|
6
|
+
sys.path.insert(0, os.path.abspath("../.."))
|
|
7
7
|
|
|
8
|
-
import ulid
|
|
8
|
+
import ulid
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
copyright = f"{datetime.now().year}, Martin Domke"
|
|
@@ -24,6 +24,7 @@ extensions = [
|
|
|
24
24
|
"sphinx.ext.autodoc",
|
|
25
25
|
"sphinx.ext.napoleon",
|
|
26
26
|
"sphinx.ext.viewcode",
|
|
27
|
+
"sphinx_copybutton",
|
|
27
28
|
]
|
|
28
29
|
|
|
29
30
|
# Add any paths that contain templates here, relative to this directory.
|
|
@@ -32,7 +33,7 @@ templates_path = ["_templates"]
|
|
|
32
33
|
# List of patterns, relative to source directory, that match files and
|
|
33
34
|
# directories to ignore when looking for source files.
|
|
34
35
|
# This pattern also affects html_static_path and html_extra_path.
|
|
35
|
-
exclude_patterns = [
|
|
36
|
+
exclude_patterns = []
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
# -- Options for HTML output -------------------------------------------------
|
|
@@ -40,15 +41,8 @@ exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
|
|
40
41
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
|
41
42
|
# a list of builtin themes.
|
|
42
43
|
#
|
|
43
|
-
html_logo = "
|
|
44
|
+
html_logo = "../../logo.png"
|
|
44
45
|
html_theme = "furo"
|
|
45
|
-
html_theme_options = {
|
|
46
|
-
"github_user": "mdomke",
|
|
47
|
-
"github_repo": "python-ulid",
|
|
48
|
-
"description": "A library for working with ULIDs",
|
|
49
|
-
"sidebar_collapse": False,
|
|
50
|
-
"logo_text_align": "center",
|
|
51
|
-
}
|
|
52
46
|
|
|
53
47
|
html_title = "Python ULID docs"
|
|
54
48
|
|
|
@@ -4,19 +4,23 @@ Python ULID
|
|
|
4
4
|
Release v\ |release| (:ref:`What's new <changelog>`)
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
.. include::
|
|
7
|
+
.. include:: ../../README.rst
|
|
8
8
|
:start-after: teaser-begin
|
|
9
9
|
:end-before: teaser-end
|
|
10
10
|
|
|
11
|
-
.. include::
|
|
11
|
+
.. include:: ../../README.rst
|
|
12
12
|
:start-after: installation-begin
|
|
13
13
|
:end-before: installation-end
|
|
14
14
|
|
|
15
|
-
.. include::
|
|
15
|
+
.. include:: ../../README.rst
|
|
16
16
|
:start-after: usage-begin
|
|
17
17
|
:end-before: usage-end
|
|
18
18
|
|
|
19
|
-
.. include::
|
|
19
|
+
.. include:: ../../README.rst
|
|
20
|
+
:start-after: pydantic-begin
|
|
21
|
+
:end-before: pydantic-end
|
|
22
|
+
|
|
23
|
+
.. include:: ../../README.rst
|
|
20
24
|
:start-after: cli-begin
|
|
21
25
|
:end-before: cli-end
|
|
22
26
|
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
[envs.default]
|
|
2
2
|
dependencies = [
|
|
3
|
-
"freezegun==1.
|
|
3
|
+
"freezegun==1.4.*",
|
|
4
4
|
"pytest-cov==4.1.*",
|
|
5
|
-
"pytest==
|
|
5
|
+
"pytest==8.1.*",
|
|
6
|
+
]
|
|
7
|
+
features = [
|
|
8
|
+
"pydantic"
|
|
6
9
|
]
|
|
7
10
|
|
|
8
11
|
[envs.default.scripts]
|
|
@@ -11,18 +14,19 @@ cov-test = "pytest --cov {args:ulid} --cov-report=term-missing --cov-report=xml"
|
|
|
11
14
|
|
|
12
15
|
[envs.lint]
|
|
13
16
|
dependencies = [
|
|
14
|
-
"ruff==0.
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
+
"ruff==0.3.*",
|
|
18
|
+
"mypy==1.9.*",
|
|
19
|
+
"doc8==1.1.*",
|
|
17
20
|
]
|
|
18
21
|
|
|
19
22
|
[envs.lint.scripts]
|
|
20
23
|
typing = "mypy --install-types --non-interactive {args:ulid}"
|
|
21
24
|
style = [
|
|
22
|
-
"
|
|
23
|
-
"ruff {args:.}",
|
|
25
|
+
"ruff format --check --diff {args:.}",
|
|
26
|
+
"ruff check {args:.}",
|
|
24
27
|
]
|
|
25
28
|
fmt = [
|
|
26
|
-
"
|
|
27
|
-
"ruff --fix {args:.}",
|
|
29
|
+
"ruff format {args:.}",
|
|
30
|
+
"ruff check --fix {args:.}",
|
|
28
31
|
]
|
|
32
|
+
docs = "doc8 docs"
|
|
@@ -22,9 +22,15 @@ classifiers = [
|
|
|
22
22
|
"Programming Language :: Python :: 3.9",
|
|
23
23
|
"Programming Language :: Python :: 3.10",
|
|
24
24
|
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
25
26
|
"Topic :: Software Development :: Libraries",
|
|
26
27
|
]
|
|
27
28
|
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
pydantic = [
|
|
31
|
+
"pydantic>=2.0"
|
|
32
|
+
]
|
|
33
|
+
|
|
28
34
|
[project.scripts]
|
|
29
35
|
ulid = "ulid.__main__:entrypoint"
|
|
30
36
|
|
|
@@ -42,6 +48,8 @@ line-length = 100
|
|
|
42
48
|
[tool.ruff]
|
|
43
49
|
target-version = "py39"
|
|
44
50
|
line-length = 100
|
|
51
|
+
|
|
52
|
+
[tool.ruff.lint]
|
|
45
53
|
select = ["A", "B", "C", "C4", "E", "F", "I", "N", "PT", "Q", "RUF", "S", "SIM", "T10", "UP", "W", "YTT"]
|
|
46
54
|
fixable = ["RUF100", "I001"]
|
|
47
55
|
ignore = [
|
|
@@ -50,21 +58,14 @@ ignore = [
|
|
|
50
58
|
"A003", # Allow shadowing bultins on classes
|
|
51
59
|
]
|
|
52
60
|
|
|
53
|
-
[tool.ruff.mccabe]
|
|
61
|
+
[tool.ruff.lint.mccabe]
|
|
54
62
|
max-complexity = 15
|
|
55
63
|
|
|
56
|
-
[tool.ruff.isort]
|
|
64
|
+
[tool.ruff.lint.isort]
|
|
57
65
|
force-single-line = true
|
|
58
66
|
lines-after-imports = 2
|
|
59
67
|
order-by-type = false
|
|
60
68
|
|
|
61
|
-
[tool.isort]
|
|
62
|
-
profile = "black"
|
|
63
|
-
force_alphabetical_sort_within_sections = true
|
|
64
|
-
force_single_line = true
|
|
65
|
-
lines_after_imports = 2
|
|
66
|
-
line_length = 100
|
|
67
|
-
|
|
68
69
|
[tool.coverage.run]
|
|
69
70
|
branch = true
|
|
70
71
|
parallel = true
|
|
@@ -78,4 +79,4 @@ content-type = "text/x-rst"
|
|
|
78
79
|
|
|
79
80
|
[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]]
|
|
80
81
|
path = "README.rst"
|
|
81
|
-
start-after = ".. teaser-begin"
|
|
82
|
+
start-after = ".. teaser-begin"
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
import ulid.__main__ as cli
|
|
4
|
+
from ulid import ULID
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.mark.parametrize("option", ["", "uuid", "uuid4", "hex", "int", "timestamp", "datetime"])
|
|
8
|
+
def test_parse_show(option: str):
|
|
9
|
+
ulid = ULID()
|
|
10
|
+
argv = ["show", f"--{option}", str(ulid)]
|
|
11
|
+
output = cli.main(argv)
|
|
12
|
+
if option == "uuid":
|
|
13
|
+
assert output == str(ulid.to_uuid())
|
|
14
|
+
elif option == "uuid4":
|
|
15
|
+
assert output == str(ulid.to_uuid4())
|
|
16
|
+
elif option == "hex":
|
|
17
|
+
assert output == ulid.hex
|
|
18
|
+
elif option == "int":
|
|
19
|
+
assert output == str(int(ulid))
|
|
20
|
+
elif option == "timestamp":
|
|
21
|
+
assert output == str(ulid.timestamp)
|
|
22
|
+
elif option == "datetime":
|
|
23
|
+
assert output == ulid.datetime.isoformat()
|
|
24
|
+
else:
|
|
25
|
+
assert str(ulid) in output
|
|
26
|
+
assert ulid.hex in output
|
|
27
|
+
assert str(int(ulid)) in output
|
|
28
|
+
assert str(ulid.timestamp) in output
|
|
29
|
+
assert str(ulid.datetime.isoformat()) in output
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@pytest.mark.parametrize(
|
|
33
|
+
"option",
|
|
34
|
+
[
|
|
35
|
+
"",
|
|
36
|
+
"from-uuid",
|
|
37
|
+
"from-str",
|
|
38
|
+
"from-hex",
|
|
39
|
+
"from-int",
|
|
40
|
+
"from-timestamp",
|
|
41
|
+
"from-datetime",
|
|
42
|
+
],
|
|
43
|
+
)
|
|
44
|
+
def test_build(option: str):
|
|
45
|
+
ulid = ULID()
|
|
46
|
+
value: str
|
|
47
|
+
includes_randomness = True
|
|
48
|
+
includes_timestamp = True
|
|
49
|
+
if option.endswith("uuid"):
|
|
50
|
+
value = str(ulid.to_uuid())
|
|
51
|
+
elif option.endswith("str"):
|
|
52
|
+
value = str(ulid)
|
|
53
|
+
elif option.endswith("hex"):
|
|
54
|
+
value = ulid.hex
|
|
55
|
+
elif option.endswith("int"):
|
|
56
|
+
value = str(int(ulid))
|
|
57
|
+
elif option.endswith("timestamp"):
|
|
58
|
+
value = str(ulid.timestamp)
|
|
59
|
+
includes_randomness = False
|
|
60
|
+
elif option.endswith("datetime"):
|
|
61
|
+
value = ulid.datetime.isoformat()
|
|
62
|
+
includes_randomness = False
|
|
63
|
+
else:
|
|
64
|
+
includes_timestamp = False
|
|
65
|
+
includes_randomness = False
|
|
66
|
+
|
|
67
|
+
argv = ["build"]
|
|
68
|
+
if option:
|
|
69
|
+
argv += [f"--{option}", value]
|
|
70
|
+
output = cli.main(argv)
|
|
71
|
+
|
|
72
|
+
if includes_randomness and includes_timestamp:
|
|
73
|
+
assert output == str(ulid)
|
|
74
|
+
|
|
75
|
+
ulid_out = ULID.from_str(output)
|
|
76
|
+
if includes_timestamp:
|
|
77
|
+
assert ulid_out.datetime == ulid.datetime
|
|
@@ -8,6 +8,8 @@ from typing import Union
|
|
|
8
8
|
|
|
9
9
|
import pytest
|
|
10
10
|
from freezegun import freeze_time
|
|
11
|
+
from pydantic import BaseModel
|
|
12
|
+
from pydantic import ValidationError
|
|
11
13
|
|
|
12
14
|
from ulid import base32
|
|
13
15
|
from ulid import constants
|
|
@@ -115,7 +117,8 @@ def test_hash() -> None:
|
|
|
115
117
|
@freeze_time()
|
|
116
118
|
def test_ulid_from_time() -> None:
|
|
117
119
|
ulid1 = ULID.from_timestamp(time.time())
|
|
118
|
-
ulid2 = ULID.
|
|
120
|
+
ulid2 = ULID.from_timestamp(time.time_ns() // 1000000)
|
|
121
|
+
ulid3 = ULID.from_datetime(utcnow())
|
|
119
122
|
|
|
120
123
|
now = utcnow()
|
|
121
124
|
t = time.time()
|
|
@@ -126,6 +129,9 @@ def test_ulid_from_time() -> None:
|
|
|
126
129
|
assert ulid2.timestamp == pytest.approx(t)
|
|
127
130
|
datetimes_almost_equal(ulid2.datetime, now)
|
|
128
131
|
|
|
132
|
+
assert ulid2.timestamp == pytest.approx(t)
|
|
133
|
+
datetimes_almost_equal(ulid3.datetime, now)
|
|
134
|
+
|
|
129
135
|
|
|
130
136
|
@freeze_time()
|
|
131
137
|
def test_ulid_from_timestamp() -> None:
|
|
@@ -155,3 +161,19 @@ Params = Union[bytes, str, int, float]
|
|
|
155
161
|
def test_ulid_invalid_input(constructor: Callable[[Params], ULID], value: Params) -> None:
|
|
156
162
|
with pytest.raises(ValueError): # noqa: PT011
|
|
157
163
|
constructor(value)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def test_pydantic_protocol() -> None:
|
|
167
|
+
ulid = ULID()
|
|
168
|
+
|
|
169
|
+
class Model(BaseModel):
|
|
170
|
+
ulid: ULID
|
|
171
|
+
|
|
172
|
+
for value in [ulid, str(ulid), int(ulid), bytes(ulid)]:
|
|
173
|
+
model = Model(ulid=value)
|
|
174
|
+
assert isinstance(model.ulid, ULID)
|
|
175
|
+
assert model.ulid == ulid
|
|
176
|
+
|
|
177
|
+
for value in [b"not-enough", "not-enough"]:
|
|
178
|
+
with pytest.raises(ValidationError):
|
|
179
|
+
Model(ulid=value)
|
|
@@ -9,15 +9,21 @@ from datetime import datetime
|
|
|
9
9
|
from datetime import timezone
|
|
10
10
|
from typing import Any
|
|
11
11
|
from typing import Generic
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
12
13
|
from typing import TypeVar
|
|
13
14
|
|
|
14
15
|
from ulid import base32
|
|
15
16
|
from ulid import constants
|
|
16
17
|
|
|
17
18
|
|
|
19
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
20
|
+
from pydantic import GetCoreSchemaHandler
|
|
21
|
+
from pydantic import ValidatorFunctionWrapHandler
|
|
22
|
+
from pydantic_core import CoreSchema
|
|
23
|
+
|
|
18
24
|
try:
|
|
19
25
|
from importlib.metadata import version
|
|
20
|
-
except ImportError:
|
|
26
|
+
except ImportError: # pragma: no cover
|
|
21
27
|
from importlib_metadata import version # type: ignore
|
|
22
28
|
|
|
23
29
|
|
|
@@ -44,6 +50,9 @@ class validate_type(Generic[T]): # noqa: N801
|
|
|
44
50
|
return wrapped
|
|
45
51
|
|
|
46
52
|
|
|
53
|
+
U = TypeVar("U", bound="ULID")
|
|
54
|
+
|
|
55
|
+
|
|
47
56
|
@functools.total_ordering
|
|
48
57
|
class ULID:
|
|
49
58
|
"""The :class:`ULID` object consists of a timestamp part of 48 bits and of 80 random bits.
|
|
@@ -67,11 +76,13 @@ class ULID:
|
|
|
67
76
|
def __init__(self, value: bytes | None = None) -> None:
|
|
68
77
|
if value is not None and len(value) != constants.BYTES_LEN:
|
|
69
78
|
raise ValueError("ULID has to be exactly 16 bytes long.")
|
|
70
|
-
self.bytes: bytes =
|
|
79
|
+
self.bytes: bytes = (
|
|
80
|
+
value or ULID.from_timestamp(time.time_ns() // constants.NANOSECS_IN_MILLISECS).bytes
|
|
81
|
+
)
|
|
71
82
|
|
|
72
83
|
@classmethod
|
|
73
84
|
@validate_type(datetime)
|
|
74
|
-
def from_datetime(cls, value: datetime) ->
|
|
85
|
+
def from_datetime(cls: type[U], value: datetime) -> U:
|
|
75
86
|
"""Create a new :class:`ULID`-object from a :class:`datetime`. The timestamp part of the
|
|
76
87
|
`ULID` will be set to the corresponding timestamp of the datetime.
|
|
77
88
|
|
|
@@ -85,7 +96,7 @@ class ULID:
|
|
|
85
96
|
|
|
86
97
|
@classmethod
|
|
87
98
|
@validate_type(int, float)
|
|
88
|
-
def from_timestamp(cls, value: int | float) ->
|
|
99
|
+
def from_timestamp(cls: type[U], value: int | float) -> U:
|
|
89
100
|
"""Create a new :class:`ULID`-object from a timestamp. The timestamp can be either a
|
|
90
101
|
`float` representing the time in seconds (as it would be returned by :func:`time.time()`)
|
|
91
102
|
or an `int` in milliseconds.
|
|
@@ -104,7 +115,7 @@ class ULID:
|
|
|
104
115
|
|
|
105
116
|
@classmethod
|
|
106
117
|
@validate_type(uuid.UUID)
|
|
107
|
-
def from_uuid(cls, value: uuid.UUID) ->
|
|
118
|
+
def from_uuid(cls: type[U], value: uuid.UUID) -> U:
|
|
108
119
|
"""Create a new :class:`ULID`-object from a :class:`uuid.UUID`. The timestamp part will be
|
|
109
120
|
random in that case.
|
|
110
121
|
|
|
@@ -118,25 +129,25 @@ class ULID:
|
|
|
118
129
|
|
|
119
130
|
@classmethod
|
|
120
131
|
@validate_type(bytes)
|
|
121
|
-
def from_bytes(cls, bytes_: bytes) ->
|
|
132
|
+
def from_bytes(cls: type[U], bytes_: bytes) -> U:
|
|
122
133
|
"""Create a new :class:`ULID`-object from sequence of 16 bytes."""
|
|
123
134
|
return cls(bytes_)
|
|
124
135
|
|
|
125
136
|
@classmethod
|
|
126
137
|
@validate_type(str)
|
|
127
|
-
def from_hex(cls, value: str) ->
|
|
138
|
+
def from_hex(cls: type[U], value: str) -> U:
|
|
128
139
|
"""Create a new :class:`ULID`-object from 32 character string of hex values."""
|
|
129
140
|
return cls.from_bytes(bytes.fromhex(value))
|
|
130
141
|
|
|
131
142
|
@classmethod
|
|
132
143
|
@validate_type(str)
|
|
133
|
-
def from_str(cls, string: str) ->
|
|
144
|
+
def from_str(cls: type[U], string: str) -> U:
|
|
134
145
|
"""Create a new :class:`ULID`-object from a 26 char long string representation."""
|
|
135
146
|
return cls(base32.decode(string))
|
|
136
147
|
|
|
137
148
|
@classmethod
|
|
138
149
|
@validate_type(int)
|
|
139
|
-
def from_int(cls, value: int) ->
|
|
150
|
+
def from_int(cls: type[U], value: int) -> U:
|
|
140
151
|
"""Create a new :class:`ULID`-object from an `int`."""
|
|
141
152
|
return cls(int.to_bytes(value, constants.BYTES_LEN, "big"))
|
|
142
153
|
|
|
@@ -209,6 +220,10 @@ class ULID:
|
|
|
209
220
|
"""Encode this object as an integer."""
|
|
210
221
|
return int.from_bytes(self.bytes, byteorder="big")
|
|
211
222
|
|
|
223
|
+
def __bytes__(self) -> bytes:
|
|
224
|
+
"""Encode this object as byte sequence."""
|
|
225
|
+
return self.bytes
|
|
226
|
+
|
|
212
227
|
def __lt__(self, other: Any) -> bool:
|
|
213
228
|
if isinstance(other, ULID):
|
|
214
229
|
return self.bytes < other.bytes
|
|
@@ -233,3 +248,34 @@ class ULID:
|
|
|
233
248
|
|
|
234
249
|
def __hash__(self) -> int:
|
|
235
250
|
return hash(self.bytes)
|
|
251
|
+
|
|
252
|
+
@classmethod
|
|
253
|
+
def __get_pydantic_core_schema__(cls, source: Any, handler: GetCoreSchemaHandler) -> CoreSchema:
|
|
254
|
+
from pydantic_core import core_schema
|
|
255
|
+
|
|
256
|
+
return core_schema.no_info_wrap_validator_function(
|
|
257
|
+
cls._pydantic_validate,
|
|
258
|
+
core_schema.union_schema(
|
|
259
|
+
[
|
|
260
|
+
core_schema.is_instance_schema(ULID),
|
|
261
|
+
core_schema.no_info_plain_validator_function(ULID),
|
|
262
|
+
]
|
|
263
|
+
),
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
@classmethod
|
|
267
|
+
def _pydantic_validate(cls, value: Any, handler: ValidatorFunctionWrapHandler) -> Any:
|
|
268
|
+
from pydantic_core import PydanticCustomError
|
|
269
|
+
|
|
270
|
+
try:
|
|
271
|
+
if isinstance(value, int):
|
|
272
|
+
ulid = cls.from_int(value)
|
|
273
|
+
elif isinstance(value, str):
|
|
274
|
+
ulid = cls.from_str(value)
|
|
275
|
+
elif isinstance(value, ULID):
|
|
276
|
+
ulid = value
|
|
277
|
+
else:
|
|
278
|
+
ulid = cls.from_bytes(value)
|
|
279
|
+
except ValueError as err:
|
|
280
|
+
raise PydanticCustomError("ulid_format", "Unrecognized format") from err
|
|
281
|
+
return handler(ulid)
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import argparse
|
|
2
4
|
import shutil
|
|
3
5
|
import sys
|
|
4
6
|
import textwrap
|
|
7
|
+
from collections.abc import Callable
|
|
5
8
|
from collections.abc import Sequence
|
|
6
9
|
from datetime import datetime
|
|
7
10
|
from functools import partial
|
|
11
|
+
from typing import Any
|
|
8
12
|
from uuid import UUID
|
|
9
13
|
|
|
10
14
|
import ulid
|
|
@@ -47,37 +51,31 @@ def make_parser(prog: str | None = None) -> argparse.ArgumentParser:
|
|
|
47
51
|
)
|
|
48
52
|
b.add_argument(
|
|
49
53
|
"--from-int",
|
|
50
|
-
type=int,
|
|
51
54
|
metavar="<int>",
|
|
52
55
|
help="create from integer",
|
|
53
56
|
)
|
|
54
57
|
b.add_argument(
|
|
55
58
|
"--from-hex",
|
|
56
|
-
type=str,
|
|
57
59
|
metavar="<str>",
|
|
58
60
|
help="create from 32 character hex value",
|
|
59
61
|
)
|
|
60
62
|
b.add_argument(
|
|
61
63
|
"--from-str",
|
|
62
|
-
type=str,
|
|
63
64
|
metavar="<str>",
|
|
64
65
|
help="create from base32 encoded string of length 26",
|
|
65
66
|
)
|
|
66
67
|
b.add_argument(
|
|
67
68
|
"--from-timestamp",
|
|
68
|
-
type=parse_numeric,
|
|
69
69
|
metavar="<int|float>",
|
|
70
70
|
help="create from timestamp either as float in secs or int as millis",
|
|
71
71
|
)
|
|
72
72
|
b.add_argument(
|
|
73
73
|
"--from-datetime",
|
|
74
|
-
type=datetime.fromisoformat,
|
|
75
74
|
metavar="<iso8601>",
|
|
76
75
|
help="create from datetime. The timestamp part of the ULID will be taken from the datetime",
|
|
77
76
|
)
|
|
78
77
|
b.add_argument(
|
|
79
78
|
"--from-uuid",
|
|
80
|
-
type=UUID,
|
|
81
79
|
metavar="<uuid>",
|
|
82
80
|
help="create from given UUID. The timestamp part will be random.",
|
|
83
81
|
)
|
|
@@ -85,7 +83,8 @@ def make_parser(prog: str | None = None) -> argparse.ArgumentParser:
|
|
|
85
83
|
|
|
86
84
|
s = subparsers.add_parser("show", help="show properties of a ULID")
|
|
87
85
|
s.add_argument("ulid", help="the ULID to inspect. The special value - reads from stdin")
|
|
88
|
-
s.add_argument("--uuid", action="store_true", help="convert to UUID")
|
|
86
|
+
s.add_argument("--uuid", action="store_true", help="convert to fully random UUID")
|
|
87
|
+
s.add_argument("--uuid4", action="store_true", help="convert to RFC 4122 compliant UUIDv4")
|
|
89
88
|
s.add_argument("--hex", action="store_true", help="convert to hex")
|
|
90
89
|
s.add_argument("--int", action="store_true", help="convert to int")
|
|
91
90
|
s.add_argument("--timestamp", "--ts", action="store_true", help="show timestamp")
|
|
@@ -94,6 +93,18 @@ def make_parser(prog: str | None = None) -> argparse.ArgumentParser:
|
|
|
94
93
|
return parser
|
|
95
94
|
|
|
96
95
|
|
|
96
|
+
def main(argv: Sequence[str], prog: str | None = None) -> str:
|
|
97
|
+
args = make_parser(prog).parse_args(argv)
|
|
98
|
+
return args.func(args)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def from_value_or_stdin(value: str, convert: Callable[[str], Any] | None = None) -> Any:
|
|
102
|
+
value = sys.stdin.readline().strip() if value == "-" else value
|
|
103
|
+
if convert is not None:
|
|
104
|
+
return convert(value)
|
|
105
|
+
return value
|
|
106
|
+
|
|
107
|
+
|
|
97
108
|
def parse_numeric(s: str) -> int | float:
|
|
98
109
|
try:
|
|
99
110
|
return int(s)
|
|
@@ -101,60 +112,54 @@ def parse_numeric(s: str) -> int | float:
|
|
|
101
112
|
return float(s)
|
|
102
113
|
|
|
103
114
|
|
|
104
|
-
def
|
|
105
|
-
args = make_parser(prog).parse_args(argv)
|
|
106
|
-
args.func(args)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def build(args: argparse.Namespace) -> None:
|
|
115
|
+
def build(args: argparse.Namespace) -> str:
|
|
110
116
|
ulid: ULID
|
|
111
117
|
if args.from_int is not None:
|
|
112
|
-
ulid = ULID.from_int(args.from_int)
|
|
118
|
+
ulid = ULID.from_int(from_value_or_stdin(args.from_int, int))
|
|
113
119
|
elif args.from_hex is not None:
|
|
114
|
-
ulid = ULID.from_hex(args.from_hex)
|
|
120
|
+
ulid = ULID.from_hex(from_value_or_stdin(args.from_hex))
|
|
115
121
|
elif args.from_str is not None:
|
|
116
|
-
ulid = ULID.from_str(args.from_str)
|
|
122
|
+
ulid = ULID.from_str(from_value_or_stdin(args.from_str))
|
|
117
123
|
elif args.from_timestamp is not None:
|
|
118
|
-
ulid = ULID.from_timestamp(args.from_timestamp)
|
|
124
|
+
ulid = ULID.from_timestamp(from_value_or_stdin(args.from_timestamp, parse_numeric))
|
|
119
125
|
elif args.from_datetime is not None:
|
|
120
|
-
ulid = ULID.from_datetime(args.from_datetime)
|
|
126
|
+
ulid = ULID.from_datetime(from_value_or_stdin(args.from_datetime, datetime.fromisoformat))
|
|
121
127
|
elif args.from_uuid is not None:
|
|
122
|
-
ulid = ULID.from_uuid(args.from_uuid)
|
|
128
|
+
ulid = ULID.from_uuid(from_value_or_stdin(args.from_uuid, UUID))
|
|
123
129
|
else:
|
|
124
130
|
ulid = ULID()
|
|
125
|
-
|
|
131
|
+
return str(ulid)
|
|
126
132
|
|
|
127
133
|
|
|
128
|
-
def show(args: argparse.Namespace) ->
|
|
129
|
-
|
|
130
|
-
ulid: ULID = ULID.from_str(value)
|
|
134
|
+
def show(args: argparse.Namespace) -> str:
|
|
135
|
+
ulid: ULID = ULID.from_str(from_value_or_stdin(args.ulid))
|
|
131
136
|
if args.uuid:
|
|
132
|
-
|
|
137
|
+
return str(ulid.to_uuid())
|
|
138
|
+
elif args.uuid4:
|
|
139
|
+
return str(ulid.to_uuid4())
|
|
133
140
|
elif args.hex:
|
|
134
|
-
|
|
141
|
+
return ulid.hex
|
|
135
142
|
elif args.int:
|
|
136
|
-
|
|
143
|
+
return str(int(ulid))
|
|
137
144
|
elif args.timestamp:
|
|
138
|
-
|
|
145
|
+
return str(ulid.timestamp)
|
|
139
146
|
elif args.datetime:
|
|
140
|
-
|
|
147
|
+
return ulid.datetime.isoformat()
|
|
141
148
|
else:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
).strip()
|
|
152
|
-
)
|
|
149
|
+
return textwrap.dedent(
|
|
150
|
+
f"""
|
|
151
|
+
ULID: {ulid!s}
|
|
152
|
+
Hex: {ulid.hex}
|
|
153
|
+
Int: {int(ulid)}
|
|
154
|
+
Timestamp: {ulid.timestamp}
|
|
155
|
+
Datetime: {ulid.datetime.isoformat()}
|
|
156
|
+
"""
|
|
157
|
+
).strip()
|
|
153
158
|
|
|
154
159
|
|
|
155
|
-
def entrypoint() -> None:
|
|
156
|
-
main(sys.argv[1:])
|
|
160
|
+
def entrypoint() -> None: # pragma: no cover
|
|
161
|
+
print(main(sys.argv[1:]))
|
|
157
162
|
|
|
158
163
|
|
|
159
|
-
if __name__ == "__main__":
|
|
164
|
+
if __name__ == "__main__": # pragma: no cover
|
|
160
165
|
main(sys.argv[1:], "python -m ulid")
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
repos:
|
|
2
|
-
- repo: https://github.com/psf/black
|
|
3
|
-
rev: 23.9.1
|
|
4
|
-
hooks:
|
|
5
|
-
- id: black
|
|
6
|
-
- repo: https://github.com/timothycrosley/isort
|
|
7
|
-
rev: 5.12.0
|
|
8
|
-
hooks:
|
|
9
|
-
- id: isort
|
|
10
|
-
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
11
|
-
# Ruff version.
|
|
12
|
-
rev: v0.0.290
|
|
13
|
-
hooks:
|
|
14
|
-
- id: ruff
|
|
15
|
-
- repo: https://github.com/pycqa/doc8
|
|
16
|
-
rev: v1.1.1
|
|
17
|
-
hooks:
|
|
18
|
-
- id: doc8
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.. include:: ../CHANGELOG.rst
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
furo
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|