Skip to content

Commit 4aaf1fa

Browse files
authored
Merge pull request #50 from akhundMurad/feat/core/37
Add full docs site and enhance CLI/docstrings
2 parents 3efd6a1 + 13b8b90 commit 4aaf1fa

File tree

20 files changed

+1285
-19
lines changed

20 files changed

+1285
-19
lines changed

.github/workflows/ci.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
- name: Set up Python
1616
uses: actions/setup-python@v5
1717
with:
18-
python-version: "3.12"
18+
python-version: "3.11"
1919

2020
- name: Install uv
2121
uses: astral-sh/setup-uv@v3
@@ -37,7 +37,7 @@ jobs:
3737
- name: Set up Python
3838
uses: actions/setup-python@v5
3939
with:
40-
python-version: "3.12"
40+
python-version: "3.11"
4141

4242
- name: Install uv
4343
uses: astral-sh/setup-uv@v3
@@ -49,3 +49,7 @@ jobs:
4949
- name: Run tests
5050
run: |
5151
make test
52+
53+
- name: Run doc tests
54+
run: |
55+
make test-docs

.github/workflows/docs.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Docs
2+
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
deploy:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
- uses: actions/setup-python@v5
18+
with:
19+
python-version: "3.12"
20+
21+
- name: Install
22+
run: |
23+
python -m pip install --upgrade pip
24+
pip install mkdocs-material mkdocstrings[python] mkdocs-git-revision-date-localized-plugin
25+
pip install -e .
26+
27+
- name: Deploy
28+
run: mkdocs gh-deploy --force

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Thank you for taking the time to contribute ❤️
1818
2. Clone your fork locally:
1919

2020
```bash
21-
git clone https://github.com/<your-username>/typeid-python.git
21+
git clone https://github.com/akhundMurad/typeid-python.git
2222
cd typeid-python
2323
```
2424

Makefile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,16 @@ release:
3838

3939
test:
4040
uv run pytest -v
41+
42+
43+
test-docs:
44+
uv run pytest README.md docs/ --markdown-docs
45+
46+
47+
docs:
48+
mkdocs serve
49+
50+
51+
docs-build:
52+
mkdocs build
53+

README.md

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,25 +64,30 @@ If the extra is not installed, JSON schemas will still work.
6464
```python
6565
from typeid import TypeID
6666

67+
# Default TypeID (no prefix)
6768
typeid = TypeID()
6869

69-
print(typeid.prefix) # ""
70-
print(typeid.suffix) # "01h45ytscbebyvny4gc8cr8ma2" (encoded uuid7 instance)
70+
assert typeid.prefix == ""
71+
assert isinstance(typeid.suffix, str)
72+
assert len(typeid.suffix) > 0 # encoded UUIDv7
7173

74+
# TypeID with prefix
7275
typeid = TypeID(prefix="user")
7376

74-
print(typeid.prefix) # "user"
75-
print(str(typeid)) # "user_01h45ytscbebyvny4gc8cr8ma2"
77+
assert typeid.prefix == "user"
78+
assert str(typeid).startswith("user_")
7679
```
7780

7881
- Create TypeID from string:
7982

8083
```python
8184
from typeid import TypeID
8285

83-
typeid = TypeID.from_string("user_01h45ytscbebyvny4gc8cr8ma2")
86+
value = "user_01h45ytscbebyvny4gc8cr8ma2"
87+
typeid = TypeID.from_string(value)
8488

85-
print(str(typeid)) # "user_01h45ytscbebyvny4gc8cr8ma2"
89+
assert str(typeid) == value
90+
assert typeid.prefix == "user"
8691
```
8792

8893
- Create TypeID from uuid7:
@@ -91,12 +96,37 @@ If the extra is not installed, JSON schemas will still work.
9196
from typeid import TypeID
9297
from uuid6 import uuid7
9398

94-
uuid = uuid7() # UUID('01890bf0-846f-7762-8605-5a3abb40e0e5')
99+
uuid = uuid7()
95100
prefix = "user"
96101

97102
typeid = TypeID.from_uuid(prefix=prefix, suffix=uuid)
98103

99-
print(str(typeid)) # "user_01h45z113fexh8c1at7axm1r75"
104+
assert typeid.prefix == prefix
105+
assert str(typeid).startswith(f"{prefix}_")
106+
107+
```
108+
109+
- Use pre-defined prefix:
110+
111+
```python
112+
from dataclasses import dataclass, field
113+
from typing import Literal
114+
from typeid import TypeID, typeid_factory
115+
116+
UserID = TypeID[Literal["user"]]
117+
gen_user_id = typeid_factory("user")
118+
119+
120+
@dataclass
121+
class UserDTO:
122+
user_id: UserID = field(default_factory=gen_user_id)
123+
full_name: str = "A J"
124+
age: int = 18
125+
126+
127+
user = UserDTO()
128+
129+
assert str(user.user_id).startswith("user_")
100130
```
101131

102132
### CLI-tool

docs/concepts.md

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Concepts
2+
3+
TypeID exists because identifiers are used for much more than uniqueness.
4+
5+
They appear in logs, URLs, dashboards, tickets, alerts, database rows, and Slack messages.
6+
Yet most identifiers—especially UUIDs—are opaque. They carry no meaning, no context, and no affordances for inspection.
7+
8+
TypeID is an attempt to fix that, without breaking the properties that make UUIDs useful.
9+
10+
---
11+
12+
## TypeID as an identifier
13+
14+
A TypeID is a string identifier composed of two independent parts:
15+
16+
```text
17+
<prefix>_<suffix>
18+
```
19+
20+
For example:
21+
22+
```text
23+
user_01h45ytscbebyvny4gc8cr8ma2
24+
```
25+
26+
The **suffix** is the identity. It is globally unique and backed by a UUID.
27+
The **prefix** is context. It tells a human (and tooling) what kind of thing the identifier refers to.
28+
29+
Crucially, the prefix does *not* participate in uniqueness. Two TypeIDs with different prefixes but the same suffix represent the same underlying UUID. The prefix is a semantic layer, not a storage primitive.
30+
31+
This separation is intentional. It allows TypeID to add meaning without interfering with existing UUID-based systems.
32+
33+
## UUID compatibility by design
34+
35+
TypeID is not a replacement for UUIDs. It is a layer on top of them.
36+
37+
Every TypeID corresponds to exactly one UUID, and that UUID can always be extracted or reconstructed. This makes it possible to:
38+
39+
* store native UUIDs in databases
40+
* use existing UUID indexes and constraints
41+
* introduce TypeID without schema migrations
42+
* roll back or interoperate with systems that know nothing about TypeID
43+
44+
The recommended pattern is simple: **store UUIDs, expose TypeIDs**.
45+
46+
TypeID lives at the boundaries of your system—APIs, logs, tooling—not at the lowest storage level.
47+
48+
## Sortability and time
49+
50+
The suffix used by TypeID is time-sortable. When two TypeIDs are compared lexicographically, the one created earlier sorts before the one created later.
51+
52+
This property is not about business semantics; it is about ergonomics.
53+
54+
Sortable identifiers make logs readable, pagination predictable, and debugging less frustrating. When you scan a list of IDs, you can usually infer their relative age without additional metadata.
55+
56+
There are important limits to this property. Ordering reflects **generation time**, not transaction time or business events. Clock skew and distributed systems still exist. TypeID does not attempt to impose global ordering or causality.
57+
58+
Sortability is a convenience, not a guarantee.
59+
60+
## Explainability
61+
62+
Once an identifier carries structure, it becomes possible to inspect it.
63+
64+
TypeID can be *explained*: given a string, the system can determine whether it is a valid TypeID, extract its UUID, derive its creation time, and report these facts in a structured way.
65+
66+
This is useful in places where identifiers normally appear as dead text:
67+
68+
* logs
69+
* error messages
70+
* database dumps
71+
* incident reports
72+
* CI output
73+
74+
Explainability is designed to be safe. Invalid identifiers do not crash the system. Unknown prefixes are accepted. Each identifier is handled independently, which makes batch processing robust.
75+
76+
## Schemas as optional meaning
77+
78+
Derived facts are always available, but they are not always enough. In real systems, prefixes often correspond to domain concepts: users, orders, events, aggregates.
79+
80+
Schemas allow you to describe that meaning explicitly.
81+
82+
A schema can say that a `user` ID represents an end-user account, that it contains PII, that it is owned by a particular team, or that related logs and dashboards can be found at specific URLs.
83+
84+
Schemas are optional and additive. If a schema is missing, outdated, or invalid, TypeID still works. The identifier does not become invalid because metadata could not be loaded.
85+
86+
This separation keeps the core identifier system stable while allowing richer interpretation where it is useful.
87+
88+
## Unknown and invalid identifiers
89+
90+
TypeID makes a clear distinction between identifiers that are **invalid** and those that are merely **unknown**.
91+
92+
An invalid identifier is structurally wrong: it cannot be parsed or decoded.
93+
An unknown identifier is structurally valid, but its prefix is not recognized by any schema.
94+
95+
Unknown identifiers are first-class citizens. They allow systems to evolve independently and avoid tight coupling between producers and consumers of IDs.
96+
97+
This distinction is essential for forward compatibility and safe tooling.
98+
99+
## A note on safety
100+
101+
TypeID is deliberately conservative.
102+
103+
It does not infer meaning.
104+
It does not mutate state.
105+
It does not enforce authorization.
106+
It does not treat identifiers as secrets.
107+
108+
Its goal is to make identifiers **more understandable**.
109+
110+
## Closing thoughts
111+
112+
TypeID treats identifiers as part of the system’s interface, not as incidental implementation details.
113+
114+
By combining UUID compatibility, time-based sortability, and structured explainability, it aims to make everyday engineering tasks—debugging, inspection, reasoning—slightly less painful.
115+
116+
Identifiers should not be mysterious. They should be inspectable, understandable, and boring in the best possible way.

docs/contributing.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--8<-- "CONTRIBUTING.md"

0 commit comments

Comments
 (0)