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.
- rmi_stitch_core-0.2.0.dev2.dist-info/METADATA +10 -0
- rmi_stitch_core-0.2.0.dev2.dist-info/RECORD +12 -0
- rmi_stitch_core-0.2.0.dev2.dist-info/WHEEL +4 -0
- stitch/core/__init__.py +0 -0
- stitch/core/_internal/__init__.py +0 -0
- stitch/core/_internal/typings/__init__.py +3 -0
- stitch/core/_internal/typings/facades.py +62 -0
- stitch/core/_internal/typings/models.py +28 -0
- stitch/core/_internal/typings/repositories.py +25 -0
- stitch/core/_internal/typings/resource.py +31 -0
- stitch/core/models.py +102 -0
- stitch/core/py.typed +0 -0
|
@@ -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,,
|
stitch/core/__init__.py
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -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
|