rmi-stitch-core 0.2.0.dev2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.3
2
+ Name: rmi-stitch-core
3
+ Version: 0.2.0.dev2
4
+ Summary: Core functionality for the Stitch platform.
5
+ Author: Michael Barlow
6
+ Author-email: Michael Barlow <mbarlow@rmi.org>
7
+ Requires-Dist: pydantic>=2.14.3
8
+ Requires-Python: >=3.12
9
+ Description-Content-Type: text/markdown
10
+
@@ -0,0 +1,12 @@
1
+ stitch/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ stitch/core/_internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ stitch/core/_internal/typings/__init__.py,sha256=IsOTv49ilV0cUr43mnCy6EjH7mzKfSLRgbiZ7sbmf2Y,73
4
+ stitch/core/_internal/typings/facades.py,sha256=jEu5YGuXgOY7LblnSqzquqIT9b7F3CG_En9QdKdIEpY,1722
5
+ stitch/core/_internal/typings/models.py,sha256=ysTRgRanmoBBkC42KhUI8XxczsQ35tD7B8-3uu9qDLs,808
6
+ stitch/core/_internal/typings/repositories.py,sha256=oGn6WYSXDZMiM5I2vqkCQjJmhN_dzKXfUcYThJ2MPo4,543
7
+ stitch/core/_internal/typings/resource.py,sha256=iY8uGCQuOq9MzKc5a4rCzQZLf3jTVhzMS9oKxcerJKE,598
8
+ stitch/core/models.py,sha256=AVmPM_Ij8MNZgjaHFQZtxL9MxJ9ZfbZ4jxY56TsPMjg,3069
9
+ stitch/core/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ rmi_stitch_core-0.2.0.dev2.dist-info/WHEEL,sha256=MWx7psoF7KNST9G72pGtiBYuaXRXQXBk9pw5R6FzYko,80
11
+ rmi_stitch_core-0.2.0.dev2.dist-info/METADATA,sha256=Yak-3op6Rfl6AqGVJJe0PtASw3KqsxhkK6rlhWKQXJA,284
12
+ rmi_stitch_core-0.2.0.dev2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.13.7
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
File without changes
File without changes
@@ -0,0 +1,3 @@
1
+ from .models import IdType, JsonValue
2
+
3
+ __all__ = ["IdType", "JsonValue"]
@@ -0,0 +1,62 @@
1
+ from collections.abc import Collection, Sequence
2
+ from typing import Any, Protocol, overload
3
+
4
+ from .repositories import (
5
+ ResourceRepository,
6
+ SourceRepository,
7
+ UserRepository,
8
+ )
9
+
10
+ type ResourceQueryParams = Any
11
+ type SourceQueryParams = Any
12
+ type MergeQueryParams = Any
13
+
14
+
15
+ class PersistenceFacade[TResource, TSource, TMerge, TId](Protocol):
16
+ __resource_model__: type[TResource]
17
+ __source_model__: type[TSource]
18
+ _users: UserRepository
19
+ _resources: ResourceRepository[TResource, TId]
20
+ _sources: SourceRepository[TSource]
21
+
22
+ async def query_resources(
23
+ self, params: ResourceQueryParams, offset: int = 0, limit: int | None = None
24
+ ) -> Collection[TResource]:
25
+ pass
26
+
27
+ async def query_resource_values(self, params: ResourceQueryParams) -> Collection[TResource]:
28
+ """For filter options."""
29
+ pass
30
+
31
+ async def query_sources(
32
+ self, params: SourceQueryParams, offset: int = 0, limit: int | None = None
33
+ ) -> Collection[TSource]:
34
+ pass
35
+
36
+ async def query_merges(
37
+ self, params: MergeQueryParams, offset: int = 0, limit: int | None = None
38
+ ) -> Collection[TMerge]:
39
+ pass
40
+
41
+ async def get_resource(self, id: TId) -> TResource:
42
+ pass
43
+
44
+ async def get_source(self, id: TId) -> TSource:
45
+ pass
46
+
47
+ async def get_merge(self, id: TId) -> TMerge:
48
+ pass
49
+
50
+ @overload
51
+ async def merge_resources(self, resources: Sequence[TResource], /) -> TResource:
52
+ pass
53
+
54
+ @overload
55
+ async def merge_resources(self, ids: Sequence[TId], /) -> TResource:
56
+ pass
57
+
58
+ async def undo_merge(self, id: TId): ...
59
+
60
+ async def unmerge_resource(self, id: TId): ...
61
+
62
+ async def attach_source_to_resource(self): ...
@@ -0,0 +1,28 @@
1
+ from typing import Annotated, Protocol, runtime_checkable
2
+ from uuid import UUID
3
+
4
+ from pydantic import Field
5
+
6
+ IdType = int | UUID
7
+ type JsonValue = None | bool | int | float | str | list["JsonValue"] | dict[str, "JsonValue"]
8
+
9
+ Year = Annotated[int, Field(ge=1700, le=2100)]
10
+ FractionalPercentage = Annotated[float, Field(ge=0.0, le=100)]
11
+ Latitude = Annotated[float, Field(ge=-90.0, le=90.0)]
12
+ Longitude = Annotated[float, Field(ge=-180.0, le=180.0)]
13
+ CountryCodeAlpha3 = Annotated[str, Field(pattern=r"^[A-Z]{3}$")]
14
+
15
+
16
+ @runtime_checkable
17
+ class Identified[TId: IdType](Protocol):
18
+ @property
19
+ def id(self) -> TId: ...
20
+
21
+
22
+ @runtime_checkable
23
+ class SourceRef[TId: IdType, TSrcKey: str](Identified[TId], Protocol):
24
+ @property
25
+ def source(self) -> TSrcKey: ...
26
+
27
+
28
+ type ResourceRef[TId: IdType] = Identified[TId]
@@ -0,0 +1,25 @@
1
+ from collections.abc import Collection
2
+ from typing import Any, Protocol
3
+
4
+
5
+ class UserRepository(Protocol): ...
6
+
7
+
8
+ type Params = Any
9
+
10
+
11
+ class ResourceRepository[T, TId](Protocol):
12
+ async def create(self) -> T: ...
13
+
14
+ async def get_one(self, id: TId) -> T: ...
15
+
16
+ async def get_many(self, id: TId) -> Collection[T]: ...
17
+
18
+ async def query(
19
+ self, params: Params, limit: int = 0, offset: int | None = None
20
+ ) -> Collection[T]: ...
21
+
22
+ async def update(self, id: TId, item: T) -> T: ...
23
+
24
+
25
+ class SourceRepository[T](Protocol): ...
@@ -0,0 +1,31 @@
1
+ from typing import Literal, Protocol
2
+
3
+
4
+ class IResource[TId](Protocol):
5
+ @classmethod
6
+ def model_class(cls): ...
7
+
8
+ @property
9
+ def model(self): ...
10
+
11
+ @property
12
+ def stored(self) -> bool: ...
13
+
14
+ @property
15
+ def id_type(self) -> type[TId]: ...
16
+
17
+
18
+ class FreeResource[TId](IResource[TId], Protocol):
19
+ @property
20
+ def stored(self) -> Literal[False]: ...
21
+
22
+
23
+ class PersistedResource[TId](IResource[TId], Protocol):
24
+ @property
25
+ def id(self) -> TId: ...
26
+
27
+ @property
28
+ def stored(self) -> Literal[True]: ...
29
+
30
+
31
+ type Resource[TId] = FreeResource[TId] | PersistedResource[TId]
stitch/core/models.py ADDED
@@ -0,0 +1,102 @@
1
+ from collections.abc import Sequence
2
+ from datetime import datetime
3
+ from typing import (
4
+ ClassVar,
5
+ NamedTuple,
6
+ )
7
+
8
+ from pydantic import BaseModel, ConfigDict, Field, model_validator
9
+
10
+ from ._internal.typings import IdType, JsonValue
11
+
12
+ __all__ = [
13
+ "Resource",
14
+ "Source",
15
+ "SourcePayload",
16
+ "SourceRecord",
17
+ "SourceRefTuple",
18
+ "SourceView",
19
+ ]
20
+
21
+
22
+ class SourceRecord(BaseModel):
23
+ record_id: str | None = None
24
+ run_id: str | None = None
25
+ observed_at: datetime
26
+ producer: str
27
+ payload: JsonValue
28
+
29
+
30
+ class Source[TId: IdType, TSrcKey: str](BaseModel):
31
+ """Base class for dependent, canonical `Source` data declarations.
32
+
33
+ Used for creational patterns and to handle use cases where identifiers should NOT be present.
34
+
35
+ Attributes:
36
+ source: key for identifying the data source
37
+ """
38
+
39
+ id: TId | None = None
40
+ source: TSrcKey
41
+ source_record: SourceRecord
42
+
43
+ # we set `from_attributes=True` to accommodate ORM or other object mappings
44
+ model_config: ClassVar[ConfigDict] = ConfigDict(from_attributes=True, extra="ignore")
45
+
46
+
47
+ class SourceView[TId: IdType, TSrcKey: str](Source[TId, TSrcKey]):
48
+ source_record: SourceRecord | None = Field(default=None, exclude=True) # ty: ignore[invalid-attribute-override]
49
+
50
+
51
+ class SourcePayload(BaseModel):
52
+ """Base for domain-specific source payload containers.
53
+
54
+ Subclass and declare attributes that hold collections (for example, mappings)
55
+ of :class:`Source` instances keyed and structured as appropriate for your domain.
56
+ """
57
+
58
+ model_config: ClassVar[ConfigDict] = ConfigDict(from_attributes=True)
59
+
60
+
61
+ class SourceRefTuple[TId: IdType, TKey: str](NamedTuple):
62
+ id: TId
63
+ source: TKey
64
+
65
+
66
+ class Resource[TResId: IdType, TSrc: Source](BaseModel):
67
+ id: TResId | None = None
68
+ source_data: Sequence[TSrc] = Field(default_factory=list)
69
+ repointed_to: TResId | None = Field(default=None)
70
+ constituents: frozenset[TResId] = Field(default_factory=frozenset)
71
+
72
+ model_config = ConfigDict(extra="forbid")
73
+
74
+ @model_validator(mode="after")
75
+ def _no_self_reference(self):
76
+ if self.id is not None:
77
+ if self.id in self.constituents:
78
+ raise ValueError("A resource cannot be a constituent of itself")
79
+ if self.repointed_to == self.id:
80
+ raise ValueError("A resource cannot be repointed to itself")
81
+ return self
82
+
83
+
84
+ class Resource_[
85
+ TResId: IdType,
86
+ TPayload: SourcePayload,
87
+ ](BaseModel):
88
+ id: TResId | None = None
89
+ source_data: TPayload
90
+ repointed_to: TResId | None = Field(default=None)
91
+ constituents: frozenset[TResId] = Field(default_factory=frozenset)
92
+
93
+ model_config = ConfigDict(extra="ignore")
94
+
95
+ @model_validator(mode="after")
96
+ def _no_self_reference(self):
97
+ if self.id is not None:
98
+ if self.id in self.constituents:
99
+ raise ValueError("A resource cannot be a constituent of itself")
100
+ if self.repointed_to == self.id:
101
+ raise ValueError("A resource cannot be repointed to itself")
102
+ return self
stitch/core/py.typed ADDED
File without changes