Metadata-Version: 2.4
Name: rvid.seq
Version: 1.0.0
Summary: Admin-friendly, fixed-length, unique-enough, fast-enough, monotonic, identifiers.
Author-email: Arvid Müllern-Aspegren <kontakt@rvid.se>
Project-URL: homepage, https://rvid.se/seq
Project-URL: repository, https://hg.sr.ht/~rvid/rvid.seq
Requires-Python: >=3.6
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: black; extra == "dev"
Requires-Dist: pytest; extra == "dev"
Requires-Dist: mypy[reports]; extra == "dev"
Requires-Dist: isort; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Dynamic: license-file

# rvid.seq a.k.a RID

Admin-friendly, fixed-length, unique-enough, fast-enough, monotonic,
identifiers.

Optimized for small contexts where you only very rarely need more than a few
million unique ID:s per day and prefer those ID:s to carry a bit of meaning
and be easy to read, remember and speak out loud.

It is essentially a compact, semi-high-resolution timestamp, with a bit of
logic for avoiding duplicates in a cross-platform manner.

```
from rvid.seq import RID
my_id = RID.next()
>>> '0Q1-USW-YU8'
```

## Definition

A RID is an *approximation* of millisecond Unix epoch time, encoded in base-35
with a readability-optimized character set and presented in exactly three
groups of exactly three alphanumerics.

The characters used for encoding is: `0123456789ABCDEFGH*JKLMN#PQRSTUVWXY`.
Note that I and O are replaced with * and #.

## Compatibility

The code is tested OK with CPython 3.6 and 3.11, and PyPy 7.3.1.

The wheel building may however have some issues on Python 3.6, since
current versions of pip no longer support interpreters that old.

## Building and installing

Via PyPI: `pip install rvid.seq`

For local build it is not much harder:

```
hg clone https://hg.sr.ht/~rvid/rvid.seq
cd rvid.seq
pip wheel . -w dist
```

The build produces a pure python wheel with no dependencies.

### Troubleshooting

> ⚠️ **pip can't find 'hg' command**

There seems to be a bug in setuptools_scm or mercurial, which on at least some
systems mean that you must run pip via a virtualenv where mercurial is
pre-installed:

```
python3 -m venv venv
venv/bin/pip install mercurial
venv/bin/pip wheel . -w dist
```

## Usage

### Command-line

The command-line tool `rid` lets you get the RID for current time or do 
translations:

```
(venv) [user@host ~]$ rid
0Q1-X1G-HQ6

(venv) [user@host ~]$ rid 0Q1-X1G-HQ6
0Q1-X1G-HQ6 corresponds to 2023-02-14 18:23:34

(venv) [user@host ~]$ rid $(date +%s)
1676397310 corresponds to 0Q1-X2P-P00
```

### In Python code

For programmatic usage, you would import the RID singleton in your software,
and use its `.next()` method to get a value:

```
from rvid.seq import RID
id = RID.next()
```

If you application is multi-threaded, you can use the thread-safe version. It
works exactly the same but is slower due to locking-overhead.

```
from rvid.seq import RID_ThreadSafe as RID
id = RID.next()
```

### Safe resumption

```
from rvid.seq import epoch2rid, RID

[RID.next() for _ in range(2)]
>>> ['0Q1-UX2-XYS', '0Q1-UX2-XYT']

RID.adjust_top_from_rid( epoch2rid( time.time() + 1000 * 1000 ) )
RID.next()
>>> '0Q2-EYH-G0Q'
```

If picking up work from e.g. a saved file in a new session, you will need to
seed the RID-generator with the max-RID from that saved file. (Or else stuff
gets weird if your clock has moved backwards).

## Scalability

By the year 2525, if man is still alive, you still have 18 billion id:s left in
the number space before wrapping over.

RID is unlikely to be too slow. A Ryzen 3900X generates about 430k RID:s/second
with CPython 3.9 and 9.5M RID:s/second with Pypy 7.3.1.

## Background and motivations

I have found UUID:s to be annoying at times. They take up lots of screen space
and are difficult to read. And I always end up having to read them when
debugging one thing or another. They are not optimized for direct human 
consumption.

### On the topic of anchoring to system time

I've chosen to anchor the number sequence on system time, because it adds some
extra context information to each ID. I'm not certain that I will stick with
that, because it also adds a bit of complication to the implementation and
some mental overhead when parsing the ID:s.

Skipping the system-time anchor would allow dropping one of the triplets and
still get a reasonable 1.8 billion unique values.

```
from rvid.seq.common import rid2epoch_ms
rid2epoch_ms("000-YYY-YYY")
>>> 1838265624
```

On the other hand, decoupling time from ID would mean that you must have and
must read a timestamp to have the slightest idea about when an object was
created. I'm not 100% sure that it would be net gain in mental overhead.
