# ulid
[](https://travis-ci.org/ahawker/ulid)
[](https://ci.appveyor.com/project/ahawker/ulid/branch/master)
[](https://codecov.io/gh/ahawker/ulid)
[](https://codeclimate.com/github/ahawker/ulid)
[](https://codeclimate.com/github/ahawker/ulid)
[](https://badge.fury.io/py/ulid-py)
[](https://pypi.python.org/pypi/ulid-py)
[](https://pyup.io/repos/github/ahawker/ulid/)
[](http://ulid.readthedocs.io/en/latest/?badge=latest)
[Universally Unique Lexicographically Sortable Identifier](https://github.com/alizain/ulid) in [Python 3](https://www.python.org/).
### Status
This project is actively maintained.
### Installation
To install ulid from [pip](https://pypi.python.org/pypi/pip):
```bash
$ pip install ulid-py
```
To install ulid from source:
```bash
$ git clone git@github.com:ahawker/ulid.git
$ cd ulid && python setup.py install
```
### Usage
Create a brand new ULID.
The timestamp value (48-bits) is from [time.time()](https://docs.python.org/3/library/time.html?highlight=time.time#time.time) with millisecond precision.
The randomness value (80-bits) is from [os.urandom()](https://docs.python.org/3/library/os.html?highlight=os.urandom#os.urandom).
```python
>>> import ulid
>>> ulid.new()
<ULID('01BJQE4QTHMFP0S5J153XCFSP9')>
```
Create a new ULID from an existing 128-bit value, such as a [UUID](https://docs.python.org/3/library/uuid.html).
Supports ULID values as `int`, `bytes`, `str`, and `UUID` types.
```python
>>> import ulid, uuid
>>> value = uuid.uuid4()
>>> value
UUID('0983d0a2-ff15-4d83-8f37-7dd945b5aa39')
>>> ulid.from_uuid(value)
<ULID('09GF8A5ZRN9P1RYDVXV52VBAHS')>
```
Create a new ULID from an existing timestamp value, such as a [datetime](https://docs.python.org/3/library/datetime.html#module-datetime) object.
Supports timestamp values as `int`, `float`, `str`, `bytes`, `bytearray`, `memoryview`, `datetime`, `Timestamp`, and `ULID` types.
```python
>>> import datetime, ulid
>>> ulid.from_timestamp(datetime.datetime(1999, 1, 1))
<ULID('00TM9HX0008S220A3PWSFVNFEH')>
```
Create a new ULID from an existing randomness value.
Supports randomness values as `int`, `float`, `str`, `bytes`, `bytearray`, `memoryview`, `Randomness`, and `ULID` types.
```python
>>> import os, ulid
>>> randomness = os.urandom(10)
>>> ulid.from_randomness(randomness)
>>> <ULID('01BJQHX2XEDK0VN0GMYWT9JN8S')>
```
For cases when you don't necessarily control the data type (input from external system), you can use the `parse` method
which will attempt to make the correct determination for you. Please note that this will be slightly slower than creating
the instance from the respective `from_*` method as it needs to make a number of type/conditional checks.
Supports values as `int`, `float`, `str`, `bytes`, `bytearray`, `memoryview`, `uuid.UUID`, and `ULID` types.
```python
>>> import ulid
>>> value = db.model.get_id() ## Unsure about datatype -- Could be int, UUID, or string?
>>> ulid.parse(value)
>>> <ULID('0K0EDFETFM8SH912DBBD4ABXSZ')>
```
Once you have a ULID object, there are a number of ways to interact with it.
The `timestamp` method will give you a snapshot view of the first 48-bits of the ULID while the `randomness` method
will give you a snapshot of the last 80-bits.
```python
>>> import ulid
>>> u = ulid.new()
>>> u
<ULID('01BJQM7SC7D5VVTG3J68ABFQ3N')>
>>> u.timestamp()
<Timestamp('01BJQM7SC7')>
>>> u.randomness()
<Randomness('D5VVTG3J68ABFQ3N')>
```
The `ULID`, `Timestamp`, and `Randomness` classes all derive from the same base class, a `MemoryView`.
A `MemoryView` provides the `bin`, `bytes`, `hex`, `int`, `oct`, and `str`, methods for changing any values representation.
```python
>>> import ulid
>>> u = ulid.new()
>>> u
<ULID('01BJQMF54D093DXEAWZ6JYRPAQ')>
>>> u.timestamp()
<Timestamp('01BJQMF54D')>
>>> u.timestamp().int
1497589322893
>>> u.timestamp().bytes
b'\x01\\\xafG\x94\x8d'
>>> u.timestamp().datetime
datetime.datetime(2017, 6, 16, 5, 2, 2, 893000)
>>> u.randomness().bytes
b'\x02F\xde\xb9\\\xf9\xa5\xecYW'
>>> u.bytes[6:] == u.randomness().bytes
True
>>> u.str
'01BJQMF54D093DXEAWZ6JYRPAQ'
>>> u.int
1810474399624548315999517391436142935
>>> u.bin
'0b1010111001010111101000111100101001000110100000010010001101101111010111001010111001111100110100101111011000101100101010111'
>>> u.hex
'0x15caf47948d0246deb95cf9a5ec5957'
>>> u.oct
'0o12712750745106402215572712717464573054527'
```
A `MemoryView` also provides rich comparison functionality.
```python
>>> import datetime, time, ulid
>>> u1 = ulid.new()
>>> time.sleep(5)
>>> u2 = ulid.new()
>>> u1 < u2
True
>>> u3 = ulid.from_timestamp(datetime.datetime(2039, 1, 1))
>>> u1 < u2 < u3
True
>>> [u.timestamp().datetime for u in sorted([u2, u3, u1])]
[datetime.datetime(2017, 6, 16, 5, 7, 14, 847000), datetime.datetime(2017, 6, 16, 5, 7, 26, 775000), datetime.datetime(2039, 1, 1, 8, 0)]
```
### Future Items
* Collection of benchmarks to track performance.
* Backport to Python 2.7?
* See [Github Issues](https://github.com/ahawker/ulid/issues) for more!
### Goals
A fast implementation in pure python of the spec with binary format support.
### Contributing
If you would like to contribute, simply fork the repository, push your changes and send a pull request.
Pull requests will be brought into the `master` branch via a rebase and fast-forward merge with the goal of having a linear branch history with no merge commits.
### License
[Apache 2.0](LICENSE)
## Why not UUID?
UUID can be suboptimal for many uses-cases because:
* It isn't the most character efficient way of encoding 128 bits of randomness
* UUID v1/v2 is impractical in many environments, as it requires access to a unique, stable MAC address
* UUID v3/v5 requires a unique seed and produces randomly distributed IDs, which can cause fragmentation in many data structures
* UUID v4 provides no other information than randomness which can cause fragmentation in many data structures
ULID provides:
* 128-bit compatibility with UUID
* 1.21e+24 unique ULIDs per millisecond
* Lexicographically sortable!
* Canonically encoded as a 26 character string, as opposed to the 36 character UUID
* Uses Crockford's base32 for better efficiency and readability (5 bits per character)
* Case insensitive
* No special characters (URL safe)
## Specification
Below is the current specification of ULID as implemented in this repository.
The binary format is implemented.
```
01AN4Z07BY 79KA1307SR9X4MV3
|----------| |----------------|
Timestamp Randomness
10chars 16chars
48bits 80bits
```
### Components
**Timestamp**
* 48 bit integer
* UNIX-time in milliseconds
* Won't run out of space till the year 10895 AD.
**Randomness**
* 80 bits
* Cryptographically secure source of randomness, if possible
### Sorting
The left-most character must be sorted first, and the right-most character sorted last (lexical order).
The default ASCII character set must be used. Within the same millisecond, sort order is not guaranteed
### Encoding
Crockford's Base32 is used as shown. This alphabet excludes the letters I, L, O, and U to avoid confusion and abuse.
```
0123456789ABCDEFGHJKMNPQRSTVWXYZ
```
### Binary Layout and Byte Order
The components are encoded as 16 octets. Each component is encoded with the Most Significant Byte first (network byte order).
```
0 1 2 3
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 32_bit_uint_time_high |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 16_bit_uint_time_low | 16_bit_uint_random |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 32_bit_uint_random |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 32_bit_uint_random |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
```
### String Representation
```
ttttttttttrrrrrrrrrrrrrrrr
where
t is Timestamp
r is Randomness
```
### Links
* [Original Implementation (Javascript)](https://github.com/alizain/ulid)
* [ulid (python)](https://github.com/mdipierro/ulid)
Raw data
{
"_id": null,
"home_page": "https://github.com/ahawker/ulid",
"name": "ulid-py",
"maintainer": "",
"docs_url": null,
"requires_python": "",
"maintainer_email": "",
"keywords": "",
"author": "Andrew Hawker",
"author_email": "andrew.r.hawker@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/3b/53/d14a8ec344048e21431821cb49e9a6722384f982b889c2dd449428dbdcc1/ulid-py-1.1.0.tar.gz",
"platform": "",
"description": "# ulid\n\n[](https://travis-ci.org/ahawker/ulid)\n[](https://ci.appveyor.com/project/ahawker/ulid/branch/master)\n[](https://codecov.io/gh/ahawker/ulid)\n[](https://codeclimate.com/github/ahawker/ulid)\n[](https://codeclimate.com/github/ahawker/ulid)\n\n[](https://badge.fury.io/py/ulid-py)\n[](https://pypi.python.org/pypi/ulid-py)\n\n[](https://pyup.io/repos/github/ahawker/ulid/)\n\n[](http://ulid.readthedocs.io/en/latest/?badge=latest)\n\n[Universally Unique Lexicographically Sortable Identifier](https://github.com/alizain/ulid) in [Python 3](https://www.python.org/).\n\n### Status\n\nThis project is actively maintained.\n\n### Installation\n\nTo install ulid from [pip](https://pypi.python.org/pypi/pip):\n```bash\n $ pip install ulid-py\n```\n\nTo install ulid from source:\n```bash\n $ git clone git@github.com:ahawker/ulid.git\n $ cd ulid && python setup.py install\n```\n\n### Usage\n\nCreate a brand new ULID.\n\nThe timestamp value (48-bits) is from [time.time()](https://docs.python.org/3/library/time.html?highlight=time.time#time.time) with millisecond precision.\n\nThe randomness value (80-bits) is from [os.urandom()](https://docs.python.org/3/library/os.html?highlight=os.urandom#os.urandom).\n\n```python\n>>> import ulid\n>>> ulid.new()\n<ULID('01BJQE4QTHMFP0S5J153XCFSP9')>\n```\n\nCreate a new ULID from an existing 128-bit value, such as a [UUID](https://docs.python.org/3/library/uuid.html).\n\nSupports ULID values as `int`, `bytes`, `str`, and `UUID` types.\n\n```python\n>>> import ulid, uuid\n>>> value = uuid.uuid4()\n>>> value\nUUID('0983d0a2-ff15-4d83-8f37-7dd945b5aa39')\n>>> ulid.from_uuid(value)\n<ULID('09GF8A5ZRN9P1RYDVXV52VBAHS')>\n```\n\nCreate a new ULID from an existing timestamp value, such as a [datetime](https://docs.python.org/3/library/datetime.html#module-datetime) object.\n\nSupports timestamp values as `int`, `float`, `str`, `bytes`, `bytearray`, `memoryview`, `datetime`, `Timestamp`, and `ULID` types.\n\n```python\n>>> import datetime, ulid\n>>> ulid.from_timestamp(datetime.datetime(1999, 1, 1))\n<ULID('00TM9HX0008S220A3PWSFVNFEH')>\n```\n\nCreate a new ULID from an existing randomness value.\n\nSupports randomness values as `int`, `float`, `str`, `bytes`, `bytearray`, `memoryview`, `Randomness`, and `ULID` types.\n\n```python\n>>> import os, ulid\n>>> randomness = os.urandom(10)\n>>> ulid.from_randomness(randomness)\n>>> <ULID('01BJQHX2XEDK0VN0GMYWT9JN8S')>\n```\n\nFor cases when you don't necessarily control the data type (input from external system), you can use the `parse` method\nwhich will attempt to make the correct determination for you. Please note that this will be slightly slower than creating\nthe instance from the respective `from_*` method as it needs to make a number of type/conditional checks.\n\nSupports values as `int`, `float`, `str`, `bytes`, `bytearray`, `memoryview`, `uuid.UUID`, and `ULID` types.\n\n```python\n>>> import ulid\n>>> value = db.model.get_id() ## Unsure about datatype -- Could be int, UUID, or string?\n>>> ulid.parse(value)\n>>> <ULID('0K0EDFETFM8SH912DBBD4ABXSZ')>\n```\n\nOnce you have a ULID object, there are a number of ways to interact with it.\n\nThe `timestamp` method will give you a snapshot view of the first 48-bits of the ULID while the `randomness` method\nwill give you a snapshot of the last 80-bits.\n\n```python\n>>> import ulid\n>>> u = ulid.new()\n>>> u\n<ULID('01BJQM7SC7D5VVTG3J68ABFQ3N')>\n>>> u.timestamp()\n<Timestamp('01BJQM7SC7')>\n>>> u.randomness()\n<Randomness('D5VVTG3J68ABFQ3N')>\n```\n\nThe `ULID`, `Timestamp`, and `Randomness` classes all derive from the same base class, a `MemoryView`.\n\nA `MemoryView` provides the `bin`, `bytes`, `hex`, `int`, `oct`, and `str`, methods for changing any values representation.\n\n```python\n>>> import ulid\n>>> u = ulid.new()\n>>> u\n<ULID('01BJQMF54D093DXEAWZ6JYRPAQ')>\n>>> u.timestamp()\n<Timestamp('01BJQMF54D')>\n>>> u.timestamp().int\n1497589322893\n>>> u.timestamp().bytes\nb'\\x01\\\\\\xafG\\x94\\x8d'\n>>> u.timestamp().datetime\ndatetime.datetime(2017, 6, 16, 5, 2, 2, 893000)\n>>> u.randomness().bytes\nb'\\x02F\\xde\\xb9\\\\\\xf9\\xa5\\xecYW'\n>>> u.bytes[6:] == u.randomness().bytes\nTrue\n>>> u.str\n'01BJQMF54D093DXEAWZ6JYRPAQ'\n>>> u.int\n1810474399624548315999517391436142935\n>>> u.bin\n'0b1010111001010111101000111100101001000110100000010010001101101111010111001010111001111100110100101111011000101100101010111'\n>>> u.hex\n'0x15caf47948d0246deb95cf9a5ec5957'\n>>> u.oct\n'0o12712750745106402215572712717464573054527'\n```\n\nA `MemoryView` also provides rich comparison functionality.\n\n```python\n>>> import datetime, time, ulid\n>>> u1 = ulid.new()\n>>> time.sleep(5)\n>>> u2 = ulid.new()\n>>> u1 < u2\nTrue\n>>> u3 = ulid.from_timestamp(datetime.datetime(2039, 1, 1))\n>>> u1 < u2 < u3\nTrue\n>>> [u.timestamp().datetime for u in sorted([u2, u3, u1])]\n[datetime.datetime(2017, 6, 16, 5, 7, 14, 847000), datetime.datetime(2017, 6, 16, 5, 7, 26, 775000), datetime.datetime(2039, 1, 1, 8, 0)]\n```\n\n### Future Items\n\n* Collection of benchmarks to track performance.\n* Backport to Python 2.7?\n* See [Github Issues](https://github.com/ahawker/ulid/issues) for more!\n\n### Goals\n\nA fast implementation in pure python of the spec with binary format support.\n\n### Contributing\n\nIf you would like to contribute, simply fork the repository, push your changes and send a pull request.\nPull requests will be brought into the `master` branch via a rebase and fast-forward merge with the goal of having a linear branch history with no merge commits.\n\n### License\n\n[Apache 2.0](LICENSE)\n\n## Why not UUID?\n\nUUID can be suboptimal for many uses-cases because:\n\n* It isn't the most character efficient way of encoding 128 bits of randomness\n* UUID v1/v2 is impractical in many environments, as it requires access to a unique, stable MAC address\n* UUID v3/v5 requires a unique seed and produces randomly distributed IDs, which can cause fragmentation in many data structures\n* UUID v4 provides no other information than randomness which can cause fragmentation in many data structures\n\nULID provides:\n\n* 128-bit compatibility with UUID\n* 1.21e+24 unique ULIDs per millisecond\n* Lexicographically sortable!\n* Canonically encoded as a 26 character string, as opposed to the 36 character UUID\n* Uses Crockford's base32 for better efficiency and readability (5 bits per character)\n* Case insensitive\n* No special characters (URL safe)\n\n## Specification\n\nBelow is the current specification of ULID as implemented in this repository.\n\nThe binary format is implemented.\n\n```\n 01AN4Z07BY 79KA1307SR9X4MV3\n\n|----------| |----------------|\n Timestamp Randomness\n 10chars 16chars\n 48bits 80bits\n```\n\n### Components\n\n**Timestamp**\n* 48 bit integer\n* UNIX-time in milliseconds\n* Won't run out of space till the year 10895 AD.\n\n**Randomness**\n* 80 bits\n* Cryptographically secure source of randomness, if possible\n\n### Sorting\n\nThe left-most character must be sorted first, and the right-most character sorted last (lexical order).\nThe default ASCII character set must be used. Within the same millisecond, sort order is not guaranteed\n\n### Encoding\n\nCrockford's Base32 is used as shown. This alphabet excludes the letters I, L, O, and U to avoid confusion and abuse.\n\n```\n0123456789ABCDEFGHJKMNPQRSTVWXYZ\n```\n\n### Binary Layout and Byte Order\n\nThe components are encoded as 16 octets. Each component is encoded with the Most Significant Byte first (network byte order).\n\n```\n0 1 2 3\n 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\n+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n| 32_bit_uint_time_high |\n+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n| 16_bit_uint_time_low | 16_bit_uint_random |\n+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n| 32_bit_uint_random |\n+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n| 32_bit_uint_random |\n+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n```\n\n### String Representation\n\n```\nttttttttttrrrrrrrrrrrrrrrr\n\nwhere\nt is Timestamp\nr is Randomness\n```\n\n### Links\n\n* [Original Implementation (Javascript)](https://github.com/alizain/ulid)\n* [ulid (python)](https://github.com/mdipierro/ulid)\n\n\n",
"bugtrack_url": null,
"license": "Apache 2.0",
"summary": "Universally Unique Lexicographically Sortable Identifier",
"version": "1.1.0",
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"md5": "52c96404ddb68afe4a0c6b1ec5b8df5c",
"sha256": "b56a0f809ef90d6020b21b89a87a48edc7c03aea80e5ed5174172e82d76e3987"
},
"downloads": -1,
"filename": "ulid_py-1.1.0-py2.py3-none-any.whl",
"has_sig": false,
"md5_digest": "52c96404ddb68afe4a0c6b1ec5b8df5c",
"packagetype": "bdist_wheel",
"python_version": "py2.py3",
"requires_python": null,
"size": 25753,
"upload_time": "2020-09-15T15:35:08",
"upload_time_iso_8601": "2020-09-15T15:35:08.075481Z",
"url": "https://files.pythonhosted.org/packages/42/7c/a12c879fe6c2b136a718c142115ff99397fbf62b4929d970d58ae386d55f/ulid_py-1.1.0-py2.py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"md5": "9e554f4e0c7e3529eab178f08db38177",
"sha256": "dc6884be91558df077c3011b9fb0c87d1097cb8fc6534b11f310161afd5738f0"
},
"downloads": -1,
"filename": "ulid-py-1.1.0.tar.gz",
"has_sig": false,
"md5_digest": "9e554f4e0c7e3529eab178f08db38177",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 22514,
"upload_time": "2020-09-15T15:35:09",
"upload_time_iso_8601": "2020-09-15T15:35:09.414672Z",
"url": "https://files.pythonhosted.org/packages/3b/53/d14a8ec344048e21431821cb49e9a6722384f982b889c2dd449428dbdcc1/ulid-py-1.1.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2020-09-15 15:35:09",
"github": true,
"gitlab": false,
"bitbucket": false,
"github_user": "ahawker",
"github_project": "ulid",
"travis_ci": true,
"coveralls": true,
"github_actions": false,
"appveyor": true,
"requirements": [],
"tox": true,
"lcname": "ulid-py"
}