lorebase-client 0.3.1__tar.gz → 0.4.0__tar.gz
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.
- {lorebase_client-0.3.1 → lorebase_client-0.4.0}/PKG-INFO +1 -1
- {lorebase_client-0.3.1 → lorebase_client-0.4.0}/pyproject.toml +1 -1
- {lorebase_client-0.3.1 → lorebase_client-0.4.0}/src/lorebase_client/__init__.py +1 -1
- {lorebase_client-0.3.1 → lorebase_client-0.4.0}/src/lorebase_client/aclient.py +13 -1
- {lorebase_client-0.3.1 → lorebase_client-0.4.0}/src/lorebase_client/client.py +13 -1
- {lorebase_client-0.3.1 → lorebase_client-0.4.0}/src/lorebase_client/endpoints.py +2 -0
- {lorebase_client-0.3.1 → lorebase_client-0.4.0}/src/lorebase_client/models.py +36 -1
- {lorebase_client-0.3.1 → lorebase_client-0.4.0}/tests/test_contract.py +3 -0
- {lorebase_client-0.3.1 → lorebase_client-0.4.0}/tests/test_requests.py +29 -0
- {lorebase_client-0.3.1 → lorebase_client-0.4.0}/.gitignore +0 -0
- {lorebase_client-0.3.1 → lorebase_client-0.4.0}/LICENSE +0 -0
- {lorebase_client-0.3.1 → lorebase_client-0.4.0}/README.md +0 -0
- {lorebase_client-0.3.1 → lorebase_client-0.4.0}/src/lorebase_client/config.py +0 -0
- {lorebase_client-0.3.1 → lorebase_client-0.4.0}/src/lorebase_client/core.py +0 -0
- {lorebase_client-0.3.1 → lorebase_client-0.4.0}/src/lorebase_client/errors.py +0 -0
- {lorebase_client-0.3.1 → lorebase_client-0.4.0}/src/lorebase_client/py.typed +0 -0
- {lorebase_client-0.3.1 → lorebase_client-0.4.0}/tests/integration/conftest.py +0 -0
- {lorebase_client-0.3.1 → lorebase_client-0.4.0}/tests/integration/test_flow.py +0 -0
- {lorebase_client-0.3.1 → lorebase_client-0.4.0}/tests/test_errors_and_config.py +0 -0
- {lorebase_client-0.3.1 → lorebase_client-0.4.0}/tests/test_parity.py +0 -0
- {lorebase_client-0.3.1 → lorebase_client-0.4.0}/tests/test_path_segments.py +0 -0
- {lorebase_client-0.3.1 → lorebase_client-0.4.0}/tests/test_trace.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: lorebase-client
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Lorebase client: typed sync and async HTTP client for the Keeper API
|
|
5
5
|
Project-URL: Homepage, https://github.com/aantenore/lorebase
|
|
6
6
|
Project-URL: Documentation, https://aantenore.github.io/lorebase/
|
|
@@ -710,9 +710,21 @@ class Records(_Resource):
|
|
|
710
710
|
at: str | None = None,
|
|
711
711
|
links: bool = False,
|
|
712
712
|
lineage: bool = False,
|
|
713
|
+
conflicts: bool = False,
|
|
713
714
|
) -> m.RecordResponse:
|
|
715
|
+
"""One Record. `conflicts` adds `conflict_in_progress`: the open Conflicts about it
|
|
716
|
+
in Proposals into the branch read (branch head only)."""
|
|
714
717
|
return await self._c.send(
|
|
715
|
-
ep.get_record(
|
|
718
|
+
ep.get_record(
|
|
719
|
+
domain,
|
|
720
|
+
kind,
|
|
721
|
+
key,
|
|
722
|
+
branch=branch,
|
|
723
|
+
at=at,
|
|
724
|
+
links=links,
|
|
725
|
+
lineage=lineage,
|
|
726
|
+
conflicts=conflicts,
|
|
727
|
+
)
|
|
716
728
|
)
|
|
717
729
|
|
|
718
730
|
async def history(
|
|
@@ -721,9 +721,21 @@ class Records(_Resource):
|
|
|
721
721
|
at: str | None = None,
|
|
722
722
|
links: bool = False,
|
|
723
723
|
lineage: bool = False,
|
|
724
|
+
conflicts: bool = False,
|
|
724
725
|
) -> m.RecordResponse:
|
|
726
|
+
"""One Record. `conflicts` adds `conflict_in_progress`: the open Conflicts about it
|
|
727
|
+
in Proposals into the branch read (branch head only)."""
|
|
725
728
|
return self._c.send(
|
|
726
|
-
ep.get_record(
|
|
729
|
+
ep.get_record(
|
|
730
|
+
domain,
|
|
731
|
+
kind,
|
|
732
|
+
key,
|
|
733
|
+
branch=branch,
|
|
734
|
+
at=at,
|
|
735
|
+
links=links,
|
|
736
|
+
lineage=lineage,
|
|
737
|
+
conflicts=conflicts,
|
|
738
|
+
)
|
|
727
739
|
)
|
|
728
740
|
|
|
729
741
|
def history(
|
|
@@ -471,6 +471,7 @@ def get_record(
|
|
|
471
471
|
at: str | None = None,
|
|
472
472
|
links: bool = False,
|
|
473
473
|
lineage: bool = False,
|
|
474
|
+
conflicts: bool = False,
|
|
474
475
|
) -> Call[m.RecordResponse]:
|
|
475
476
|
return Call(
|
|
476
477
|
"GET",
|
|
@@ -481,6 +482,7 @@ def get_record(
|
|
|
481
482
|
"at": at,
|
|
482
483
|
"links": links or None,
|
|
483
484
|
"lineage": lineage or None,
|
|
485
|
+
"conflicts": conflicts or None,
|
|
484
486
|
},
|
|
485
487
|
safe=True,
|
|
486
488
|
cacheable=True,
|
|
@@ -547,8 +547,22 @@ class Served(Model):
|
|
|
547
547
|
served_from: ServedFrom
|
|
548
548
|
|
|
549
549
|
|
|
550
|
+
class ConflictInProgress(Model):
|
|
551
|
+
"""A Conflict about a Record that waits for a decision in an open Proposal into the
|
|
552
|
+
branch read; `attribute` is None when the whole Record is contested."""
|
|
553
|
+
|
|
554
|
+
proposal_id: str
|
|
555
|
+
conflict_id: str
|
|
556
|
+
code: str
|
|
557
|
+
attribute: str | None = None
|
|
558
|
+
competing_hash: str | None = None
|
|
559
|
+
|
|
560
|
+
|
|
550
561
|
class RecordResponse(Served):
|
|
562
|
+
"""A Record. `conflict_in_progress` is set when the read asked for it (`conflicts`)."""
|
|
563
|
+
|
|
551
564
|
record: RecordItem
|
|
565
|
+
conflict_in_progress: list[ConflictInProgress] | None = None
|
|
552
566
|
|
|
553
567
|
|
|
554
568
|
class RecordPage(Served):
|
|
@@ -1155,14 +1169,35 @@ class Attempt(Model):
|
|
|
1155
1169
|
auto_resolutions: list[dict[str, Any]]
|
|
1156
1170
|
|
|
1157
1171
|
|
|
1172
|
+
class SideProvenance(Model):
|
|
1173
|
+
"""Who or what last changed the Attribute of a Conflict on one side, and when."""
|
|
1174
|
+
|
|
1175
|
+
provenance_id: str
|
|
1176
|
+
at: datetime
|
|
1177
|
+
actor: str | None = None
|
|
1178
|
+
origin: str | None = None
|
|
1179
|
+
source: str | None = None
|
|
1180
|
+
principal: str | None = None
|
|
1181
|
+
labels: list[str] = Field(default_factory=list)
|
|
1182
|
+
|
|
1183
|
+
|
|
1184
|
+
class ConflictProvenance(Model):
|
|
1185
|
+
"""The provenance of the two competing sides; `None` for a side of unknown Lineage."""
|
|
1186
|
+
|
|
1187
|
+
ours: SideProvenance | None = None
|
|
1188
|
+
theirs: SideProvenance | None = None
|
|
1189
|
+
|
|
1190
|
+
|
|
1158
1191
|
class ConflictItem(Model):
|
|
1159
|
-
"""A Conflict: `id`, `status` and the members the merge engine reports.
|
|
1192
|
+
"""A Conflict: `id`, `status` and the members the merge engine reports. `provenance` is
|
|
1193
|
+
set on Attribute Conflicts whose merge knew who changed a side."""
|
|
1160
1194
|
|
|
1161
1195
|
model_config = ConfigDict(extra="allow")
|
|
1162
1196
|
|
|
1163
1197
|
id: str
|
|
1164
1198
|
status: ConflictStatus
|
|
1165
1199
|
resolution: dict[str, Any] | None = None
|
|
1200
|
+
provenance: ConflictProvenance | None = None
|
|
1166
1201
|
|
|
1167
1202
|
|
|
1168
1203
|
class Conflicts(Attempt):
|
|
@@ -64,6 +64,7 @@ RESPONSES: dict[type[m.Model], str] = {
|
|
|
64
64
|
m.ExtractionEvidence: "ExtractionEvidenceOut",
|
|
65
65
|
m.ValueLineage: "ValueLineageOut",
|
|
66
66
|
m.RecordLineage: "RecordLineageOut",
|
|
67
|
+
m.ConflictInProgress: "ConflictInProgressOut",
|
|
67
68
|
m.RecordResponse: "RecordResponse",
|
|
68
69
|
m.RecordPage: "RecordPage",
|
|
69
70
|
m.AggregateCount: "AggregateCount",
|
|
@@ -117,6 +118,8 @@ RESPONSES: dict[type[m.Model], str] = {
|
|
|
117
118
|
m.Approval: "ApprovalOut",
|
|
118
119
|
m.Resolution: "ResolutionOut",
|
|
119
120
|
m.Attempt: "AttemptOut",
|
|
121
|
+
m.SideProvenance: "SideProvenanceOut",
|
|
122
|
+
m.ConflictProvenance: "ConflictProvenanceOut",
|
|
120
123
|
m.ConflictItem: "ConflictOut",
|
|
121
124
|
m.Conflicts: "ConflictsOut",
|
|
122
125
|
m.Proposal: "lorebase_keeper__routes__proposals__ProposalOut",
|
|
@@ -590,6 +590,35 @@ RECORD = {
|
|
|
590
590
|
}
|
|
591
591
|
|
|
592
592
|
|
|
593
|
+
def test_a_record_read_can_ask_for_pending_conflicts(
|
|
594
|
+
http: respx.MockRouter, sleeps: Sleeps
|
|
595
|
+
) -> None:
|
|
596
|
+
pending = {
|
|
597
|
+
"proposal_id": "prp_1",
|
|
598
|
+
"conflict_id": "ab" * 32,
|
|
599
|
+
"code": "DV1",
|
|
600
|
+
"attribute": "risk_score",
|
|
601
|
+
"competing_hash": "cd" * 32,
|
|
602
|
+
}
|
|
603
|
+
route = http.get("/domains/supply/kinds/Part/records/P-1").mock(
|
|
604
|
+
side_effect=lambda request: httpx.Response(
|
|
605
|
+
200,
|
|
606
|
+
json={**RECORD, "conflict_in_progress": [pending]}
|
|
607
|
+
if "conflicts" in request.url.params
|
|
608
|
+
else RECORD,
|
|
609
|
+
)
|
|
610
|
+
)
|
|
611
|
+
with make(sleeps) as client:
|
|
612
|
+
plain = client.records.get("supply", "Part", "P-1")
|
|
613
|
+
asked = client.records.get("supply", "Part", "P-1", conflicts=True)
|
|
614
|
+
assert "conflicts" not in route.calls[0].request.url.params
|
|
615
|
+
assert route.calls[1].request.url.params["conflicts"] == "true"
|
|
616
|
+
assert plain.conflict_in_progress is None
|
|
617
|
+
assert asked.conflict_in_progress is not None
|
|
618
|
+
[marker] = asked.conflict_in_progress
|
|
619
|
+
assert marker.model_dump() == pending
|
|
620
|
+
|
|
621
|
+
|
|
593
622
|
def test_etag_cache_revalidates_and_serves_the_cached_body(
|
|
594
623
|
http: respx.MockRouter, sleeps: Sleeps
|
|
595
624
|
) -> None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|