Skip to content

Commit

Permalink
extend built-in UUID class for uuid6, uuid7
Browse files Browse the repository at this point in the history
  • Loading branch information
YoussefEgla committed Feb 15, 2024
1 parent c9fa6a5 commit 8886c3a
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 82 deletions.
12 changes: 0 additions & 12 deletions .github/FUNDING.yml

This file was deleted.

12 changes: 6 additions & 6 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ name: CI
on:
# Triggers the workflow on push or pull request events but only for the main branch
push:
branches: [ main ]
branches: [main]
pull_request:
branches: [ main ]
branches: [main]

# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
Expand All @@ -18,7 +18,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: [3.9]
python-version: [3.10.13]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
Expand All @@ -36,7 +36,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: [3.9]
python-version: [3.10.13]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
Expand All @@ -58,7 +58,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: [3.9]
python-version: [3.10.13]
os: [macos-latest]
runs-on: ${{ matrix.os }}
steps:
Expand All @@ -76,7 +76,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: [3.9]
python-version: [3.10.13]
os: [windows-latest]
runs-on: ${{ matrix.os }}
steps:
Expand Down
34 changes: 17 additions & 17 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
push:
# Sequence of patterns matched against refs/tags
tags:
- '*' # Push events to matching v*, i.e. v1.0, v20.15.10
- "*" # Push events to matching v*, i.e. v1.0, v20.15.10

# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
Expand Down Expand Up @@ -32,19 +32,19 @@ jobs:
needs: release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel twine
- name: Build and publish
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: |
python setup.py sdist bdist_wheel
twine upload dist/*
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel twine
- name: Build and publish
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: |
python setup.py sdist bdist_wheel
twine upload dist/*
8 changes: 4 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Python setup.py for uuid_v7 package"""

import io
import os
from setuptools import find_packages, setup
Expand Down Expand Up @@ -32,15 +33,14 @@ def read_requirements(path):
setup(
name="uuid_v7",
version=read("uuid_v7", "VERSION"),
description="Awesome uuid_v7 created by YoussefEgla",
description="UUID v6 and v7 for Python 3.10+.",
url="https://github.com/YoussefEgla/uuid-v7/",
long_description=read("README.md"),
long_description_content_type="text/markdown",
author="YoussefEgla",
packages=find_packages(exclude=["tests", ".github"]),
install_requires=read_requirements("requirements.txt"),
entry_points={
"console_scripts": ["uuid_v7 = uuid_v7.__main__:main"]
},
entry_points={"console_scripts": ["uuid_v7 = uuid_v7.__main__:main"]},
extras_require={"test": read_requirements("requirements-test.txt")},
python_requires=">=3.10",
)
28 changes: 25 additions & 3 deletions tests/test_base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
from uuid_v7.base import NAME
from uuid_v7.base import uuid6, uuid7, UUID


def test_base():
assert NAME == "uuid_v7"
def test_uuid7_returns_uuid_instance():
assert isinstance(uuid7(), UUID)


def test_uuid7_returns_unique_values():
for _ in range(1000):
assert uuid7() != uuid7()


def test_uuid7_correct_length():
assert len(str(uuid7())) == 36


def test_uuid6_returns_uuid_instance():
assert isinstance(uuid6(), UUID)


def test_uuid6_returns_unique_values():
for _ in range(1000):
assert uuid6() != uuid6()


def test_uuid6_correct_length():
assert len(str(uuid6())) == 36
2 changes: 1 addition & 1 deletion uuid_v7/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.1.0
1.0.0
100 changes: 89 additions & 11 deletions uuid_v7/base.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,95 @@
"""
uuid_v7 base module.
from uuid import UUID as BaseUUID
from uuid import SafeUUID

This is the principal module of the uuid_v7 project.
here you put your main classes and objects.

Be creative! do whatever you want!
class UUID(BaseUUID):
def __init__(
self,
hex: str | None = None,
bytes: bytes | None = None,
bytes_le: bytes | None = None,
fields: tuple[int, int, int, int, int, int] | None = None,
int: int | None = None,
version: int | None = None,
*,
is_safe: SafeUUID = SafeUUID.unknown
) -> None:
super().__init__(
hex, bytes, bytes_le, fields, int, version=None, is_safe=is_safe
)

If you want to replace this with a Flask application run:
if version is not None:
if not 1 <= version <= 7:
raise ValueError("illegal version number")
# Set the variant to RFC 4122.
int = int or 0
int &= ~(0xC000 << 48)
int |= 0x8000 << 48
# Set the version number.
int &= ~(0xF000 << 64)
int |= version << 76
object.__setattr__(self, "int", int)
object.__setattr__(self, "is_safe", is_safe)

$ make init

and then choose `flask` as template.
"""
_last_v6_timestamp = None
_last_v7_timestamp = None

# example constant variable
NAME = "uuid_v7"

def uuid6(clock_seq=None):
"""Generate a UUID from sequence number, and the current time.
If 'clock_seq' is given, it is used as the sequence number;
otherwise a random 14-bit sequence number is chosen."""

global _last_v6_timestamp
import random
import time

nanoseconds = time.time_ns()
# 0x01b21dd213814000 is the number of 100-ns intervals between the
# UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00.
timestamp = nanoseconds // 100 + 0x01B21DD213814000
if _last_v6_timestamp is not None and timestamp <= _last_v6_timestamp:
timestamp = _last_v6_timestamp + 1
_last_v6_timestamp = timestamp
if clock_seq is None:
clock_seq = random.SystemRandom().getrandbits(
14
) # instead of stable storage
node = random.SystemRandom().getrandbits(48)
time_high_and_time_mid = (timestamp >> 12) & 0xFFFFFFFFFFFF
time_low_and_version = timestamp & 0x0FFF
uuid_int = time_high_and_time_mid << 80
uuid_int += time_low_and_version << 64
uuid_int += (clock_seq & 0x3FFF) << 48
uuid_int += node
return UUID(int=uuid_int, version=6)


def uuid7():
"""The UUIDv7 format is designed to encode a Unix timestamp with
arbitrary sub-second precision. The key property provided by UUIDv7
is that timestamp values generated by one system and parsed by
another are guaranteed to have sub-second precision of either the
generator or the parser, whichever is less. Additionally, the system
parsing the UUIDv7 value does not need to know which precision was
used during encoding in order to function correctly."""

global _last_v7_timestamp
import random
import time

nanoseconds = time.time_ns()
if _last_v7_timestamp is not None and nanoseconds <= _last_v7_timestamp:
nanoseconds = _last_v7_timestamp + 1
_last_v7_timestamp = nanoseconds
timestamp_s, timestamp_ns = divmod(nanoseconds, 10**9)
subsec_a = timestamp_ns >> 18
subsec_b = (timestamp_ns >> 6) & 0x0FFF
subsec_seq_node = (timestamp_ns & 0x3F) << 56
subsec_seq_node += random.SystemRandom().getrandbits(56)
uuid_int = (timestamp_s & 0x0FFFFFFFFF) << 92
uuid_int += subsec_a << 80
uuid_int += subsec_b << 64
uuid_int += subsec_seq_node
return UUID(int=uuid_int, version=7)
30 changes: 2 additions & 28 deletions uuid_v7/cli.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,2 @@
"""CLI interface for uuid_v7 project.
Be creative! do whatever you want!
- Install click or typer and create a CLI app
- Use builtin argparse
- Start a web application
- Import things from your .base module
"""


def main(): # pragma: no cover
"""
The main function executes on commands:
`python -m uuid_v7` and `$ uuid_v7 `.
This is your program's entry point.
You can change this function to do whatever you want.
Examples:
* Run a test suite
* Run a server
* Do some other stuff
* Run a command line application (Click, Typer, ArgParse)
* List all available tasks
* Run an application (Flask, FastAPI, Django, etc.)
"""
print("This will do something")
def main():
pass

0 comments on commit 8886c3a

Please sign in to comment.