mfid 1.0.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.
- mfid-1.0.0/LICENSE +24 -0
- mfid-1.0.0/PKG-INFO +198 -0
- mfid-1.0.0/README.md +178 -0
- mfid-1.0.0/mfid.egg-info/PKG-INFO +198 -0
- mfid-1.0.0/mfid.egg-info/SOURCES.txt +9 -0
- mfid-1.0.0/mfid.egg-info/dependency_links.txt +1 -0
- mfid-1.0.0/mfid.egg-info/requires.txt +1 -0
- mfid-1.0.0/mfid.egg-info/top_level.txt +1 -0
- mfid-1.0.0/mfid.py +103 -0
- mfid-1.0.0/setup.cfg +4 -0
- mfid-1.0.0/setup.py +25 -0
mfid-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Copyright (c) 2024-2025 Edward S. Barnard
|
|
2
|
+
All rights reserved.
|
|
3
|
+
|
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
|
6
|
+
* Redistributions of source code must retain the above copyright
|
|
7
|
+
notice, this list of conditions and the following disclaimer.
|
|
8
|
+
* Redistributions in binary form must reproduce the above copyright
|
|
9
|
+
notice, this list of conditions and the following disclaimer in the
|
|
10
|
+
documentation and/or other materials provided with the distribution.
|
|
11
|
+
* Neither the name of the <organization> nor the
|
|
12
|
+
names of its contributors may be used to endorse or promote products
|
|
13
|
+
derived from this software without specific prior written permission.
|
|
14
|
+
|
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
16
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
17
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
18
|
+
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
|
19
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
20
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
21
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
22
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
23
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
24
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
mfid-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: mfid
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: MFID: a Mighty Fine Identifier
|
|
5
|
+
Home-page: https://github.com/MolecularFoundry/mfid
|
|
6
|
+
Author: Edward S. Barnard
|
|
7
|
+
Author-email: esbarnard@lbl.gov
|
|
8
|
+
License: BSD
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: uuid7
|
|
12
|
+
Dynamic: author
|
|
13
|
+
Dynamic: author-email
|
|
14
|
+
Dynamic: description
|
|
15
|
+
Dynamic: description-content-type
|
|
16
|
+
Dynamic: home-page
|
|
17
|
+
Dynamic: license
|
|
18
|
+
Dynamic: requires-dist
|
|
19
|
+
Dynamic: summary
|
|
20
|
+
|
|
21
|
+
# MFID: a Mighty Fine Identifier
|
|
22
|
+
|
|
23
|
+
A compact universal persistent identifier is useful in many contexts,
|
|
24
|
+
especially in scientific and engineering disiplines that happen distributed around the world.
|
|
25
|
+
|
|
26
|
+
We would like to uniquely identify data sets, samples, without coordinating the generation of such identifiers.
|
|
27
|
+
|
|
28
|
+
Guiding principles for creating an identifier scheme:
|
|
29
|
+
|
|
30
|
+
- Global uniqueness
|
|
31
|
+
- Compact
|
|
32
|
+
- Human readable/typeable
|
|
33
|
+
- Lexicographically sortable (by time)
|
|
34
|
+
- Used as a filename (limits on filesystems: case-sensitivity, length, allowable characters)
|
|
35
|
+
- Use existing standards as much as possible
|
|
36
|
+
|
|
37
|
+
**Short TL;DR:** MFID is a UUIDv7 + Crockford's Base32 representation. MFID gives a standards compliant timestamp-based compact universally unique identifier.
|
|
38
|
+
|
|
39
|
+
**An example MFID: `0swqzb3a1sthv000xd8kta0vrw`**
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
## Using existing standards
|
|
43
|
+
|
|
44
|
+
MFID is based on the UUIDv7 standard. UUID's are [RFC standardized](https://datatracker.ietf.org/doc/rfc9562/) "universal" identifiers. UUIDs have are 128-bit numbers with a specifc form, including randomly generated sections.
|
|
45
|
+
128 bits enough for every grain of sand on earth to have 10<sup>20</sup> UUIDs.
|
|
46
|
+
Therefore, collisions are extremely unlikely, so we can create UUIDs without checking a central database.
|
|
47
|
+
|
|
48
|
+
UUIDs are cannonically represented as a hexdecimal string with `-` seperators. This ends up giving you a 36 character representation. For Example: `064dfc00-f4e6-71ae-8000-d890eded3ecd`. MFID uses the UUIDv7 unqiue indentifier, but packs it into a more space efficent manner for use in labelling data and physical objects (See Compact Representation section below).
|
|
49
|
+
|
|
50
|
+
UUIDs v7 (part of the 2024 version of the RFC standard) has an interesting and useful property: Leading XX bits are time ordered and represent a timestamp of creation. This means that to the millisecond time-scale UUIDv7s are lexicographically by time. The rest of the UUIDv7 bits encode randomness, avoiding collision issues.
|
|
51
|
+
|
|
52
|
+
### Anatomy of a UUIDv7 (borrowed from python package `uuidv7`):
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
0 1 2 3
|
|
56
|
+
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
57
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
58
|
+
t1 | unixts (secs since epoch) |
|
|
59
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
60
|
+
t2/t3 |unixts | frac secs (12 bits) | ver | frac secs (12 bits) |
|
|
61
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
62
|
+
t4/rand |var| seq (14 bits) | rand (16 bits) |
|
|
63
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
64
|
+
rand | rand (32 bits) |
|
|
65
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
Other non-RFC standards exist and have inspired MFID. These schemes handle many of our needs, but not all:
|
|
70
|
+
|
|
71
|
+
[**ULID**](https://github.com/ulid/spec): Handles most of the needs, but is not convertable to and from a valid RFC-defined UUID. Inspired our use of Crockerfords Base32 representation.
|
|
72
|
+
|
|
73
|
+
[**NanoID**](https://github.com/ai/nanoid): A nice compact global identifier, but random and time sorted.
|
|
74
|
+
|
|
75
|
+
## Compact representation
|
|
76
|
+
|
|
77
|
+
[Crockford’s Base32](https://www.crockford.com/base32.html) encoding scheme can take the 128bit UUIDv7 and its 36 character hexdecimal representation and compactly present the same information in 26 alphanumeric characters (09,a-z).
|
|
78
|
+
|
|
79
|
+
UUIDv7: `06797fac-6a0e-751d-8000-eb513d281bc7`
|
|
80
|
+
|
|
81
|
+
transforms via CB32 to:
|
|
82
|
+
|
|
83
|
+
MFID: `0swqzb3a1sthv000xd8kta0vrw`
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
## Shortened MFID
|
|
90
|
+
|
|
91
|
+
For cases when microsecond time-based collisions are unlikely, we can often shorten the MFID to the first 13 characters, skipping the version ID and random portions of the UUIDv7:
|
|
92
|
+
|
|
93
|
+
Example: `0swqzb3a1sthv`
|
|
94
|
+
|
|
95
|
+
## Examples
|
|
96
|
+
|
|
97
|
+
### New Years Day for years!
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
for yr in [2023,2024,2025, 2030, 2038, 2040, 2200, 4100]:
|
|
101
|
+
x = datetime(yr, 1,1,0,0,0)
|
|
102
|
+
ns = int(x.replace(tzinfo=timezone.utc).timestamp()*10**9)
|
|
103
|
+
print(yr, mfid(ns))
|
|
104
|
+
...
|
|
105
|
+
2023 ('0rxgsm0001r010006jjjm8t8ng', UUID('063b0cd0-0000-7000-8000-34a52a2348ac'))
|
|
106
|
+
2024 ('0scj020001r01000w7rcx2j4hw', UUID('06592008-0000-7000-8000-e1f0ce8a448f'))
|
|
107
|
+
2025 ('0svmgp0001r010007b7cwx3jz8', UUID('06774858-0000-7000-8000-3acece7472fa'))
|
|
108
|
+
2030 ('0w6vv20001r01000dhzrn9hpyw', UUID('070dbd88-0000-7000-8000-6c7f8aa636f7'))
|
|
109
|
+
2038 ('0zz82y0001r010006ekbn2c3w8', UUID('07fe8178-0000-7000-8000-33a6ba8983e2'))
|
|
110
|
+
2040 ('10xaft0001r01000qwbh0mmxvm', UUID('083aa7e8-0000-7000-8000-bf1710529ddd'))
|
|
111
|
+
2200 ('3c4y340001r010000ha7kk1pj4', UUID('1b09e190-0000-7000-8000-045479cc3691'))
|
|
112
|
+
4100 ('z9k82t0001r0100006969dz8ec', UUID('fa668168-0000-7000-8000-019264b7e873'))
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Second timestamps in the first 8 characters
|
|
116
|
+
```python
|
|
117
|
+
for seconds in range(10):
|
|
118
|
+
x = datetime(2025,1,27, 10,42,seconds)
|
|
119
|
+
ns = int(x.replace(tzinfo=timezone.utc).timestamp()*10**9)
|
|
120
|
+
print(seconds, mfid(ns))
|
|
121
|
+
...
|
|
122
|
+
0 ('0swqcbw001s3q000gsrdyz9378', UUID('0679762f-8000-723b-8000-8670df7d233a'))
|
|
123
|
+
1 ('0swqcbwg01s3q000bqmjy1mtw8', UUID('0679762f-9000-723b-8000-5de92f069ae2'))
|
|
124
|
+
2 ('0swqcbx001s3q0001hhksjsvy4', UUID('0679762f-a000-723b-8000-0c633ccb3bf1'))
|
|
125
|
+
3 ('0swqcbxg01s3q000sbvrwte3nm', UUID('0679762f-b000-723b-8000-caf78e69c3ad'))
|
|
126
|
+
4 ('0swqcby001s3q0009egs4xa0bg', UUID('0679762f-c000-723b-8000-4ba19275405c'))
|
|
127
|
+
5 ('0swqcbyg01s3q00015edb5wwnm', UUID('0679762f-d000-723b-8000-095cd5979cad'))
|
|
128
|
+
6 ('0swqcbz001s3q000acpnac54s0', UUID('0679762f-e000-723b-8000-532d5530a4c8'))
|
|
129
|
+
7 ('0swqcbzg01s3q000wvqaa411sw', UUID('0679762f-f000-723b-8000-e6eea51021cf'))
|
|
130
|
+
8 ('0swqcc0001s3q000vp989v3gfg', UUID('06797630-0000-723b-8000-dd9284ec707c'))
|
|
131
|
+
9 ('0swqcc0g01s3q000p7x2a1ez28', UUID('06797630-1000-723b-8000-b1fa2505df12'))
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Microsecond representation in the first 13 characters
|
|
135
|
+
```python
|
|
136
|
+
for microseconds in range(10):
|
|
137
|
+
x = datetime(2025,1,27, 10,42,23, microsecond=microseconds)
|
|
138
|
+
ns = int(x.replace(tzinfo=timezone.utc).timestamp()*10**9)
|
|
139
|
+
print(microseconds, mfid(ns))
|
|
140
|
+
...
|
|
141
|
+
0 ('0swqcc7g01r01000307p2d6p5r', UUID('06797630-f000-7000-8000-180f6134d62e'))
|
|
142
|
+
1 ('0swqcc7g01r1300068qb5q6a0m', UUID('06797630-f000-7011-8000-322eb2dcca05'))
|
|
143
|
+
2 ('0swqcc7g01r1x000qkqafxsw2r', UUID('06797630-f000-701e-8000-bceea7f73c16'))
|
|
144
|
+
3 ('0swqcc7g01r37000sbxpjk39er', UUID('06797630-f000-7033-8000-cafb694c6976'))
|
|
145
|
+
4 ('0swqcc7g01r49000ezrr6jbtt4', UUID('06797630-f000-7044-8000-77f183497ad1'))
|
|
146
|
+
5 ('0swqcc7g01r5b0009rec26p9g8', UUID('06797630-f000-7055-8000-4e1cc11ac982'))
|
|
147
|
+
6 ('0swqcc7g01r650004bs8egfwrr', UUID('06797630-f000-7062-8000-22f28741fcc6'))
|
|
148
|
+
7 ('0swqcc7g01r770006k9jrrgp2m', UUID('06797630-f000-7073-8000-34d32c621615'))
|
|
149
|
+
8 ('0swqcc7g01r8k000vvctva7eem', UUID('06797630-f000-7089-8000-ded9ada8ee75'))
|
|
150
|
+
9 ('0swqcc7g01r9d000j21jksndv0', UUID('06797630-f000-7096-8000-908329e6add8'))
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Randomness helps with identical timestamps
|
|
154
|
+
```python
|
|
155
|
+
for i in range(10):
|
|
156
|
+
x = datetime(2025,1,27, 10,42,23,563)
|
|
157
|
+
ns = int(x.replace(tzinfo=timezone.utc).timestamp()*10**9)
|
|
158
|
+
print(i, mfid(ns))
|
|
159
|
+
...
|
|
160
|
+
0 ('0swqcc7g09te9000qzbx6hybcc', UUID('06797630-f002-74e4-8000-bfd7d347cb63'))
|
|
161
|
+
1 ('0swqcc7g09te9001n0pp3repmg', UUID('06797630-f002-74e4-8001-a82d61e1d6a4'))
|
|
162
|
+
2 ('0swqcc7g09te90021zgr40n04g', UUID('06797630-f002-74e4-8002-0fe18202a024'))
|
|
163
|
+
3 ('0swqcc7g09te9003a04nkksdgg', UUID('06797630-f002-74e4-8003-500959cf2d84'))
|
|
164
|
+
4 ('0swqcc7g09te9004ab86st4jz8', UUID('06797630-f002-74e4-8004-52d06ce892fa'))
|
|
165
|
+
5 ('0swqcc7g09te9005p5mqczg7ac', UUID('06797630-f002-74e4-8005-b169767e0753'))
|
|
166
|
+
6 ('0swqcc7g09te9006ncvx7ht68c', UUID('06797630-f002-74e4-8006-ab37d3c74643'))
|
|
167
|
+
7 ('0swqcc7g09te9007kkw28mqk9r', UUID('06797630-f002-74e4-8007-9cf82452f34e'))
|
|
168
|
+
8 ('0swqcc7g09te9008xhrrs70qh0', UUID('06797630-f002-74e4-8008-ec718c9c1788'))
|
|
169
|
+
9 ('0swqcc7g09te90090knvrvb0gc', UUID('06797630-f002-74e4-8009-04ebbc6d6083'))
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Python implementation
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
```console
|
|
176
|
+
$ pip install mfid
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
from mfid import mfid
|
|
181
|
+
mfid_str, uuid_obj = mfid()
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
```pycon
|
|
185
|
+
>>> import mfid
|
|
186
|
+
>>> mfid.mfid()
|
|
187
|
+
('0sx3p4n631xck000vvs2ecrarc', UUID('067a3b12-a618-7ac9-8000-def227330ac3'))
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
The function `mfid()`creates a 26 character encoded string based on lowercase Crockford's Base32 encoding of a UUID.
|
|
191
|
+
Uses a time sequential UUIDv7 if available, otherwise create a random UUIDv4. It returns a tuple of mfid string and the associated UUID object.
|
|
192
|
+
|
|
193
|
+
Note that the python standard library does not include a UUIDv7 generator yet, so we rely on the [`uuidv7`](https://pypi.org/project/uuid7/) package for UUID generation. MFID will fallback to UUIDv4 (fully random) if UUIDv7 is unavailable.
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
## Author
|
|
197
|
+
|
|
198
|
+
Edward S. Barnard <esbarnard@lbl.gov>
|
mfid-1.0.0/README.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# MFID: a Mighty Fine Identifier
|
|
2
|
+
|
|
3
|
+
A compact universal persistent identifier is useful in many contexts,
|
|
4
|
+
especially in scientific and engineering disiplines that happen distributed around the world.
|
|
5
|
+
|
|
6
|
+
We would like to uniquely identify data sets, samples, without coordinating the generation of such identifiers.
|
|
7
|
+
|
|
8
|
+
Guiding principles for creating an identifier scheme:
|
|
9
|
+
|
|
10
|
+
- Global uniqueness
|
|
11
|
+
- Compact
|
|
12
|
+
- Human readable/typeable
|
|
13
|
+
- Lexicographically sortable (by time)
|
|
14
|
+
- Used as a filename (limits on filesystems: case-sensitivity, length, allowable characters)
|
|
15
|
+
- Use existing standards as much as possible
|
|
16
|
+
|
|
17
|
+
**Short TL;DR:** MFID is a UUIDv7 + Crockford's Base32 representation. MFID gives a standards compliant timestamp-based compact universally unique identifier.
|
|
18
|
+
|
|
19
|
+
**An example MFID: `0swqzb3a1sthv000xd8kta0vrw`**
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
## Using existing standards
|
|
23
|
+
|
|
24
|
+
MFID is based on the UUIDv7 standard. UUID's are [RFC standardized](https://datatracker.ietf.org/doc/rfc9562/) "universal" identifiers. UUIDs have are 128-bit numbers with a specifc form, including randomly generated sections.
|
|
25
|
+
128 bits enough for every grain of sand on earth to have 10<sup>20</sup> UUIDs.
|
|
26
|
+
Therefore, collisions are extremely unlikely, so we can create UUIDs without checking a central database.
|
|
27
|
+
|
|
28
|
+
UUIDs are cannonically represented as a hexdecimal string with `-` seperators. This ends up giving you a 36 character representation. For Example: `064dfc00-f4e6-71ae-8000-d890eded3ecd`. MFID uses the UUIDv7 unqiue indentifier, but packs it into a more space efficent manner for use in labelling data and physical objects (See Compact Representation section below).
|
|
29
|
+
|
|
30
|
+
UUIDs v7 (part of the 2024 version of the RFC standard) has an interesting and useful property: Leading XX bits are time ordered and represent a timestamp of creation. This means that to the millisecond time-scale UUIDv7s are lexicographically by time. The rest of the UUIDv7 bits encode randomness, avoiding collision issues.
|
|
31
|
+
|
|
32
|
+
### Anatomy of a UUIDv7 (borrowed from python package `uuidv7`):
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
0 1 2 3
|
|
36
|
+
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
37
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
38
|
+
t1 | unixts (secs since epoch) |
|
|
39
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
40
|
+
t2/t3 |unixts | frac secs (12 bits) | ver | frac secs (12 bits) |
|
|
41
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
42
|
+
t4/rand |var| seq (14 bits) | rand (16 bits) |
|
|
43
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
44
|
+
rand | rand (32 bits) |
|
|
45
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
Other non-RFC standards exist and have inspired MFID. These schemes handle many of our needs, but not all:
|
|
50
|
+
|
|
51
|
+
[**ULID**](https://github.com/ulid/spec): Handles most of the needs, but is not convertable to and from a valid RFC-defined UUID. Inspired our use of Crockerfords Base32 representation.
|
|
52
|
+
|
|
53
|
+
[**NanoID**](https://github.com/ai/nanoid): A nice compact global identifier, but random and time sorted.
|
|
54
|
+
|
|
55
|
+
## Compact representation
|
|
56
|
+
|
|
57
|
+
[Crockford’s Base32](https://www.crockford.com/base32.html) encoding scheme can take the 128bit UUIDv7 and its 36 character hexdecimal representation and compactly present the same information in 26 alphanumeric characters (09,a-z).
|
|
58
|
+
|
|
59
|
+
UUIDv7: `06797fac-6a0e-751d-8000-eb513d281bc7`
|
|
60
|
+
|
|
61
|
+
transforms via CB32 to:
|
|
62
|
+
|
|
63
|
+
MFID: `0swqzb3a1sthv000xd8kta0vrw`
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
## Shortened MFID
|
|
70
|
+
|
|
71
|
+
For cases when microsecond time-based collisions are unlikely, we can often shorten the MFID to the first 13 characters, skipping the version ID and random portions of the UUIDv7:
|
|
72
|
+
|
|
73
|
+
Example: `0swqzb3a1sthv`
|
|
74
|
+
|
|
75
|
+
## Examples
|
|
76
|
+
|
|
77
|
+
### New Years Day for years!
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
for yr in [2023,2024,2025, 2030, 2038, 2040, 2200, 4100]:
|
|
81
|
+
x = datetime(yr, 1,1,0,0,0)
|
|
82
|
+
ns = int(x.replace(tzinfo=timezone.utc).timestamp()*10**9)
|
|
83
|
+
print(yr, mfid(ns))
|
|
84
|
+
...
|
|
85
|
+
2023 ('0rxgsm0001r010006jjjm8t8ng', UUID('063b0cd0-0000-7000-8000-34a52a2348ac'))
|
|
86
|
+
2024 ('0scj020001r01000w7rcx2j4hw', UUID('06592008-0000-7000-8000-e1f0ce8a448f'))
|
|
87
|
+
2025 ('0svmgp0001r010007b7cwx3jz8', UUID('06774858-0000-7000-8000-3acece7472fa'))
|
|
88
|
+
2030 ('0w6vv20001r01000dhzrn9hpyw', UUID('070dbd88-0000-7000-8000-6c7f8aa636f7'))
|
|
89
|
+
2038 ('0zz82y0001r010006ekbn2c3w8', UUID('07fe8178-0000-7000-8000-33a6ba8983e2'))
|
|
90
|
+
2040 ('10xaft0001r01000qwbh0mmxvm', UUID('083aa7e8-0000-7000-8000-bf1710529ddd'))
|
|
91
|
+
2200 ('3c4y340001r010000ha7kk1pj4', UUID('1b09e190-0000-7000-8000-045479cc3691'))
|
|
92
|
+
4100 ('z9k82t0001r0100006969dz8ec', UUID('fa668168-0000-7000-8000-019264b7e873'))
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Second timestamps in the first 8 characters
|
|
96
|
+
```python
|
|
97
|
+
for seconds in range(10):
|
|
98
|
+
x = datetime(2025,1,27, 10,42,seconds)
|
|
99
|
+
ns = int(x.replace(tzinfo=timezone.utc).timestamp()*10**9)
|
|
100
|
+
print(seconds, mfid(ns))
|
|
101
|
+
...
|
|
102
|
+
0 ('0swqcbw001s3q000gsrdyz9378', UUID('0679762f-8000-723b-8000-8670df7d233a'))
|
|
103
|
+
1 ('0swqcbwg01s3q000bqmjy1mtw8', UUID('0679762f-9000-723b-8000-5de92f069ae2'))
|
|
104
|
+
2 ('0swqcbx001s3q0001hhksjsvy4', UUID('0679762f-a000-723b-8000-0c633ccb3bf1'))
|
|
105
|
+
3 ('0swqcbxg01s3q000sbvrwte3nm', UUID('0679762f-b000-723b-8000-caf78e69c3ad'))
|
|
106
|
+
4 ('0swqcby001s3q0009egs4xa0bg', UUID('0679762f-c000-723b-8000-4ba19275405c'))
|
|
107
|
+
5 ('0swqcbyg01s3q00015edb5wwnm', UUID('0679762f-d000-723b-8000-095cd5979cad'))
|
|
108
|
+
6 ('0swqcbz001s3q000acpnac54s0', UUID('0679762f-e000-723b-8000-532d5530a4c8'))
|
|
109
|
+
7 ('0swqcbzg01s3q000wvqaa411sw', UUID('0679762f-f000-723b-8000-e6eea51021cf'))
|
|
110
|
+
8 ('0swqcc0001s3q000vp989v3gfg', UUID('06797630-0000-723b-8000-dd9284ec707c'))
|
|
111
|
+
9 ('0swqcc0g01s3q000p7x2a1ez28', UUID('06797630-1000-723b-8000-b1fa2505df12'))
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Microsecond representation in the first 13 characters
|
|
115
|
+
```python
|
|
116
|
+
for microseconds in range(10):
|
|
117
|
+
x = datetime(2025,1,27, 10,42,23, microsecond=microseconds)
|
|
118
|
+
ns = int(x.replace(tzinfo=timezone.utc).timestamp()*10**9)
|
|
119
|
+
print(microseconds, mfid(ns))
|
|
120
|
+
...
|
|
121
|
+
0 ('0swqcc7g01r01000307p2d6p5r', UUID('06797630-f000-7000-8000-180f6134d62e'))
|
|
122
|
+
1 ('0swqcc7g01r1300068qb5q6a0m', UUID('06797630-f000-7011-8000-322eb2dcca05'))
|
|
123
|
+
2 ('0swqcc7g01r1x000qkqafxsw2r', UUID('06797630-f000-701e-8000-bceea7f73c16'))
|
|
124
|
+
3 ('0swqcc7g01r37000sbxpjk39er', UUID('06797630-f000-7033-8000-cafb694c6976'))
|
|
125
|
+
4 ('0swqcc7g01r49000ezrr6jbtt4', UUID('06797630-f000-7044-8000-77f183497ad1'))
|
|
126
|
+
5 ('0swqcc7g01r5b0009rec26p9g8', UUID('06797630-f000-7055-8000-4e1cc11ac982'))
|
|
127
|
+
6 ('0swqcc7g01r650004bs8egfwrr', UUID('06797630-f000-7062-8000-22f28741fcc6'))
|
|
128
|
+
7 ('0swqcc7g01r770006k9jrrgp2m', UUID('06797630-f000-7073-8000-34d32c621615'))
|
|
129
|
+
8 ('0swqcc7g01r8k000vvctva7eem', UUID('06797630-f000-7089-8000-ded9ada8ee75'))
|
|
130
|
+
9 ('0swqcc7g01r9d000j21jksndv0', UUID('06797630-f000-7096-8000-908329e6add8'))
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Randomness helps with identical timestamps
|
|
134
|
+
```python
|
|
135
|
+
for i in range(10):
|
|
136
|
+
x = datetime(2025,1,27, 10,42,23,563)
|
|
137
|
+
ns = int(x.replace(tzinfo=timezone.utc).timestamp()*10**9)
|
|
138
|
+
print(i, mfid(ns))
|
|
139
|
+
...
|
|
140
|
+
0 ('0swqcc7g09te9000qzbx6hybcc', UUID('06797630-f002-74e4-8000-bfd7d347cb63'))
|
|
141
|
+
1 ('0swqcc7g09te9001n0pp3repmg', UUID('06797630-f002-74e4-8001-a82d61e1d6a4'))
|
|
142
|
+
2 ('0swqcc7g09te90021zgr40n04g', UUID('06797630-f002-74e4-8002-0fe18202a024'))
|
|
143
|
+
3 ('0swqcc7g09te9003a04nkksdgg', UUID('06797630-f002-74e4-8003-500959cf2d84'))
|
|
144
|
+
4 ('0swqcc7g09te9004ab86st4jz8', UUID('06797630-f002-74e4-8004-52d06ce892fa'))
|
|
145
|
+
5 ('0swqcc7g09te9005p5mqczg7ac', UUID('06797630-f002-74e4-8005-b169767e0753'))
|
|
146
|
+
6 ('0swqcc7g09te9006ncvx7ht68c', UUID('06797630-f002-74e4-8006-ab37d3c74643'))
|
|
147
|
+
7 ('0swqcc7g09te9007kkw28mqk9r', UUID('06797630-f002-74e4-8007-9cf82452f34e'))
|
|
148
|
+
8 ('0swqcc7g09te9008xhrrs70qh0', UUID('06797630-f002-74e4-8008-ec718c9c1788'))
|
|
149
|
+
9 ('0swqcc7g09te90090knvrvb0gc', UUID('06797630-f002-74e4-8009-04ebbc6d6083'))
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Python implementation
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
```console
|
|
156
|
+
$ pip install mfid
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from mfid import mfid
|
|
161
|
+
mfid_str, uuid_obj = mfid()
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
```pycon
|
|
165
|
+
>>> import mfid
|
|
166
|
+
>>> mfid.mfid()
|
|
167
|
+
('0sx3p4n631xck000vvs2ecrarc', UUID('067a3b12-a618-7ac9-8000-def227330ac3'))
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The function `mfid()`creates a 26 character encoded string based on lowercase Crockford's Base32 encoding of a UUID.
|
|
171
|
+
Uses a time sequential UUIDv7 if available, otherwise create a random UUIDv4. It returns a tuple of mfid string and the associated UUID object.
|
|
172
|
+
|
|
173
|
+
Note that the python standard library does not include a UUIDv7 generator yet, so we rely on the [`uuidv7`](https://pypi.org/project/uuid7/) package for UUID generation. MFID will fallback to UUIDv4 (fully random) if UUIDv7 is unavailable.
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
## Author
|
|
177
|
+
|
|
178
|
+
Edward S. Barnard <esbarnard@lbl.gov>
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: mfid
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: MFID: a Mighty Fine Identifier
|
|
5
|
+
Home-page: https://github.com/MolecularFoundry/mfid
|
|
6
|
+
Author: Edward S. Barnard
|
|
7
|
+
Author-email: esbarnard@lbl.gov
|
|
8
|
+
License: BSD
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: uuid7
|
|
12
|
+
Dynamic: author
|
|
13
|
+
Dynamic: author-email
|
|
14
|
+
Dynamic: description
|
|
15
|
+
Dynamic: description-content-type
|
|
16
|
+
Dynamic: home-page
|
|
17
|
+
Dynamic: license
|
|
18
|
+
Dynamic: requires-dist
|
|
19
|
+
Dynamic: summary
|
|
20
|
+
|
|
21
|
+
# MFID: a Mighty Fine Identifier
|
|
22
|
+
|
|
23
|
+
A compact universal persistent identifier is useful in many contexts,
|
|
24
|
+
especially in scientific and engineering disiplines that happen distributed around the world.
|
|
25
|
+
|
|
26
|
+
We would like to uniquely identify data sets, samples, without coordinating the generation of such identifiers.
|
|
27
|
+
|
|
28
|
+
Guiding principles for creating an identifier scheme:
|
|
29
|
+
|
|
30
|
+
- Global uniqueness
|
|
31
|
+
- Compact
|
|
32
|
+
- Human readable/typeable
|
|
33
|
+
- Lexicographically sortable (by time)
|
|
34
|
+
- Used as a filename (limits on filesystems: case-sensitivity, length, allowable characters)
|
|
35
|
+
- Use existing standards as much as possible
|
|
36
|
+
|
|
37
|
+
**Short TL;DR:** MFID is a UUIDv7 + Crockford's Base32 representation. MFID gives a standards compliant timestamp-based compact universally unique identifier.
|
|
38
|
+
|
|
39
|
+
**An example MFID: `0swqzb3a1sthv000xd8kta0vrw`**
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
## Using existing standards
|
|
43
|
+
|
|
44
|
+
MFID is based on the UUIDv7 standard. UUID's are [RFC standardized](https://datatracker.ietf.org/doc/rfc9562/) "universal" identifiers. UUIDs have are 128-bit numbers with a specifc form, including randomly generated sections.
|
|
45
|
+
128 bits enough for every grain of sand on earth to have 10<sup>20</sup> UUIDs.
|
|
46
|
+
Therefore, collisions are extremely unlikely, so we can create UUIDs without checking a central database.
|
|
47
|
+
|
|
48
|
+
UUIDs are cannonically represented as a hexdecimal string with `-` seperators. This ends up giving you a 36 character representation. For Example: `064dfc00-f4e6-71ae-8000-d890eded3ecd`. MFID uses the UUIDv7 unqiue indentifier, but packs it into a more space efficent manner for use in labelling data and physical objects (See Compact Representation section below).
|
|
49
|
+
|
|
50
|
+
UUIDs v7 (part of the 2024 version of the RFC standard) has an interesting and useful property: Leading XX bits are time ordered and represent a timestamp of creation. This means that to the millisecond time-scale UUIDv7s are lexicographically by time. The rest of the UUIDv7 bits encode randomness, avoiding collision issues.
|
|
51
|
+
|
|
52
|
+
### Anatomy of a UUIDv7 (borrowed from python package `uuidv7`):
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
0 1 2 3
|
|
56
|
+
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
57
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
58
|
+
t1 | unixts (secs since epoch) |
|
|
59
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
60
|
+
t2/t3 |unixts | frac secs (12 bits) | ver | frac secs (12 bits) |
|
|
61
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
62
|
+
t4/rand |var| seq (14 bits) | rand (16 bits) |
|
|
63
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
64
|
+
rand | rand (32 bits) |
|
|
65
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
Other non-RFC standards exist and have inspired MFID. These schemes handle many of our needs, but not all:
|
|
70
|
+
|
|
71
|
+
[**ULID**](https://github.com/ulid/spec): Handles most of the needs, but is not convertable to and from a valid RFC-defined UUID. Inspired our use of Crockerfords Base32 representation.
|
|
72
|
+
|
|
73
|
+
[**NanoID**](https://github.com/ai/nanoid): A nice compact global identifier, but random and time sorted.
|
|
74
|
+
|
|
75
|
+
## Compact representation
|
|
76
|
+
|
|
77
|
+
[Crockford’s Base32](https://www.crockford.com/base32.html) encoding scheme can take the 128bit UUIDv7 and its 36 character hexdecimal representation and compactly present the same information in 26 alphanumeric characters (09,a-z).
|
|
78
|
+
|
|
79
|
+
UUIDv7: `06797fac-6a0e-751d-8000-eb513d281bc7`
|
|
80
|
+
|
|
81
|
+
transforms via CB32 to:
|
|
82
|
+
|
|
83
|
+
MFID: `0swqzb3a1sthv000xd8kta0vrw`
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
## Shortened MFID
|
|
90
|
+
|
|
91
|
+
For cases when microsecond time-based collisions are unlikely, we can often shorten the MFID to the first 13 characters, skipping the version ID and random portions of the UUIDv7:
|
|
92
|
+
|
|
93
|
+
Example: `0swqzb3a1sthv`
|
|
94
|
+
|
|
95
|
+
## Examples
|
|
96
|
+
|
|
97
|
+
### New Years Day for years!
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
for yr in [2023,2024,2025, 2030, 2038, 2040, 2200, 4100]:
|
|
101
|
+
x = datetime(yr, 1,1,0,0,0)
|
|
102
|
+
ns = int(x.replace(tzinfo=timezone.utc).timestamp()*10**9)
|
|
103
|
+
print(yr, mfid(ns))
|
|
104
|
+
...
|
|
105
|
+
2023 ('0rxgsm0001r010006jjjm8t8ng', UUID('063b0cd0-0000-7000-8000-34a52a2348ac'))
|
|
106
|
+
2024 ('0scj020001r01000w7rcx2j4hw', UUID('06592008-0000-7000-8000-e1f0ce8a448f'))
|
|
107
|
+
2025 ('0svmgp0001r010007b7cwx3jz8', UUID('06774858-0000-7000-8000-3acece7472fa'))
|
|
108
|
+
2030 ('0w6vv20001r01000dhzrn9hpyw', UUID('070dbd88-0000-7000-8000-6c7f8aa636f7'))
|
|
109
|
+
2038 ('0zz82y0001r010006ekbn2c3w8', UUID('07fe8178-0000-7000-8000-33a6ba8983e2'))
|
|
110
|
+
2040 ('10xaft0001r01000qwbh0mmxvm', UUID('083aa7e8-0000-7000-8000-bf1710529ddd'))
|
|
111
|
+
2200 ('3c4y340001r010000ha7kk1pj4', UUID('1b09e190-0000-7000-8000-045479cc3691'))
|
|
112
|
+
4100 ('z9k82t0001r0100006969dz8ec', UUID('fa668168-0000-7000-8000-019264b7e873'))
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Second timestamps in the first 8 characters
|
|
116
|
+
```python
|
|
117
|
+
for seconds in range(10):
|
|
118
|
+
x = datetime(2025,1,27, 10,42,seconds)
|
|
119
|
+
ns = int(x.replace(tzinfo=timezone.utc).timestamp()*10**9)
|
|
120
|
+
print(seconds, mfid(ns))
|
|
121
|
+
...
|
|
122
|
+
0 ('0swqcbw001s3q000gsrdyz9378', UUID('0679762f-8000-723b-8000-8670df7d233a'))
|
|
123
|
+
1 ('0swqcbwg01s3q000bqmjy1mtw8', UUID('0679762f-9000-723b-8000-5de92f069ae2'))
|
|
124
|
+
2 ('0swqcbx001s3q0001hhksjsvy4', UUID('0679762f-a000-723b-8000-0c633ccb3bf1'))
|
|
125
|
+
3 ('0swqcbxg01s3q000sbvrwte3nm', UUID('0679762f-b000-723b-8000-caf78e69c3ad'))
|
|
126
|
+
4 ('0swqcby001s3q0009egs4xa0bg', UUID('0679762f-c000-723b-8000-4ba19275405c'))
|
|
127
|
+
5 ('0swqcbyg01s3q00015edb5wwnm', UUID('0679762f-d000-723b-8000-095cd5979cad'))
|
|
128
|
+
6 ('0swqcbz001s3q000acpnac54s0', UUID('0679762f-e000-723b-8000-532d5530a4c8'))
|
|
129
|
+
7 ('0swqcbzg01s3q000wvqaa411sw', UUID('0679762f-f000-723b-8000-e6eea51021cf'))
|
|
130
|
+
8 ('0swqcc0001s3q000vp989v3gfg', UUID('06797630-0000-723b-8000-dd9284ec707c'))
|
|
131
|
+
9 ('0swqcc0g01s3q000p7x2a1ez28', UUID('06797630-1000-723b-8000-b1fa2505df12'))
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Microsecond representation in the first 13 characters
|
|
135
|
+
```python
|
|
136
|
+
for microseconds in range(10):
|
|
137
|
+
x = datetime(2025,1,27, 10,42,23, microsecond=microseconds)
|
|
138
|
+
ns = int(x.replace(tzinfo=timezone.utc).timestamp()*10**9)
|
|
139
|
+
print(microseconds, mfid(ns))
|
|
140
|
+
...
|
|
141
|
+
0 ('0swqcc7g01r01000307p2d6p5r', UUID('06797630-f000-7000-8000-180f6134d62e'))
|
|
142
|
+
1 ('0swqcc7g01r1300068qb5q6a0m', UUID('06797630-f000-7011-8000-322eb2dcca05'))
|
|
143
|
+
2 ('0swqcc7g01r1x000qkqafxsw2r', UUID('06797630-f000-701e-8000-bceea7f73c16'))
|
|
144
|
+
3 ('0swqcc7g01r37000sbxpjk39er', UUID('06797630-f000-7033-8000-cafb694c6976'))
|
|
145
|
+
4 ('0swqcc7g01r49000ezrr6jbtt4', UUID('06797630-f000-7044-8000-77f183497ad1'))
|
|
146
|
+
5 ('0swqcc7g01r5b0009rec26p9g8', UUID('06797630-f000-7055-8000-4e1cc11ac982'))
|
|
147
|
+
6 ('0swqcc7g01r650004bs8egfwrr', UUID('06797630-f000-7062-8000-22f28741fcc6'))
|
|
148
|
+
7 ('0swqcc7g01r770006k9jrrgp2m', UUID('06797630-f000-7073-8000-34d32c621615'))
|
|
149
|
+
8 ('0swqcc7g01r8k000vvctva7eem', UUID('06797630-f000-7089-8000-ded9ada8ee75'))
|
|
150
|
+
9 ('0swqcc7g01r9d000j21jksndv0', UUID('06797630-f000-7096-8000-908329e6add8'))
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Randomness helps with identical timestamps
|
|
154
|
+
```python
|
|
155
|
+
for i in range(10):
|
|
156
|
+
x = datetime(2025,1,27, 10,42,23,563)
|
|
157
|
+
ns = int(x.replace(tzinfo=timezone.utc).timestamp()*10**9)
|
|
158
|
+
print(i, mfid(ns))
|
|
159
|
+
...
|
|
160
|
+
0 ('0swqcc7g09te9000qzbx6hybcc', UUID('06797630-f002-74e4-8000-bfd7d347cb63'))
|
|
161
|
+
1 ('0swqcc7g09te9001n0pp3repmg', UUID('06797630-f002-74e4-8001-a82d61e1d6a4'))
|
|
162
|
+
2 ('0swqcc7g09te90021zgr40n04g', UUID('06797630-f002-74e4-8002-0fe18202a024'))
|
|
163
|
+
3 ('0swqcc7g09te9003a04nkksdgg', UUID('06797630-f002-74e4-8003-500959cf2d84'))
|
|
164
|
+
4 ('0swqcc7g09te9004ab86st4jz8', UUID('06797630-f002-74e4-8004-52d06ce892fa'))
|
|
165
|
+
5 ('0swqcc7g09te9005p5mqczg7ac', UUID('06797630-f002-74e4-8005-b169767e0753'))
|
|
166
|
+
6 ('0swqcc7g09te9006ncvx7ht68c', UUID('06797630-f002-74e4-8006-ab37d3c74643'))
|
|
167
|
+
7 ('0swqcc7g09te9007kkw28mqk9r', UUID('06797630-f002-74e4-8007-9cf82452f34e'))
|
|
168
|
+
8 ('0swqcc7g09te9008xhrrs70qh0', UUID('06797630-f002-74e4-8008-ec718c9c1788'))
|
|
169
|
+
9 ('0swqcc7g09te90090knvrvb0gc', UUID('06797630-f002-74e4-8009-04ebbc6d6083'))
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Python implementation
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
```console
|
|
176
|
+
$ pip install mfid
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
from mfid import mfid
|
|
181
|
+
mfid_str, uuid_obj = mfid()
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
```pycon
|
|
185
|
+
>>> import mfid
|
|
186
|
+
>>> mfid.mfid()
|
|
187
|
+
('0sx3p4n631xck000vvs2ecrarc', UUID('067a3b12-a618-7ac9-8000-def227330ac3'))
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
The function `mfid()`creates a 26 character encoded string based on lowercase Crockford's Base32 encoding of a UUID.
|
|
191
|
+
Uses a time sequential UUIDv7 if available, otherwise create a random UUIDv4. It returns a tuple of mfid string and the associated UUID object.
|
|
192
|
+
|
|
193
|
+
Note that the python standard library does not include a UUIDv7 generator yet, so we rely on the [`uuidv7`](https://pypi.org/project/uuid7/) package for UUID generation. MFID will fallback to UUIDv4 (fully random) if UUIDv7 is unavailable.
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
## Author
|
|
197
|
+
|
|
198
|
+
Edward S. Barnard <esbarnard@lbl.gov>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
uuid7
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mfid
|
mfid-1.0.0/mfid.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
# MFID: a Mighty Fine Identifier
|
|
3
|
+
|
|
4
|
+
MFID gives a standards compliant timestamp-based compact
|
|
5
|
+
universally unique identifier.
|
|
6
|
+
MFID is a UUIDv7 + Crockford's Base32 representation.
|
|
7
|
+
|
|
8
|
+
**An example MFID: `0swqzb3a1sthv000xd8kta0vrw`**
|
|
9
|
+
|
|
10
|
+
Author: Edward S. Barnard <esbarnard@lbl.gov>
|
|
11
|
+
License: BSD
|
|
12
|
+
URL: https://github.com/MolecularFoundry/mfid
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
import uuid
|
|
17
|
+
import base64 # part of standard library
|
|
18
|
+
|
|
19
|
+
b32_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
|
|
20
|
+
cb32_alphabet = "0123456789abcdefghjkmnpqrstvwxyz"
|
|
21
|
+
|
|
22
|
+
b32_to_cb32_map = dict(zip(b32_alphabet,cb32_alphabet))
|
|
23
|
+
|
|
24
|
+
def b32_to_cb32(x):
|
|
25
|
+
"""
|
|
26
|
+
Map a base32 encoded string (without padding)
|
|
27
|
+
to a Crockford's Base32 encoded string
|
|
28
|
+
|
|
29
|
+
https://www.crockford.com/base32.html
|
|
30
|
+
"""
|
|
31
|
+
return "".join(
|
|
32
|
+
[ b32_to_cb32_map[k] for k in x ]
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
def uuid_to_cb32(u):
|
|
36
|
+
"""Creates a 26 character encoded string based on lowercase
|
|
37
|
+
Crockford's Base32 encoding of the uuid.bytes
|
|
38
|
+
|
|
39
|
+
u: uuid object
|
|
40
|
+
returns string of Crockford's Base32
|
|
41
|
+
"""
|
|
42
|
+
# note we use typical base32 RFC 4648 alphabet in the base64
|
|
43
|
+
# standard library and then convert to Crockford's by alphabet mapping
|
|
44
|
+
b32 = base64.b32encode(u.bytes).decode().strip("=")
|
|
45
|
+
cb32 = b32_to_cb32(b32)
|
|
46
|
+
return cb32
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def cb32_uuid4(*args, **kwargs):
|
|
50
|
+
"""
|
|
51
|
+
Creates a 26 character encoded string based on lowercase
|
|
52
|
+
Crockford's Base32 (cb32) encoding of the uuid.bytes (UUIDv7)
|
|
53
|
+
|
|
54
|
+
Useful as a compact random, globally unique
|
|
55
|
+
identifier that can be used as a filename on most operating systems
|
|
56
|
+
|
|
57
|
+
returns cb32 string and UUIDv4 object
|
|
58
|
+
"""
|
|
59
|
+
u = uuid.uuid4(*args, **kwargs)
|
|
60
|
+
cb32 = uuid_to_cb32(u)
|
|
61
|
+
return cb32,u
|
|
62
|
+
|
|
63
|
+
def cb32_uuid7(*args, **kwargs):
|
|
64
|
+
"""
|
|
65
|
+
Creates a 26 character encoded string based on lowercase
|
|
66
|
+
Crockford's Base32 (cb32) encoding of the uuid.bytes (UUIDv7)
|
|
67
|
+
|
|
68
|
+
Useful as a compact time-based lexicographically-sortable
|
|
69
|
+
identifier that can be used as a filename on most operating systems
|
|
70
|
+
|
|
71
|
+
args and kwargs are passed to uuid7()
|
|
72
|
+
|
|
73
|
+
returns cb32 string and UUIDv7 object
|
|
74
|
+
"""
|
|
75
|
+
from uuid_extensions import uuid7 # pip install uuid7
|
|
76
|
+
|
|
77
|
+
u = uuid7(*args, **kwargs)
|
|
78
|
+
cb32 = uuid_to_cb32(u)
|
|
79
|
+
return cb32,u
|
|
80
|
+
|
|
81
|
+
def mfid(*args, **kwargs):
|
|
82
|
+
"""
|
|
83
|
+
Creates an MFID (a mighty fine identifier).
|
|
84
|
+
This is a 26 character encoded string based on lowercase
|
|
85
|
+
Crockford's Base32 encoding of a UUID.
|
|
86
|
+
Uses a time sequential UUIDv7 if available,
|
|
87
|
+
otherwise create a random UUIDv4
|
|
88
|
+
|
|
89
|
+
returns mfid string and associated UUID object as a tuple.
|
|
90
|
+
|
|
91
|
+
example:
|
|
92
|
+
>>> mfid.mfid()
|
|
93
|
+
('0sx3p4n631xck000vvs2ecrarc', UUID('067a3b12-a618-7ac9-8000-def227330ac3'))
|
|
94
|
+
"""
|
|
95
|
+
try:
|
|
96
|
+
return cb32_uuid7(*args, **kwargs)
|
|
97
|
+
except:
|
|
98
|
+
return cb32_uuid4(*args, **kwargs)
|
|
99
|
+
|
|
100
|
+
cb32_uuid = mfid
|
|
101
|
+
|
|
102
|
+
if __name__ == '__main__':
|
|
103
|
+
print(mfid())
|
mfid-1.0.0/setup.cfg
ADDED
mfid-1.0.0/setup.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from setuptools import setup
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name = 'mfid',
|
|
5
|
+
|
|
6
|
+
version = '1.0.0',
|
|
7
|
+
|
|
8
|
+
description = 'MFID: a Mighty Fine Identifier',
|
|
9
|
+
long_description =open('README.md', 'r').read(),
|
|
10
|
+
long_description_content_type='text/markdown',
|
|
11
|
+
|
|
12
|
+
# Author details
|
|
13
|
+
author='Edward S. Barnard',
|
|
14
|
+
author_email='esbarnard@lbl.gov',
|
|
15
|
+
|
|
16
|
+
# license
|
|
17
|
+
license='BSD',
|
|
18
|
+
|
|
19
|
+
url='https://github.com/MolecularFoundry/mfid',
|
|
20
|
+
|
|
21
|
+
py_modules=["mfid"],
|
|
22
|
+
|
|
23
|
+
install_requires=["uuid7"],
|
|
24
|
+
|
|
25
|
+
)
|