lorebase-client 0.2.3__tar.gz → 0.3.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.2.3 → lorebase_client-0.3.0}/PKG-INFO +1 -1
- {lorebase_client-0.2.3 → lorebase_client-0.3.0}/pyproject.toml +1 -1
- {lorebase_client-0.2.3 → lorebase_client-0.3.0}/src/lorebase_client/__init__.py +1 -1
- {lorebase_client-0.2.3 → lorebase_client-0.3.0}/src/lorebase_client/aclient.py +206 -9
- {lorebase_client-0.2.3 → lorebase_client-0.3.0}/src/lorebase_client/client.py +201 -10
- {lorebase_client-0.2.3 → lorebase_client-0.3.0}/src/lorebase_client/endpoints.py +154 -1
- {lorebase_client-0.2.3 → lorebase_client-0.3.0}/src/lorebase_client/models.py +355 -8
- {lorebase_client-0.2.3 → lorebase_client-0.3.0}/tests/test_contract.py +42 -2
- lorebase_client-0.3.0/tests/test_parity.py +62 -0
- {lorebase_client-0.2.3 → lorebase_client-0.3.0}/tests/test_requests.py +326 -1
- {lorebase_client-0.2.3 → lorebase_client-0.3.0}/.gitignore +0 -0
- {lorebase_client-0.2.3 → lorebase_client-0.3.0}/src/lorebase_client/config.py +0 -0
- {lorebase_client-0.2.3 → lorebase_client-0.3.0}/src/lorebase_client/core.py +0 -0
- {lorebase_client-0.2.3 → lorebase_client-0.3.0}/src/lorebase_client/errors.py +0 -0
- {lorebase_client-0.2.3 → lorebase_client-0.3.0}/src/lorebase_client/py.typed +0 -0
- {lorebase_client-0.2.3 → lorebase_client-0.3.0}/tests/integration/conftest.py +0 -0
- {lorebase_client-0.2.3 → lorebase_client-0.3.0}/tests/integration/test_flow.py +0 -0
- {lorebase_client-0.2.3 → lorebase_client-0.3.0}/tests/test_errors_and_config.py +0 -0
- {lorebase_client-0.2.3 → lorebase_client-0.3.0}/tests/test_path_segments.py +0 -0
- {lorebase_client-0.2.3 → lorebase_client-0.3.0}/tests/test_trace.py +0 -0
|
@@ -106,6 +106,7 @@ class AsyncLorebaseClient:
|
|
|
106
106
|
self.proposals = Proposals(self)
|
|
107
107
|
self.agents = Agents(self)
|
|
108
108
|
self.webhooks = Webhooks(self)
|
|
109
|
+
self.reconciliation = Reconciliation(self)
|
|
109
110
|
self.sources = Sources(self)
|
|
110
111
|
self.documents = Documents(self)
|
|
111
112
|
|
|
@@ -181,6 +182,11 @@ class AsyncLorebaseClient:
|
|
|
181
182
|
"""The authenticated caller."""
|
|
182
183
|
return await self.send(ep.whoami())
|
|
183
184
|
|
|
185
|
+
async def session_info(self) -> m.SessionInfo:
|
|
186
|
+
"""The browser session the caller's session cookie belongs to; 401 for API keys and
|
|
187
|
+
bearer tokens."""
|
|
188
|
+
return await self.send(ep.session_info())
|
|
189
|
+
|
|
184
190
|
def idempotency_key(self, prefix: str | None = None) -> str:
|
|
185
191
|
"""A fresh key to pass as `idempotency_key=` to a write."""
|
|
186
192
|
return new_idempotency_key(prefix)
|
|
@@ -228,9 +234,14 @@ class Domains(_Resource):
|
|
|
228
234
|
return await self._c.send(ep.get_domain(id))
|
|
229
235
|
|
|
230
236
|
async def update(
|
|
231
|
-
self,
|
|
237
|
+
self,
|
|
238
|
+
id: str,
|
|
239
|
+
*,
|
|
240
|
+
display_name: str | None = None,
|
|
241
|
+
description: str | None = None,
|
|
242
|
+
settings: JsonObject | None = None,
|
|
232
243
|
) -> m.Domain:
|
|
233
|
-
body = m.DomainUpdate(display_name=display_name, description=description)
|
|
244
|
+
body = m.DomainUpdate(display_name=display_name, description=description, settings=settings)
|
|
234
245
|
return await self._c.send(ep.update_domain(id, body))
|
|
235
246
|
|
|
236
247
|
async def delete(self, id: str, *, confirm: str) -> None:
|
|
@@ -854,6 +865,10 @@ class Records(_Resource):
|
|
|
854
865
|
body = request if isinstance(request, m.SearchRequest) else m.SearchRequest(**request)
|
|
855
866
|
return await self._c.send(ep.search(domain, body))
|
|
856
867
|
|
|
868
|
+
async def resolve(self, domain: str, request: m.ResolveRequest | JsonObject) -> m.ResolveResult:
|
|
869
|
+
body = request if isinstance(request, m.ResolveRequest) else m.ResolveRequest(**request)
|
|
870
|
+
return await self._c.send(ep.resolve(domain, body))
|
|
871
|
+
|
|
857
872
|
|
|
858
873
|
def _query_request(query: m.QueryRequest | JsonObject) -> m.QueryRequest:
|
|
859
874
|
return query if isinstance(query, m.QueryRequest) else m.QueryRequest(**query)
|
|
@@ -1189,6 +1204,7 @@ class Proposals(_Resource):
|
|
|
1189
1204
|
source: str | None = None,
|
|
1190
1205
|
target: str | None = None,
|
|
1191
1206
|
author: str | None = None,
|
|
1207
|
+
assigned: m.AssignedFilter | None = None,
|
|
1192
1208
|
limit: int | None = None,
|
|
1193
1209
|
cursor: str | None = None,
|
|
1194
1210
|
) -> m.Page[m.Proposal]:
|
|
@@ -1200,6 +1216,7 @@ class Proposals(_Resource):
|
|
|
1200
1216
|
source=source,
|
|
1201
1217
|
target=target,
|
|
1202
1218
|
author=author,
|
|
1219
|
+
assigned=assigned,
|
|
1203
1220
|
limit=limit,
|
|
1204
1221
|
cursor=cursor,
|
|
1205
1222
|
)
|
|
@@ -1214,6 +1231,7 @@ class Proposals(_Resource):
|
|
|
1214
1231
|
source: str | None = None,
|
|
1215
1232
|
target: str | None = None,
|
|
1216
1233
|
author: str | None = None,
|
|
1234
|
+
assigned: m.AssignedFilter | None = None,
|
|
1217
1235
|
page_size: int | None = None,
|
|
1218
1236
|
) -> AsyncIterator[m.Proposal]:
|
|
1219
1237
|
call = ep.list_proposals(
|
|
@@ -1223,6 +1241,7 @@ class Proposals(_Resource):
|
|
|
1223
1241
|
source=source,
|
|
1224
1242
|
target=target,
|
|
1225
1243
|
author=author,
|
|
1244
|
+
assigned=assigned,
|
|
1226
1245
|
limit=page_size,
|
|
1227
1246
|
)
|
|
1228
1247
|
return self._c.iterate(call)
|
|
@@ -1515,6 +1534,141 @@ class Webhooks(_Resource):
|
|
|
1515
1534
|
await self._c.send(ep.delete_webhook(domain, webhook_id))
|
|
1516
1535
|
|
|
1517
1536
|
|
|
1537
|
+
class Reconciliation(_Resource):
|
|
1538
|
+
async def list(
|
|
1539
|
+
self,
|
|
1540
|
+
domain: str,
|
|
1541
|
+
*,
|
|
1542
|
+
branch: str | None = None,
|
|
1543
|
+
type: m.CandidateType | None = None,
|
|
1544
|
+
status: m.CandidateStatus | None = None,
|
|
1545
|
+
kind: str | None = None,
|
|
1546
|
+
attribute: str | None = None,
|
|
1547
|
+
source: str | None = None,
|
|
1548
|
+
assigned: m.AssignedFilter | None = None,
|
|
1549
|
+
limit: int | None = None,
|
|
1550
|
+
cursor: str | None = None,
|
|
1551
|
+
) -> m.Page[m.ReconciliationCandidate]:
|
|
1552
|
+
"""One page of the candidates of a branch (default: the default branch), newest first.
|
|
1553
|
+
|
|
1554
|
+
`kind` and `attribute` are api names (`attribute` needs `kind`); `source` is a Source
|
|
1555
|
+
identity such as `source:src_...`."""
|
|
1556
|
+
return await self._c.send(
|
|
1557
|
+
ep.list_reconciliation(
|
|
1558
|
+
domain,
|
|
1559
|
+
branch=branch,
|
|
1560
|
+
type=type,
|
|
1561
|
+
status=status,
|
|
1562
|
+
kind=kind,
|
|
1563
|
+
attribute=attribute,
|
|
1564
|
+
source=source,
|
|
1565
|
+
assigned=assigned,
|
|
1566
|
+
limit=limit,
|
|
1567
|
+
cursor=cursor,
|
|
1568
|
+
)
|
|
1569
|
+
)
|
|
1570
|
+
|
|
1571
|
+
def iterate(
|
|
1572
|
+
self,
|
|
1573
|
+
domain: str,
|
|
1574
|
+
*,
|
|
1575
|
+
branch: str | None = None,
|
|
1576
|
+
type: m.CandidateType | None = None,
|
|
1577
|
+
status: m.CandidateStatus | None = None,
|
|
1578
|
+
kind: str | None = None,
|
|
1579
|
+
attribute: str | None = None,
|
|
1580
|
+
source: str | None = None,
|
|
1581
|
+
assigned: m.AssignedFilter | None = None,
|
|
1582
|
+
page_size: int | None = None,
|
|
1583
|
+
) -> AsyncIterator[m.ReconciliationCandidate]:
|
|
1584
|
+
call = ep.list_reconciliation(
|
|
1585
|
+
domain,
|
|
1586
|
+
branch=branch,
|
|
1587
|
+
type=type,
|
|
1588
|
+
status=status,
|
|
1589
|
+
kind=kind,
|
|
1590
|
+
attribute=attribute,
|
|
1591
|
+
source=source,
|
|
1592
|
+
assigned=assigned,
|
|
1593
|
+
limit=page_size,
|
|
1594
|
+
)
|
|
1595
|
+
return self._c.iterate(call)
|
|
1596
|
+
|
|
1597
|
+
async def get(self, domain: str, candidate_id: str) -> m.ReconciliationCandidate:
|
|
1598
|
+
return await self._c.send(ep.get_reconciliation_candidate(domain, candidate_id))
|
|
1599
|
+
|
|
1600
|
+
async def resolve(
|
|
1601
|
+
self,
|
|
1602
|
+
domain: str,
|
|
1603
|
+
candidate_id: str,
|
|
1604
|
+
choice: m.ReconcileChoice,
|
|
1605
|
+
*,
|
|
1606
|
+
reason: str,
|
|
1607
|
+
claim_id: str | None = None,
|
|
1608
|
+
value: JsonValue = None,
|
|
1609
|
+
policy: m.ReconcilePolicy | None = None,
|
|
1610
|
+
survivor: str | None = None,
|
|
1611
|
+
values: JsonObject | None = None,
|
|
1612
|
+
dry_run: bool = False,
|
|
1613
|
+
) -> m.ReconciliationDecision:
|
|
1614
|
+
"""Decide a candidate; `dry_run` reports the outcome without writing."""
|
|
1615
|
+
body = m.ReconcileIn(
|
|
1616
|
+
choice=choice,
|
|
1617
|
+
claim_id=claim_id,
|
|
1618
|
+
value=value,
|
|
1619
|
+
policy=policy,
|
|
1620
|
+
survivor=survivor,
|
|
1621
|
+
values=None if values is None else dict(values),
|
|
1622
|
+
reason=reason,
|
|
1623
|
+
dry_run=dry_run,
|
|
1624
|
+
)
|
|
1625
|
+
return await self._c.send(ep.resolve_reconciliation(domain, candidate_id, body))
|
|
1626
|
+
|
|
1627
|
+
async def dismiss(
|
|
1628
|
+
self, domain: str, candidate_id: str, *, reason: str
|
|
1629
|
+
) -> m.ReconciliationDecision:
|
|
1630
|
+
return await self._c.send(
|
|
1631
|
+
ep.dismiss_reconciliation(domain, candidate_id, m.ReasonIn(reason=reason))
|
|
1632
|
+
)
|
|
1633
|
+
|
|
1634
|
+
async def reopen(
|
|
1635
|
+
self, domain: str, candidate_id: str, *, reason: str
|
|
1636
|
+
) -> m.ReconciliationDecision:
|
|
1637
|
+
"""Reopen a dismissed candidate."""
|
|
1638
|
+
return await self._c.send(
|
|
1639
|
+
ep.reopen_reconciliation(domain, candidate_id, m.ReasonIn(reason=reason))
|
|
1640
|
+
)
|
|
1641
|
+
|
|
1642
|
+
async def resolve_many(
|
|
1643
|
+
self,
|
|
1644
|
+
domain: str,
|
|
1645
|
+
choice: m.BulkChoice,
|
|
1646
|
+
*,
|
|
1647
|
+
reason: str,
|
|
1648
|
+
ids: Sequence[str] | None = None,
|
|
1649
|
+
where: m.ReconcileFilter | None = None,
|
|
1650
|
+
policy: m.ReconcilePolicy | None = None,
|
|
1651
|
+
dry_run: bool = False,
|
|
1652
|
+
) -> m.BulkDecision:
|
|
1653
|
+
"""Apply a policy or a dismissal to the `ids` given, or to the open candidates
|
|
1654
|
+
matching `where`."""
|
|
1655
|
+
body = m.BulkReconcileIn(
|
|
1656
|
+
ids=None if ids is None else list(ids),
|
|
1657
|
+
filter=where,
|
|
1658
|
+
choice=choice,
|
|
1659
|
+
policy=policy,
|
|
1660
|
+
reason=reason,
|
|
1661
|
+
dry_run=dry_run,
|
|
1662
|
+
)
|
|
1663
|
+
return await self._c.send(ep.resolve_reconciliation_bulk(domain, body))
|
|
1664
|
+
|
|
1665
|
+
async def queue_sweep(self, domain: str, *, branch: str | None = None) -> m.SweepQueued:
|
|
1666
|
+
"""Queue a reconciliation sweep of a branch for Scribe."""
|
|
1667
|
+
return await self._c.send(
|
|
1668
|
+
ep.queue_reconciliation_sweep(domain, m.SweepRequest(branch=branch))
|
|
1669
|
+
)
|
|
1670
|
+
|
|
1671
|
+
|
|
1518
1672
|
class Sources(_Resource):
|
|
1519
1673
|
async def sync(self, domain: str, source: str, *, dry_run: bool = False) -> m.SyncQueued:
|
|
1520
1674
|
"""Queue a synchronization of the Source (id or api name) for Scribe."""
|
|
@@ -1541,12 +1695,16 @@ class Documents(_Resource):
|
|
|
1541
1695
|
filename: str | None = None,
|
|
1542
1696
|
extract: bool | None = None,
|
|
1543
1697
|
tiers: Sequence[int] | None = None,
|
|
1698
|
+
source_key: str | None = None,
|
|
1544
1699
|
) -> m.DocumentUploaded:
|
|
1545
1700
|
"""Upload a document: a path (named after the file) or bytes with a `filename`.
|
|
1546
1701
|
|
|
1547
|
-
`extract` (server default: true) queues an extraction job on the given `tiers`.
|
|
1702
|
+
`extract` (server default: true) queues an extraction job on the given `tiers`. With a
|
|
1703
|
+
`source_key`, new bytes become a new version of the key's document.
|
|
1548
1704
|
"""
|
|
1549
|
-
call = ep.upload_document(
|
|
1705
|
+
call = ep.upload_document(
|
|
1706
|
+
domain, file, filename=filename, extract=extract, tiers=tiers, source_key=source_key
|
|
1707
|
+
)
|
|
1550
1708
|
return await self._c.send(call)
|
|
1551
1709
|
|
|
1552
1710
|
async def upload_url(
|
|
@@ -1557,6 +1715,7 @@ class Documents(_Resource):
|
|
|
1557
1715
|
filename: str | None = None,
|
|
1558
1716
|
extract: bool | None = None,
|
|
1559
1717
|
tiers: Sequence[int] | None = None,
|
|
1718
|
+
source_key: str | None = None,
|
|
1560
1719
|
) -> m.DocumentUploaded:
|
|
1561
1720
|
"""Have the server fetch the document from an allowlisted URL."""
|
|
1562
1721
|
body = m.DocumentFromUrl(
|
|
@@ -1564,17 +1723,55 @@ class Documents(_Resource):
|
|
|
1564
1723
|
filename=filename,
|
|
1565
1724
|
extract=extract,
|
|
1566
1725
|
tiers=None if tiers is None else list(tiers),
|
|
1726
|
+
source_key=source_key,
|
|
1567
1727
|
)
|
|
1568
1728
|
return await self._c.send(ep.upload_document_url(domain, body))
|
|
1569
1729
|
|
|
1570
1730
|
async def list(
|
|
1571
|
-
self,
|
|
1731
|
+
self,
|
|
1732
|
+
domain: str,
|
|
1733
|
+
*,
|
|
1734
|
+
source_key: str | None = None,
|
|
1735
|
+
limit: int | None = None,
|
|
1736
|
+
cursor: str | None = None,
|
|
1572
1737
|
) -> m.Page[m.Document]:
|
|
1573
|
-
"""Documents of the Domain, newest first."""
|
|
1574
|
-
|
|
1738
|
+
"""Documents of the Domain, newest first; `source_key` keeps one key's versions."""
|
|
1739
|
+
call = ep.list_documents(domain, source_key=source_key, limit=limit, cursor=cursor)
|
|
1740
|
+
return await self._c.send(call)
|
|
1741
|
+
|
|
1742
|
+
def iterate(
|
|
1743
|
+
self, domain: str, *, source_key: str | None = None, page_size: int | None = None
|
|
1744
|
+
) -> AsyncIterator[m.Document]:
|
|
1745
|
+
return self._c.iterate(ep.list_documents(domain, source_key=source_key, limit=page_size))
|
|
1746
|
+
|
|
1747
|
+
async def versions(
|
|
1748
|
+
self,
|
|
1749
|
+
domain: str,
|
|
1750
|
+
document_id: str,
|
|
1751
|
+
*,
|
|
1752
|
+
limit: int | None = None,
|
|
1753
|
+
cursor: str | None = None,
|
|
1754
|
+
) -> m.Page[m.Document]:
|
|
1755
|
+
"""The versions of the document's source key, newest first."""
|
|
1756
|
+
call = ep.document_versions(domain, document_id, limit=limit, cursor=cursor)
|
|
1757
|
+
return await self._c.send(call)
|
|
1575
1758
|
|
|
1576
|
-
def
|
|
1577
|
-
|
|
1759
|
+
def iterate_versions(
|
|
1760
|
+
self, domain: str, document_id: str, *, page_size: int | None = None
|
|
1761
|
+
) -> AsyncIterator[m.Document]:
|
|
1762
|
+
return self._c.iterate(ep.document_versions(domain, document_id, limit=page_size))
|
|
1763
|
+
|
|
1764
|
+
async def retire(self, domain: str, document_id: str, *, reason: str) -> m.DocumentRetired:
|
|
1765
|
+
"""Retire a document: it is never extracted again and its evidence is re-checked."""
|
|
1766
|
+
body = m.RetirementIn(reason=reason)
|
|
1767
|
+
return await self._c.send(ep.retire_document(domain, document_id, body))
|
|
1768
|
+
|
|
1769
|
+
async def purge(
|
|
1770
|
+
self, domain: str, document_id: str, *, reason: str, confirm: str
|
|
1771
|
+
) -> m.DocumentPurged:
|
|
1772
|
+
"""Irreversibly delete the content of a document; `confirm` is the document id."""
|
|
1773
|
+
body = m.PurgeIn(reason=reason, confirm=confirm)
|
|
1774
|
+
return await self._c.send(ep.purge_document(domain, document_id, body))
|
|
1578
1775
|
|
|
1579
1776
|
async def get(self, domain: str, document_id: str) -> m.DocumentDetail:
|
|
1580
1777
|
"""A document with its extraction jobs."""
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
Resources group the endpoints (`domains`, `branches`, `tags`, `schema`, `operations`, `records`,
|
|
9
9
|
`revisions`, `api_keys`, `grants`, `principals`, `audit`, `proposals`, `agents`, `webhooks`,
|
|
10
|
-
`sources`, `documents`).
|
|
10
|
+
`reconciliation`, `sources`, `documents`).
|
|
11
11
|
Every list endpoint has a `list` method (one page) and an `iterate` method (all items; pages
|
|
12
12
|
are fetched on demand). Errors
|
|
13
13
|
raise subclasses of `LorebaseError`. The request layer is shared with `AsyncLorebaseClient`
|
|
@@ -126,6 +126,7 @@ class LorebaseClient:
|
|
|
126
126
|
self.proposals = Proposals(self)
|
|
127
127
|
self.agents = Agents(self)
|
|
128
128
|
self.webhooks = Webhooks(self)
|
|
129
|
+
self.reconciliation = Reconciliation(self)
|
|
129
130
|
self.sources = Sources(self)
|
|
130
131
|
self.documents = Documents(self)
|
|
131
132
|
|
|
@@ -200,6 +201,11 @@ class LorebaseClient:
|
|
|
200
201
|
"""The authenticated caller."""
|
|
201
202
|
return self.send(ep.whoami())
|
|
202
203
|
|
|
204
|
+
def session_info(self) -> m.SessionInfo:
|
|
205
|
+
"""The browser session the caller's session cookie belongs to; 401 for API keys and
|
|
206
|
+
bearer tokens."""
|
|
207
|
+
return self.send(ep.session_info())
|
|
208
|
+
|
|
203
209
|
def idempotency_key(self, prefix: str | None = None) -> str:
|
|
204
210
|
"""A fresh key to pass as `idempotency_key=` to a write."""
|
|
205
211
|
return new_idempotency_key(prefix)
|
|
@@ -245,9 +251,14 @@ class Domains(_Resource):
|
|
|
245
251
|
return self._c.send(ep.get_domain(id))
|
|
246
252
|
|
|
247
253
|
def update(
|
|
248
|
-
self,
|
|
254
|
+
self,
|
|
255
|
+
id: str,
|
|
256
|
+
*,
|
|
257
|
+
display_name: str | None = None,
|
|
258
|
+
description: str | None = None,
|
|
259
|
+
settings: JsonObject | None = None,
|
|
249
260
|
) -> m.Domain:
|
|
250
|
-
body = m.DomainUpdate(display_name=display_name, description=description)
|
|
261
|
+
body = m.DomainUpdate(display_name=display_name, description=description, settings=settings)
|
|
251
262
|
return self._c.send(ep.update_domain(id, body))
|
|
252
263
|
|
|
253
264
|
def delete(self, id: str, *, confirm: str) -> None:
|
|
@@ -862,6 +873,10 @@ class Records(_Resource):
|
|
|
862
873
|
body = request if isinstance(request, m.SearchRequest) else m.SearchRequest(**request)
|
|
863
874
|
return self._c.send(ep.search(domain, body))
|
|
864
875
|
|
|
876
|
+
def resolve(self, domain: str, request: m.ResolveRequest | JsonObject) -> m.ResolveResult:
|
|
877
|
+
body = request if isinstance(request, m.ResolveRequest) else m.ResolveRequest(**request)
|
|
878
|
+
return self._c.send(ep.resolve(domain, body))
|
|
879
|
+
|
|
865
880
|
|
|
866
881
|
def _query_request(query: m.QueryRequest | JsonObject) -> m.QueryRequest:
|
|
867
882
|
return query if isinstance(query, m.QueryRequest) else m.QueryRequest(**query)
|
|
@@ -1195,6 +1210,7 @@ class Proposals(_Resource):
|
|
|
1195
1210
|
source: str | None = None,
|
|
1196
1211
|
target: str | None = None,
|
|
1197
1212
|
author: str | None = None,
|
|
1213
|
+
assigned: m.AssignedFilter | None = None,
|
|
1198
1214
|
limit: int | None = None,
|
|
1199
1215
|
cursor: str | None = None,
|
|
1200
1216
|
) -> m.Page[m.Proposal]:
|
|
@@ -1206,6 +1222,7 @@ class Proposals(_Resource):
|
|
|
1206
1222
|
source=source,
|
|
1207
1223
|
target=target,
|
|
1208
1224
|
author=author,
|
|
1225
|
+
assigned=assigned,
|
|
1209
1226
|
limit=limit,
|
|
1210
1227
|
cursor=cursor,
|
|
1211
1228
|
)
|
|
@@ -1220,6 +1237,7 @@ class Proposals(_Resource):
|
|
|
1220
1237
|
source: str | None = None,
|
|
1221
1238
|
target: str | None = None,
|
|
1222
1239
|
author: str | None = None,
|
|
1240
|
+
assigned: m.AssignedFilter | None = None,
|
|
1223
1241
|
page_size: int | None = None,
|
|
1224
1242
|
) -> Iterator[m.Proposal]:
|
|
1225
1243
|
call = ep.list_proposals(
|
|
@@ -1229,6 +1247,7 @@ class Proposals(_Resource):
|
|
|
1229
1247
|
source=source,
|
|
1230
1248
|
target=target,
|
|
1231
1249
|
author=author,
|
|
1250
|
+
assigned=assigned,
|
|
1232
1251
|
limit=page_size,
|
|
1233
1252
|
)
|
|
1234
1253
|
return self._c.iterate(call)
|
|
@@ -1517,6 +1536,135 @@ class Webhooks(_Resource):
|
|
|
1517
1536
|
self._c.send(ep.delete_webhook(domain, webhook_id))
|
|
1518
1537
|
|
|
1519
1538
|
|
|
1539
|
+
class Reconciliation(_Resource):
|
|
1540
|
+
def list(
|
|
1541
|
+
self,
|
|
1542
|
+
domain: str,
|
|
1543
|
+
*,
|
|
1544
|
+
branch: str | None = None,
|
|
1545
|
+
type: m.CandidateType | None = None,
|
|
1546
|
+
status: m.CandidateStatus | None = None,
|
|
1547
|
+
kind: str | None = None,
|
|
1548
|
+
attribute: str | None = None,
|
|
1549
|
+
source: str | None = None,
|
|
1550
|
+
assigned: m.AssignedFilter | None = None,
|
|
1551
|
+
limit: int | None = None,
|
|
1552
|
+
cursor: str | None = None,
|
|
1553
|
+
) -> m.Page[m.ReconciliationCandidate]:
|
|
1554
|
+
"""One page of the candidates of a branch (default: the default branch), newest first.
|
|
1555
|
+
|
|
1556
|
+
`kind` and `attribute` are api names (`attribute` needs `kind`); `source` is a Source
|
|
1557
|
+
identity such as `source:src_...`."""
|
|
1558
|
+
return self._c.send(
|
|
1559
|
+
ep.list_reconciliation(
|
|
1560
|
+
domain,
|
|
1561
|
+
branch=branch,
|
|
1562
|
+
type=type,
|
|
1563
|
+
status=status,
|
|
1564
|
+
kind=kind,
|
|
1565
|
+
attribute=attribute,
|
|
1566
|
+
source=source,
|
|
1567
|
+
assigned=assigned,
|
|
1568
|
+
limit=limit,
|
|
1569
|
+
cursor=cursor,
|
|
1570
|
+
)
|
|
1571
|
+
)
|
|
1572
|
+
|
|
1573
|
+
def iterate(
|
|
1574
|
+
self,
|
|
1575
|
+
domain: str,
|
|
1576
|
+
*,
|
|
1577
|
+
branch: str | None = None,
|
|
1578
|
+
type: m.CandidateType | None = None,
|
|
1579
|
+
status: m.CandidateStatus | None = None,
|
|
1580
|
+
kind: str | None = None,
|
|
1581
|
+
attribute: str | None = None,
|
|
1582
|
+
source: str | None = None,
|
|
1583
|
+
assigned: m.AssignedFilter | None = None,
|
|
1584
|
+
page_size: int | None = None,
|
|
1585
|
+
) -> Iterator[m.ReconciliationCandidate]:
|
|
1586
|
+
call = ep.list_reconciliation(
|
|
1587
|
+
domain,
|
|
1588
|
+
branch=branch,
|
|
1589
|
+
type=type,
|
|
1590
|
+
status=status,
|
|
1591
|
+
kind=kind,
|
|
1592
|
+
attribute=attribute,
|
|
1593
|
+
source=source,
|
|
1594
|
+
assigned=assigned,
|
|
1595
|
+
limit=page_size,
|
|
1596
|
+
)
|
|
1597
|
+
return self._c.iterate(call)
|
|
1598
|
+
|
|
1599
|
+
def get(self, domain: str, candidate_id: str) -> m.ReconciliationCandidate:
|
|
1600
|
+
return self._c.send(ep.get_reconciliation_candidate(domain, candidate_id))
|
|
1601
|
+
|
|
1602
|
+
def resolve(
|
|
1603
|
+
self,
|
|
1604
|
+
domain: str,
|
|
1605
|
+
candidate_id: str,
|
|
1606
|
+
choice: m.ReconcileChoice,
|
|
1607
|
+
*,
|
|
1608
|
+
reason: str,
|
|
1609
|
+
claim_id: str | None = None,
|
|
1610
|
+
value: JsonValue = None,
|
|
1611
|
+
policy: m.ReconcilePolicy | None = None,
|
|
1612
|
+
survivor: str | None = None,
|
|
1613
|
+
values: JsonObject | None = None,
|
|
1614
|
+
dry_run: bool = False,
|
|
1615
|
+
) -> m.ReconciliationDecision:
|
|
1616
|
+
"""Decide a candidate; `dry_run` reports the outcome without writing."""
|
|
1617
|
+
body = m.ReconcileIn(
|
|
1618
|
+
choice=choice,
|
|
1619
|
+
claim_id=claim_id,
|
|
1620
|
+
value=value,
|
|
1621
|
+
policy=policy,
|
|
1622
|
+
survivor=survivor,
|
|
1623
|
+
values=None if values is None else dict(values),
|
|
1624
|
+
reason=reason,
|
|
1625
|
+
dry_run=dry_run,
|
|
1626
|
+
)
|
|
1627
|
+
return self._c.send(ep.resolve_reconciliation(domain, candidate_id, body))
|
|
1628
|
+
|
|
1629
|
+
def dismiss(self, domain: str, candidate_id: str, *, reason: str) -> m.ReconciliationDecision:
|
|
1630
|
+
return self._c.send(
|
|
1631
|
+
ep.dismiss_reconciliation(domain, candidate_id, m.ReasonIn(reason=reason))
|
|
1632
|
+
)
|
|
1633
|
+
|
|
1634
|
+
def reopen(self, domain: str, candidate_id: str, *, reason: str) -> m.ReconciliationDecision:
|
|
1635
|
+
"""Reopen a dismissed candidate."""
|
|
1636
|
+
return self._c.send(
|
|
1637
|
+
ep.reopen_reconciliation(domain, candidate_id, m.ReasonIn(reason=reason))
|
|
1638
|
+
)
|
|
1639
|
+
|
|
1640
|
+
def resolve_many(
|
|
1641
|
+
self,
|
|
1642
|
+
domain: str,
|
|
1643
|
+
choice: m.BulkChoice,
|
|
1644
|
+
*,
|
|
1645
|
+
reason: str,
|
|
1646
|
+
ids: Sequence[str] | None = None,
|
|
1647
|
+
where: m.ReconcileFilter | None = None,
|
|
1648
|
+
policy: m.ReconcilePolicy | None = None,
|
|
1649
|
+
dry_run: bool = False,
|
|
1650
|
+
) -> m.BulkDecision:
|
|
1651
|
+
"""Apply a policy or a dismissal to the `ids` given, or to the open candidates
|
|
1652
|
+
matching `where`."""
|
|
1653
|
+
body = m.BulkReconcileIn(
|
|
1654
|
+
ids=None if ids is None else list(ids),
|
|
1655
|
+
filter=where,
|
|
1656
|
+
choice=choice,
|
|
1657
|
+
policy=policy,
|
|
1658
|
+
reason=reason,
|
|
1659
|
+
dry_run=dry_run,
|
|
1660
|
+
)
|
|
1661
|
+
return self._c.send(ep.resolve_reconciliation_bulk(domain, body))
|
|
1662
|
+
|
|
1663
|
+
def queue_sweep(self, domain: str, *, branch: str | None = None) -> m.SweepQueued:
|
|
1664
|
+
"""Queue a reconciliation sweep of a branch for Scribe."""
|
|
1665
|
+
return self._c.send(ep.queue_reconciliation_sweep(domain, m.SweepRequest(branch=branch)))
|
|
1666
|
+
|
|
1667
|
+
|
|
1520
1668
|
class Sources(_Resource):
|
|
1521
1669
|
def sync(self, domain: str, source: str, *, dry_run: bool = False) -> m.SyncQueued:
|
|
1522
1670
|
"""Queue a synchronization of the Source (id or api name) for Scribe."""
|
|
@@ -1543,12 +1691,16 @@ class Documents(_Resource):
|
|
|
1543
1691
|
filename: str | None = None,
|
|
1544
1692
|
extract: bool | None = None,
|
|
1545
1693
|
tiers: Sequence[int] | None = None,
|
|
1694
|
+
source_key: str | None = None,
|
|
1546
1695
|
) -> m.DocumentUploaded:
|
|
1547
1696
|
"""Upload a document: a path (named after the file) or bytes with a `filename`.
|
|
1548
1697
|
|
|
1549
|
-
`extract` (server default: true) queues an extraction job on the given `tiers`.
|
|
1698
|
+
`extract` (server default: true) queues an extraction job on the given `tiers`. With a
|
|
1699
|
+
`source_key`, new bytes become a new version of the key's document.
|
|
1550
1700
|
"""
|
|
1551
|
-
call = ep.upload_document(
|
|
1701
|
+
call = ep.upload_document(
|
|
1702
|
+
domain, file, filename=filename, extract=extract, tiers=tiers, source_key=source_key
|
|
1703
|
+
)
|
|
1552
1704
|
return self._c.send(call)
|
|
1553
1705
|
|
|
1554
1706
|
def upload_url(
|
|
@@ -1559,6 +1711,7 @@ class Documents(_Resource):
|
|
|
1559
1711
|
filename: str | None = None,
|
|
1560
1712
|
extract: bool | None = None,
|
|
1561
1713
|
tiers: Sequence[int] | None = None,
|
|
1714
|
+
source_key: str | None = None,
|
|
1562
1715
|
) -> m.DocumentUploaded:
|
|
1563
1716
|
"""Have the server fetch the document from an allowlisted URL."""
|
|
1564
1717
|
body = m.DocumentFromUrl(
|
|
@@ -1566,17 +1719,55 @@ class Documents(_Resource):
|
|
|
1566
1719
|
filename=filename,
|
|
1567
1720
|
extract=extract,
|
|
1568
1721
|
tiers=None if tiers is None else list(tiers),
|
|
1722
|
+
source_key=source_key,
|
|
1569
1723
|
)
|
|
1570
1724
|
return self._c.send(ep.upload_document_url(domain, body))
|
|
1571
1725
|
|
|
1572
1726
|
def list(
|
|
1573
|
-
self,
|
|
1727
|
+
self,
|
|
1728
|
+
domain: str,
|
|
1729
|
+
*,
|
|
1730
|
+
source_key: str | None = None,
|
|
1731
|
+
limit: int | None = None,
|
|
1732
|
+
cursor: str | None = None,
|
|
1574
1733
|
) -> m.Page[m.Document]:
|
|
1575
|
-
"""Documents of the Domain, newest first."""
|
|
1576
|
-
|
|
1734
|
+
"""Documents of the Domain, newest first; `source_key` keeps one key's versions."""
|
|
1735
|
+
call = ep.list_documents(domain, source_key=source_key, limit=limit, cursor=cursor)
|
|
1736
|
+
return self._c.send(call)
|
|
1737
|
+
|
|
1738
|
+
def iterate(
|
|
1739
|
+
self, domain: str, *, source_key: str | None = None, page_size: int | None = None
|
|
1740
|
+
) -> Iterator[m.Document]:
|
|
1741
|
+
return self._c.iterate(ep.list_documents(domain, source_key=source_key, limit=page_size))
|
|
1742
|
+
|
|
1743
|
+
def versions(
|
|
1744
|
+
self,
|
|
1745
|
+
domain: str,
|
|
1746
|
+
document_id: str,
|
|
1747
|
+
*,
|
|
1748
|
+
limit: int | None = None,
|
|
1749
|
+
cursor: str | None = None,
|
|
1750
|
+
) -> m.Page[m.Document]:
|
|
1751
|
+
"""The versions of the document's source key, newest first."""
|
|
1752
|
+
call = ep.document_versions(domain, document_id, limit=limit, cursor=cursor)
|
|
1753
|
+
return self._c.send(call)
|
|
1577
1754
|
|
|
1578
|
-
def
|
|
1579
|
-
|
|
1755
|
+
def iterate_versions(
|
|
1756
|
+
self, domain: str, document_id: str, *, page_size: int | None = None
|
|
1757
|
+
) -> Iterator[m.Document]:
|
|
1758
|
+
return self._c.iterate(ep.document_versions(domain, document_id, limit=page_size))
|
|
1759
|
+
|
|
1760
|
+
def retire(self, domain: str, document_id: str, *, reason: str) -> m.DocumentRetired:
|
|
1761
|
+
"""Retire a document: it is never extracted again and its evidence is re-checked."""
|
|
1762
|
+
body = m.RetirementIn(reason=reason)
|
|
1763
|
+
return self._c.send(ep.retire_document(domain, document_id, body))
|
|
1764
|
+
|
|
1765
|
+
def purge(
|
|
1766
|
+
self, domain: str, document_id: str, *, reason: str, confirm: str
|
|
1767
|
+
) -> m.DocumentPurged:
|
|
1768
|
+
"""Irreversibly delete the content of a document; `confirm` is the document id."""
|
|
1769
|
+
body = m.PurgeIn(reason=reason, confirm=confirm)
|
|
1770
|
+
return self._c.send(ep.purge_document(domain, document_id, body))
|
|
1580
1771
|
|
|
1581
1772
|
def get(self, domain: str, document_id: str) -> m.DocumentDetail:
|
|
1582
1773
|
"""A document with its extraction jobs."""
|