mirror of
https://github.com/exo-explore/exo.git
synced 2025-12-23 22:27:50 -05:00
Scaffold Event Sourcing
This commit is contained in:
26
master/idempotency.py
Normal file
26
master/idempotency.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from hashlib import sha3_224 as hasher
|
||||
from typing import Sequence, TypeVar
|
||||
|
||||
from shared.types.event_sourcing import EventId, EventTypes, IdemKeyGenerator, State
|
||||
|
||||
EventTypeT = TypeVar("EventTypeT", bound=EventTypes)
|
||||
|
||||
|
||||
def get_idem_tag_generator(base: str) -> IdemKeyGenerator[EventTypeT]:
|
||||
"""Generates idempotency keys for events.
|
||||
|
||||
The keys are generated by hashing the state sequence number against a base string.
|
||||
You can pick any base string, **so long as it's not used in any other function that generates idempotency keys**.
|
||||
"""
|
||||
|
||||
def get_idem_keys(state: State[EventTypeT], num_keys: int) -> Sequence[EventId]:
|
||||
def recurse(n: int, last: bytes) -> Sequence[EventId]:
|
||||
if n == 0:
|
||||
return []
|
||||
next_hash = hasher(last).digest()
|
||||
return (EventId(next_hash.hex()), *recurse(n - 1, next_hash))
|
||||
|
||||
initial_bytes = state.sequence_number.to_bytes(8, byteorder="big", signed=False)
|
||||
return recurse(num_keys, initial_bytes)
|
||||
|
||||
return get_idem_keys
|
||||
1
networking/.gitignore
vendored
Normal file
1
networking/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
target/
|
||||
171
networking/Cargo.lock
generated
Normal file
171
networking/Cargo.lock
generated
Normal file
@@ -0,0 +1,171 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "2.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.174"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "networking"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"pyo3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3"
|
||||
version = "0.22.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f402062616ab18202ae8319da13fa4279883a2b8a9d9f83f20dbade813ce1884"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"indoc",
|
||||
"libc",
|
||||
"memoffset",
|
||||
"once_cell",
|
||||
"portable-atomic",
|
||||
"pyo3-build-config",
|
||||
"pyo3-ffi",
|
||||
"pyo3-macros",
|
||||
"unindent",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-build-config"
|
||||
version = "0.22.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b14b5775b5ff446dd1056212d778012cbe8a0fbffd368029fd9e25b514479c38"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"target-lexicon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-ffi"
|
||||
version = "0.22.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ab5bcf04a2cdcbb50c7d6105de943f543f9ed92af55818fd17b660390fc8636"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pyo3-build-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-macros"
|
||||
version = "0.22.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fd24d897903a9e6d80b968368a34e1525aeb719d568dba8b3d4bfa5dc67d453"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"pyo3-macros-backend",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-macros-backend"
|
||||
version = "0.22.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36c011a03ba1e50152b4b394b479826cad97e7a21eb52df179cd91ac411cbfbe"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"pyo3-build-config",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "target-lexicon"
|
||||
version = "0.12.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "unindent"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
|
||||
14
networking/Cargo.toml
Normal file
14
networking/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "networking"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "_core"
|
||||
# "cdylib" is necessary to produce a shared library for Python to import from.
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
# "extension-module" tells pyo3 we want to build an extension module (skips linking against libpython.so)
|
||||
# "abi3-py39" tells pyo3 (and maturin) to build using the stable ABI with minimum Python version 3.9
|
||||
pyo3 = { version = "0.22.4", features = ["extension-module", "abi3-py39"] }
|
||||
0
networking/README.md
Normal file
0
networking/README.md
Normal file
22
networking/pyproject.toml
Normal file
22
networking/pyproject.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[project]
|
||||
name = "exo-networking"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
{ name = "Arbion Halili", email = "99731180+ToxicPine@users.noreply.github.com" }
|
||||
]
|
||||
requires-python = ">=3.13"
|
||||
dependencies = []
|
||||
|
||||
[project.scripts]
|
||||
networking = "networking:main"
|
||||
|
||||
[tool.maturin]
|
||||
module-name = "networking._core"
|
||||
python-packages = ["networking"]
|
||||
python-source = "src"
|
||||
|
||||
[build-system]
|
||||
requires = ["maturin>=1.0,<2.0"]
|
||||
build-backend = "maturin"
|
||||
15
networking/src/lib.rs
Normal file
15
networking/src/lib.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[pyfunction]
|
||||
fn hello_from_bin() -> String {
|
||||
"Hello from networking!".to_string()
|
||||
}
|
||||
|
||||
/// A Python module implemented in Rust. The name of this function must match
|
||||
/// the `lib.name` setting in the `Cargo.toml`, else Python will not be able to
|
||||
/// import the module.
|
||||
#[pymodule]
|
||||
fn _core(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(hello_from_bin, m)?)?;
|
||||
Ok(())
|
||||
}
|
||||
5
networking/src/networking/__init__.py
Normal file
5
networking/src/networking/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from networking._core import hello_from_bin
|
||||
|
||||
|
||||
def main() -> None:
|
||||
print(hello_from_bin())
|
||||
1
networking/src/networking/_core.pyi
Normal file
1
networking/src/networking/_core.pyi
Normal file
@@ -0,0 +1 @@
|
||||
def hello_from_bin() -> str: ...
|
||||
@@ -13,6 +13,7 @@ dependencies = [
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"basedpyright>=1.29.4",
|
||||
"maturin>=1.9.0",
|
||||
"pytest>=8.4.0",
|
||||
"ruff>=0.11.13",
|
||||
]
|
||||
@@ -29,7 +30,11 @@ darwin = [
|
||||
|
||||
[tool.uv.workspace]
|
||||
members = [
|
||||
"master", "worker", "shared", "engines/*",
|
||||
"master",
|
||||
"worker",
|
||||
"shared",
|
||||
"engines/*",
|
||||
"networking",
|
||||
]
|
||||
|
||||
[tool.uv.sources]
|
||||
@@ -37,6 +42,7 @@ exo-shared = { workspace = true }
|
||||
exo-master = { workspace = true }
|
||||
exo-worker = { workspace = true }
|
||||
exo-engine-mlx = { workspace = true }
|
||||
exo-networking = { workspace = true }
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
|
||||
11
shared/constants.py
Normal file
11
shared/constants.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from pathlib import Path
|
||||
|
||||
EXO_HOME = Path.home() / ".exo"
|
||||
EXO_EVENT_DB = EXO_HOME / "event_db.sqlite3"
|
||||
EXO_MASTER_CONFIG = EXO_HOME / "master.json"
|
||||
EXO_WORKER_CONFIG = EXO_HOME / "worker.json"
|
||||
EXO_MASTER_LOG = EXO_HOME / "master.log"
|
||||
EXO_WORKER_LOG = EXO_HOME / "worker.log"
|
||||
|
||||
EXO_WORKER_KEYRING_FILE = EXO_HOME / "worker_keyring"
|
||||
EXO_MASTER_KEYRING_FILE = EXO_HOME / "master_keyring"
|
||||
@@ -2,9 +2,20 @@ import logging
|
||||
import os
|
||||
from typing import TypeVar
|
||||
|
||||
from pydantic import BaseModel, ValidationError
|
||||
from pydantic import BaseModel, ConfigDict, ValidationError
|
||||
|
||||
EnvSchema = TypeVar("EnvSchema", bound=BaseModel)
|
||||
env_model_config = ConfigDict(
|
||||
strict=True,
|
||||
frozen=True,
|
||||
extra="forbid",
|
||||
)
|
||||
|
||||
|
||||
class BaseEnv(BaseModel):
|
||||
model_config = env_model_config
|
||||
|
||||
|
||||
EnvSchema = TypeVar("EnvSchema", bound=BaseEnv)
|
||||
|
||||
|
||||
def get_validated_env(
|
||||
|
||||
@@ -5,6 +5,7 @@ description = "Shared utilities for the Exo project"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"pathlib>=1.0.1",
|
||||
"protobuf>=6.31.1",
|
||||
"pydantic>=2.11.7",
|
||||
"rich>=14.0.0",
|
||||
@@ -30,4 +31,4 @@ exclude = ["protobufs/schemas", "*.md", "pyproject.toml"]
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"types-protobuf>=6.30.2.20250516",
|
||||
]
|
||||
]
|
||||
|
||||
99
shared/types/event_sourcing.py
Normal file
99
shared/types/event_sourcing.py
Normal file
@@ -0,0 +1,99 @@
|
||||
from typing import (
|
||||
Annotated,
|
||||
Callable,
|
||||
Generic,
|
||||
Literal,
|
||||
Protocol,
|
||||
Sequence,
|
||||
Tuple,
|
||||
TypeVar,
|
||||
get_args,
|
||||
)
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field, TypeAdapter
|
||||
from pydantic.types import UuidVersion
|
||||
|
||||
_EventId = Annotated[UUID, UuidVersion(4)]
|
||||
EventId = type("EventID", (UUID,), {})
|
||||
EventIdParser: TypeAdapter[EventId] = TypeAdapter(_EventId)
|
||||
|
||||
EventTypes = Literal["create", "update", "delete"]
|
||||
EventTypeT = TypeVar("EventTypeT", bound=EventTypes)
|
||||
TEventType = TypeVar("TEventType", bound=EventTypes, covariant=True)
|
||||
|
||||
|
||||
class Event(BaseModel, Generic[TEventType]):
|
||||
event_type: TEventType
|
||||
idem_key: EventId
|
||||
|
||||
|
||||
class State(BaseModel, Generic[EventTypeT]):
|
||||
event_types: tuple[EventTypeT, ...] = get_args(EventTypeT)
|
||||
sequence_number: int = Field(default=0, ge=0)
|
||||
|
||||
|
||||
AnnotatedEventType = Annotated[EventTypes, Field(discriminator="event_type")]
|
||||
EventTypeParser: TypeAdapter[AnnotatedEventType] = TypeAdapter(AnnotatedEventType)
|
||||
|
||||
Applicator = Callable[[State[EventTypeT], Event[TEventType]], State[EventTypeT]]
|
||||
Apply = Callable[[State[EventTypeT], Event[EventTypeT]], State[EventTypeT]]
|
||||
SagaApplicator = Callable[
|
||||
[State[EventTypeT], Event[TEventType]], Sequence[Event[EventTypeT]]
|
||||
]
|
||||
Saga = Callable[[State[EventTypeT], Event[EventTypeT]], Sequence[Event[EventTypeT]]]
|
||||
|
||||
StateAndEvent = Tuple[State[EventTypeT], Event[EventTypeT]]
|
||||
EffectHandler = Callable[[StateAndEvent[EventTypeT], State[EventTypeT]], None]
|
||||
EventPublisher = Callable[[Event[EventTypeT]], None]
|
||||
|
||||
|
||||
class EventOutbox(Protocol):
|
||||
def send(self, events: Sequence[Event[EventTypeT]]) -> None: ...
|
||||
|
||||
|
||||
class EventProcessor(Protocol):
|
||||
def update(
|
||||
self,
|
||||
state: State[EventTypeT],
|
||||
apply: Apply[EventTypeT],
|
||||
effect_handlers: Sequence[EffectHandler[EventTypeT]],
|
||||
) -> State[EventTypeT]: ...
|
||||
|
||||
|
||||
def get_saga_effect_handler(
|
||||
sagas: Saga[EventTypeT], event_publisher: EventPublisher[EventTypeT]
|
||||
) -> EffectHandler[EventTypeT]:
|
||||
def effect_handler(state_and_event: StateAndEvent[EventTypeT]) -> None:
|
||||
trigger_state, trigger_event = state_and_event
|
||||
for event in sagas(trigger_state, trigger_event):
|
||||
event_publisher(event)
|
||||
|
||||
return lambda state_and_event, _: effect_handler(state_and_event)
|
||||
|
||||
|
||||
def get_effects_from_sagas(
|
||||
sagas: Sequence[Saga[EventTypeT]], event_publisher: EventPublisher[EventTypeT]
|
||||
) -> Sequence[EffectHandler[EventTypeT]]:
|
||||
return [get_saga_effect_handler(saga, event_publisher) for saga in sagas]
|
||||
|
||||
|
||||
IdemKeyGenerator = Callable[[State[EventTypeT], int], Sequence[EventId]]
|
||||
|
||||
_CommandId = Annotated[UUID, UuidVersion(4)]
|
||||
CommandId = type("CommandID", (UUID,), {})
|
||||
CommandIdParser: TypeAdapter[CommandId] = TypeAdapter(_CommandId)
|
||||
|
||||
CommandTypes = Literal["create", "update", "delete"]
|
||||
CommandTypeT = TypeVar("CommandTypeT", bound=EventTypes)
|
||||
TCommandType = TypeVar("TCommandType", bound=EventTypes, covariant=True)
|
||||
|
||||
|
||||
class Command(BaseModel, Generic[TEventType, TCommandType]):
|
||||
command_type: TCommandType
|
||||
idem_key: CommandId
|
||||
|
||||
|
||||
Decide = Callable[
|
||||
[State[EventTypeT], Command[TEventType, TCommandType]], Sequence[Event[EventTypeT]]
|
||||
]
|
||||
36
uv.lock
generated
36
uv.lock
generated
@@ -15,6 +15,7 @@ members = [
|
||||
"exo",
|
||||
"exo-engine-mlx",
|
||||
"exo-master",
|
||||
"exo-networking",
|
||||
"exo-shared",
|
||||
"exo-worker",
|
||||
]
|
||||
@@ -57,6 +58,7 @@ darwin = [
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "basedpyright", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "maturin", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "pytest", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "ruff", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
]
|
||||
@@ -72,6 +74,7 @@ provides-extras = ["darwin"]
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "basedpyright", specifier = ">=1.29.4" },
|
||||
{ name = "maturin", specifier = ">=1.9.0" },
|
||||
{ name = "pytest", specifier = ">=8.4.0" },
|
||||
{ name = "ruff", specifier = ">=0.11.13" },
|
||||
]
|
||||
@@ -92,11 +95,17 @@ dependencies = [
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "exo-shared", editable = "shared" }]
|
||||
|
||||
[[package]]
|
||||
name = "exo-networking"
|
||||
version = "0.1.0"
|
||||
source = { editable = "networking" }
|
||||
|
||||
[[package]]
|
||||
name = "exo-shared"
|
||||
version = "0.1.0"
|
||||
source = { editable = "shared" }
|
||||
dependencies = [
|
||||
{ name = "pathlib", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "protobuf", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "rich", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
@@ -109,6 +118,7 @@ dev = [
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "pathlib", specifier = ">=1.0.1" },
|
||||
{ name = "protobuf", specifier = ">=6.31.1" },
|
||||
{ name = "pydantic", specifier = ">=2.11.7" },
|
||||
{ name = "rich", specifier = ">=14.0.0" },
|
||||
@@ -149,6 +159,23 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "maturin"
|
||||
version = "1.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2a/3a/117a238e055c7d9de5a27619e09f2762830f3ea227f69e110d86e2ec5bd9/maturin-1.9.0.tar.gz", hash = "sha256:ccb9cb87f8df88d1bab8f49efe3fc77f0abb0639ea4b4ebf4f35549200d16b9e", size = 209543, upload-time = "2025-06-23T14:36:05.768Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/3f/3063ce9ace8fe33e02cc05209551a5a0d0af9b7990b14e063876ff149e82/maturin-1.9.0-py3-none-linux_armv6l.whl", hash = "sha256:18d77e395f62a0227697098526be6becb3ceea34a79f338b1b716fb96e42a1b2", size = 8130784, upload-time = "2025-06-23T14:35:35.813Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/52/cb5491ad290002186af3bcb4768f7bb5c6c8d6917cf0a98b945533cd8c04/maturin-1.9.0-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:33f046f52327b68c28203efe5ecc4fd1952b4d1fe34e65853092e3347a6a6fa0", size = 16082407, upload-time = "2025-06-23T14:35:39.584Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/9c/c6fd50c23875fc741651b2fedfffdf4f671cb74c46e66f365d1f9b861daf/maturin-1.9.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6b075f82dc87fa70d583b1fe909ac5e96f36ec2043721acb82f9d6757e860459", size = 8405709, upload-time = "2025-06-23T14:35:42.248Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/44/bf61ff9d3f0db8c5a868da55e7827e5fb1a82642705384bcc85bc9a1918f/maturin-1.9.0-py3-none-manylinux_2_12_i686.manylinux2010_i686.musllinux_1_1_i686.whl", hash = "sha256:c99003470cb37388a31152af4b00492c5db8d767f689a64f45eb5830adc6f3f4", size = 8152167, upload-time = "2025-06-23T14:35:45.013Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/99/634aa686a41f899b39300c28ecca756974609e65e80e7a1b7a77765bd070/maturin-1.9.0-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:35a506c3139d6847edd160f99fd0da7c7b2bbb4d53e0fef995479eed3a92ac37", size = 8808959, upload-time = "2025-06-23T14:35:47.099Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/4d/4cfa79bad83d2722c47c058f0b527ac5f27c852845b9e79aca95e4fe09c5/maturin-1.9.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:a48d8917e60875a06ef36568c2c4a926b6e2681616a251cc50cbf0a5c8aa7428", size = 7911691, upload-time = "2025-06-23T14:35:49.768Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/8b/a9410f5ebccad93f86539ab2f77a7aabb9dd05396f9238125c946dc0798c/maturin-1.9.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:5a7a829b03415b7fcaaabeafb520a92cd32b6dd9e8d12e34c7cd7689d404e6a3", size = 7990238, upload-time = "2025-06-23T14:35:51.8Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/8c/9dd88d5a30717a01793f81ad561b4e77316e0e6154f73e8b072b9ad3378e/maturin-1.9.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:3aa8de021f91bd41918f4afd1b285e84e1b858e354b1de01597bb97a1b9820e1", size = 10134367, upload-time = "2025-06-23T14:35:54.288Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/34/bb85f46570b4ff2e7bf0dfb8c7408855df811f15d0c1a22896a4699ac0ac/maturin-1.9.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:289d0c2925a8c8ba3ce058e7b691b1c274fd06e36a915232f4e07fa62266f9b6", size = 9001993, upload-time = "2025-06-23T14:35:56.692Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mdurl"
|
||||
version = "0.1.2"
|
||||
@@ -192,6 +219,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pathlib"
|
||||
version = "1.0.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ac/aa/9b065a76b9af472437a0059f77e8f962fe350438b927cb80184c32f075eb/pathlib-1.0.1.tar.gz", hash = "sha256:6940718dfc3eff4258203ad5021090933e5c04707d5ca8cc9e73c94a7894ea9f", size = 49298, upload-time = "2014-09-03T15:41:57.18Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/78/f9/690a8600b93c332de3ab4a344a4ac34f00c8f104917061f779db6a918ed6/pathlib-1.0.1-py3-none-any.whl", hash = "sha256:f35f95ab8b0f59e6d354090350b44a80a80635d22efdedfa84c7ad1cf0a74147", size = 14363, upload-time = "2022-05-04T13:37:20.585Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.6.0"
|
||||
|
||||
Reference in New Issue
Block a user