docforge-sdk 0.28.0__tar.gz → 0.30.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.
Files changed (87) hide show
  1. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/PKG-INFO +1 -1
  2. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/__init__.py +13 -0
  3. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/_version.py +1 -1
  4. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/client.py +10 -0
  5. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/models/__init__.py +29 -0
  6. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/models/_shared.py +29 -2
  7. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/models/auth.py +11 -4
  8. docforge_sdk-0.30.0/docforge_sdk/models/collection_aliases.py +43 -0
  9. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/models/collections.py +32 -3
  10. docforge_sdk-0.30.0/docforge_sdk/models/config_versions.py +83 -0
  11. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/models/corpus.py +4 -0
  12. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/models/jobs.py +6 -0
  13. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/resources/__init__.py +5 -0
  14. docforge_sdk-0.30.0/docforge_sdk/resources/collection_aliases.py +91 -0
  15. docforge_sdk-0.30.0/docforge_sdk/resources/config_versions.py +148 -0
  16. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/resources/corpus.py +59 -9
  17. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/resources/documents.py +19 -10
  18. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/tests/openapi_snapshot.json +1328 -342
  19. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/tests/parity_map.py +26 -0
  20. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/tests/route_map.py +9 -0
  21. docforge_sdk-0.30.0/tests/unit/test_collection_aliases.py +58 -0
  22. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/.gitignore +0 -0
  23. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/.python-version +0 -0
  24. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/LICENSE +0 -0
  25. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/README.md +0 -0
  26. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/_exceptions.py +0 -0
  27. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/_requestspec.py +0 -0
  28. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/_transport_async.py +0 -0
  29. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/_transport_base.py +0 -0
  30. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/_transport_sync.py +0 -0
  31. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/models/audit.py +0 -0
  32. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/models/blobs.py +0 -0
  33. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/models/capabilities.py +0 -0
  34. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/models/documents.py +0 -0
  35. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/models/estimate.py +0 -0
  36. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/models/explorer.py +0 -0
  37. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/models/field_spec.py +0 -0
  38. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/models/health.py +0 -0
  39. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/models/index_rebuild.py +0 -0
  40. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/models/ir.py +0 -0
  41. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/models/pipelines.py +0 -0
  42. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/models/preview.py +0 -0
  43. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/models/reingest.py +0 -0
  44. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/models/schema_ops.py +0 -0
  45. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/models/search.py +0 -0
  46. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/models/snippets.py +0 -0
  47. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/models/storage.py +0 -0
  48. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/models/trace.py +0 -0
  49. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/models/transfers.py +0 -0
  50. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/py.typed +0 -0
  51. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/resources/_base.py +0 -0
  52. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/resources/audit.py +0 -0
  53. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/resources/auth.py +0 -0
  54. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/resources/blobs.py +0 -0
  55. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/resources/capabilities.py +0 -0
  56. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/resources/collections.py +0 -0
  57. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/resources/explorer.py +0 -0
  58. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/resources/health.py +0 -0
  59. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/resources/jobs.py +0 -0
  60. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/resources/pipelines.py +0 -0
  61. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/resources/search.py +0 -0
  62. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/resources/snippets.py +0 -0
  63. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/docforge_sdk/resources/transfers.py +0 -0
  64. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/pyproject.toml +0 -0
  65. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/tests/__init__.py +0 -0
  66. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/tests/check_schema_drift.py +0 -0
  67. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/tests/live/__init__.py +0 -0
  68. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/tests/live/test_openapi_parity_live.py +0 -0
  69. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/tests/unit/__init__.py +0 -0
  70. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/tests/unit/test_audit.py +0 -0
  71. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/tests/unit/test_auth.py +0 -0
  72. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/tests/unit/test_blobs.py +0 -0
  73. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/tests/unit/test_collections.py +0 -0
  74. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/tests/unit/test_corpus.py +0 -0
  75. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/tests/unit/test_documents.py +0 -0
  76. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/tests/unit/test_explorer.py +0 -0
  77. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/tests/unit/test_health.py +0 -0
  78. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/tests/unit/test_jobs.py +0 -0
  79. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/tests/unit/test_models_offline_parity.py +0 -0
  80. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/tests/unit/test_pipelines.py +0 -0
  81. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/tests/unit/test_resource_parity.py +0 -0
  82. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/tests/unit/test_route_coverage.py +0 -0
  83. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/tests/unit/test_search.py +0 -0
  84. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/tests/unit/test_snippets.py +0 -0
  85. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/tests/unit/test_transfers.py +0 -0
  86. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/tests/unit/test_transport.py +0 -0
  87. {docforge_sdk-0.28.0 → docforge_sdk-0.30.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: docforge-sdk
3
- Version: 0.28.0
3
+ Version: 0.30.0
4
4
  Summary: Typed async + sync Python client for the DocForge REST API (standalone, zero server deps).
5
5
  Project-URL: Homepage, https://github.com/Florian-BARRE/docforge
6
6
  Project-URL: Repository, https://github.com/Florian-BARRE/docforge
@@ -24,6 +24,7 @@ from .models._shared import (
24
24
  FieldScope,
25
25
  FieldType,
26
26
  KeyPermissions,
27
+ KeyProfile,
27
28
  SourceKind,
28
29
  )
29
30
 
@@ -39,6 +40,13 @@ from .models.blobs import BlobContent
39
40
  # ------------------- Capabilities models ------------------- #
40
41
  from .models.capabilities import CapabilitiesResponse, CapabilityMatrix, ServiceInfo
41
42
 
43
+ # ------------------- Collection alias models ------------------- #
44
+ from .models.collection_aliases import (
45
+ CollectionAliasModel,
46
+ SetCollectionAliasRequest,
47
+ SetCollectionAliasResponse,
48
+ )
49
+
42
50
  # ------------------- Collections models ------------------- #
43
51
  from .models.collections import (
44
52
  BulkReingestAccepted,
@@ -189,6 +197,7 @@ __all__ = [
189
197
  # Shared vocabulary
190
198
  "Capability",
191
199
  "KeyPermissions",
200
+ "KeyProfile",
192
201
  "FieldType",
193
202
  "FieldOrigin",
194
203
  "FieldScope",
@@ -206,6 +215,10 @@ __all__ = [
206
215
  "KeyInfo",
207
216
  # Health
208
217
  "HealthStatus",
218
+ # Collection aliases
219
+ "CollectionAliasModel",
220
+ "SetCollectionAliasRequest",
221
+ "SetCollectionAliasResponse",
209
222
  # Collections
210
223
  "FieldSpec",
211
224
  "CollectionModel",
@@ -2,4 +2,4 @@
2
2
  # Single source of truth for the package version. Hatchling reads ``__version__`` from this file to
3
3
  # populate the distribution metadata (see ``[tool.hatch.version]`` in pyproject.toml).
4
4
 
5
- __version__ = "0.28.0"
5
+ __version__ = "0.30.0"
@@ -13,7 +13,9 @@ from .resources.audit import AsyncAudit, SyncAudit
13
13
  from .resources.auth import AsyncAuth, SyncAuth
14
14
  from .resources.blobs import AsyncBlobs, SyncBlobs
15
15
  from .resources.capabilities import AsyncCapabilities, SyncCapabilities
16
+ from .resources.collection_aliases import AsyncCollectionAliases, SyncCollectionAliases
16
17
  from .resources.collections import AsyncCollections, SyncCollections
18
+ from .resources.config_versions import AsyncConfigVersions, SyncConfigVersions
17
19
  from .resources.corpus import AsyncCorpus, SyncCorpus
18
20
  from .resources.documents import AsyncDocuments, SyncDocuments
19
21
  from .resources.explorer import AsyncExplorer, SyncExplorer
@@ -42,6 +44,8 @@ class AsyncClient:
42
44
  pipelines (AsyncPipelines): Pipeline discovery + design.
43
45
  transfers (AsyncTransfers): Collection export/import.
44
46
  snippets (AsyncSnippets): Granular collection-config snippet export/apply.
47
+ config_versions (AsyncConfigVersions): Collection config history (list/get/diff/restore).
48
+ collection_aliases (AsyncCollectionAliases): Stable collection names (list/set/delete).
45
49
  audit (AsyncAudit): Root-only audit-trail reads.
46
50
  corpus (AsyncCorpus): Server-side document grid — query + bulk delete/enable/reingest.
47
51
  """
@@ -69,6 +73,8 @@ class AsyncClient:
69
73
  self.pipelines = AsyncPipelines(self._transport)
70
74
  self.transfers = AsyncTransfers(self._transport)
71
75
  self.snippets = AsyncSnippets(self._transport)
76
+ self.config_versions = AsyncConfigVersions(self._transport)
77
+ self.collection_aliases = AsyncCollectionAliases(self._transport)
72
78
  self.corpus = AsyncCorpus(self._transport)
73
79
 
74
80
  async def __aenter__(self) -> "AsyncClient":
@@ -106,6 +112,8 @@ class Client:
106
112
  pipelines (SyncPipelines): Pipeline discovery + design.
107
113
  transfers (SyncTransfers): Collection export/import.
108
114
  snippets (SyncSnippets): Granular collection-config snippet export/apply.
115
+ config_versions (SyncConfigVersions): Collection config history (list/get/diff/restore).
116
+ collection_aliases (SyncCollectionAliases): Stable collection names (list/set/delete).
109
117
  audit (SyncAudit): Root-only audit-trail reads.
110
118
  corpus (SyncCorpus): Server-side document grid — query + bulk delete/enable/reingest.
111
119
  """
@@ -133,6 +141,8 @@ class Client:
133
141
  self.pipelines = SyncPipelines(self._transport)
134
142
  self.transfers = SyncTransfers(self._transport)
135
143
  self.snippets = SyncSnippets(self._transport)
144
+ self.config_versions = SyncConfigVersions(self._transport)
145
+ self.collection_aliases = SyncCollectionAliases(self._transport)
136
146
  self.corpus = SyncCorpus(self._transport)
137
147
 
138
148
  def __enter__(self) -> "Client":
@@ -8,6 +8,7 @@ from ._shared import (
8
8
  FieldScope,
9
9
  FieldType,
10
10
  KeyPermissions,
11
+ KeyProfile,
11
12
  SourceKind,
12
13
  )
13
14
 
@@ -23,6 +24,13 @@ from .blobs import BlobContent
23
24
  # ------------------- Capabilities models ------------------- #
24
25
  from .capabilities import CapabilitiesResponse, CapabilityMatrix, ServiceInfo
25
26
 
27
+ # ------------------- Collection alias models ------------------- #
28
+ from .collection_aliases import (
29
+ CollectionAliasModel,
30
+ SetCollectionAliasRequest,
31
+ SetCollectionAliasResponse,
32
+ )
33
+
26
34
  # ------------------- Collections models ------------------- #
27
35
  from .collections import (
28
36
  BulkReingestAccepted,
@@ -40,6 +48,16 @@ from .collections import (
40
48
  UpdateCollectionResponse,
41
49
  )
42
50
 
51
+ # ------------------- Config history models ------------------- #
52
+ from .config_versions import (
53
+ ConfigDiffEntry,
54
+ ConfigVersionDetail,
55
+ ConfigVersionDiffResponse,
56
+ ConfigVersionListResponse,
57
+ ConfigVersionRestoreResponse,
58
+ ConfigVersionSummary,
59
+ )
60
+
43
61
  # ------------------- Corpus filter + grid models ------------------- #
44
62
  from .corpus import (
45
63
  BulkDeleteResponse,
@@ -225,6 +243,7 @@ __all__ = [
225
243
  # Shared vocabulary
226
244
  "Capability",
227
245
  "KeyPermissions",
246
+ "KeyProfile",
228
247
  "FieldType",
229
248
  "FieldOrigin",
230
249
  "FieldScope",
@@ -277,6 +296,16 @@ __all__ = [
277
296
  "MetadataValuesPatch",
278
297
  "MetadataUpdateResponse",
279
298
  "DocumentView",
299
+ # Config history
300
+ "ConfigDiffEntry",
301
+ "CollectionAliasModel",
302
+ "SetCollectionAliasRequest",
303
+ "SetCollectionAliasResponse",
304
+ "ConfigVersionDetail",
305
+ "ConfigVersionDiffResponse",
306
+ "ConfigVersionListResponse",
307
+ "ConfigVersionRestoreResponse",
308
+ "ConfigVersionSummary",
280
309
  # Snippets
281
310
  "SNIPPET_FILE_EXTENSION",
282
311
  "SnippetKind",
@@ -12,15 +12,31 @@ from pydantic import BaseModel, Field
12
12
 
13
13
 
14
14
  class Capability(StrEnum):
15
- """A coarse action class an endpoint requires of the calling key."""
15
+ """
16
+ A coarse action class an endpoint requires of the calling key.
17
+
18
+ ``READ`` is the legacy pre-split alias: the server normalizes it to ``READ_TEXT`` +
19
+ ``READ_TECHNICAL`` and never returns it.
20
+ """
16
21
 
17
22
  READ = "read"
23
+ READ_TEXT = "read_text"
24
+ READ_TECHNICAL = "read_technical"
18
25
  WRITE = "write"
19
26
  SEARCH = "search"
20
27
  CREATE = "create"
21
28
  ADMIN = "admin"
22
29
 
23
30
 
31
+ class KeyProfile(StrEnum):
32
+ """A named capability preset offered at key creation."""
33
+
34
+ AGENT_READER = "agent_reader"
35
+ AGENT_SEARCHER = "agent_searcher"
36
+ OPERATOR = "operator"
37
+ ADMIN = "admin"
38
+
39
+
24
40
  class FieldType(StrEnum):
25
41
  """Data type of a metadata field — drives validation and storage."""
26
42
 
@@ -99,18 +115,29 @@ class KeyPermissions(BaseModel):
99
115
  that can authenticate but is authorized for nothing).
100
116
  collections (list[str]): Either ``["*"]`` (every collection) or an explicit list of
101
117
  collection UUID strings the key is scoped to.
118
+ profile (KeyProfile | None): A named preset — give it INSTEAD of ``capabilities`` and the
119
+ server expands it to the preset's explicit list (stored for display).
102
120
  """
103
121
 
104
122
  capabilities: list[Capability] = Field(
105
- description="The action classes this key grants (READ / WRITE / SEARCH / ADMIN)."
123
+ default_factory=list,
124
+ description="The action classes this key grants (read_text / read_technical / write / "
125
+ "search / create / admin; the legacy 'read' = read_text + read_technical). May be omitted "
126
+ "when 'profile' is given.",
106
127
  )
107
128
  collections: list[str] = Field(
108
129
  description="Collection scope: ['*'] for all, else explicit collection UUID strings."
109
130
  )
131
+ profile: KeyProfile | None = Field(
132
+ default=None,
133
+ description="Named capability preset (agent_reader / agent_searcher / operator / admin). "
134
+ "Given instead of 'capabilities', it expands to the preset; stored for display only.",
135
+ )
110
136
 
111
137
 
112
138
  __all__ = [
113
139
  "Capability",
140
+ "KeyProfile",
114
141
  "FieldType",
115
142
  "FieldOrigin",
116
143
  "FieldScope",
@@ -11,7 +11,7 @@ from typing import Any
11
11
  from pydantic import BaseModel, Field
12
12
 
13
13
  # ====== Local Project Imports ======
14
- from ._shared import KeyPermissions
14
+ from ._shared import KeyPermissions, KeyProfile
15
15
 
16
16
 
17
17
  class CreateKeyRequest(BaseModel):
@@ -28,7 +28,8 @@ class CreateKeyRequest(BaseModel):
28
28
  name: str = Field(min_length=1, description="Human-readable label for the key.")
29
29
  permissions: KeyPermissions | None = Field(
30
30
  default=None,
31
- description="Per-key capability + collection scope; null = full access (root).",
31
+ description="Per-key capability + collection scope; null = full access (root). Give "
32
+ "'profile' instead of 'capabilities' to apply a named preset.",
32
33
  )
33
34
  expires_at: datetime | None = Field(
34
35
  default=None,
@@ -128,16 +129,22 @@ class KeyInfo(BaseModel):
128
129
 
129
130
 
130
131
  class WhoAmI(BaseModel):
131
- """The calling token's own access — capabilities + collection scope, for self-introspection."""
132
+ """The calling token's own access — capabilities, collection scope and usage profile."""
132
133
 
133
134
  authenticated: bool = Field(description="Always true for a resolved principal.")
134
135
  root: bool = Field(description="Full, unscoped access (auth off, or a NULL-permissions key).")
135
136
  capabilities: list[str] = Field(
136
- description="Action classes this token grants (read / write / search / create / admin)."
137
+ description="Action classes this token grants (read_text / read_technical / write / search "
138
+ "/ create / admin)."
137
139
  )
138
140
  collections: list[str] = Field(
139
141
  description="Collection scope: ['*'] for all, else explicit collection UUID strings."
140
142
  )
143
+ profile: KeyProfile | None = Field(
144
+ default=None,
145
+ description="Named preset matching the capabilities (agent_reader / agent_searcher / "
146
+ "operator / admin); null for root or a custom capability set.",
147
+ )
141
148
 
142
149
 
143
150
  __all__ = ["CreateKeyRequest", "RotateKeyRequest", "CreatedKey", "KeyInfo", "WhoAmI"]
@@ -0,0 +1,43 @@
1
+ # ====== Code Summary ======
2
+ # Models of the collection-alias surface, mirrored field-for-field from the DocForge backend
3
+ # (app/backend/routers/collection_aliases/models.py): an alias and its current target, the PUT body
4
+ # (the target collection), and the PUT outcome (the alias now + where it pointed before). A collection
5
+ # alias is a stable deployment-level name for one collection, usable wherever a collection id is taken.
6
+
7
+ # ====== Standard Library Imports ======
8
+ from datetime import datetime
9
+
10
+ # ====== Third-Party Library Imports ======
11
+ from pydantic import BaseModel, ConfigDict, Field
12
+
13
+
14
+ class CollectionAliasModel(BaseModel):
15
+ """One collection alias and the collection it currently targets."""
16
+
17
+ name: str = Field(description="The alias name (a slug: lowercase, digits, '-', '_').")
18
+ collection_id: str = Field(description="The UUID of the collection it points at.")
19
+ collection_name: str = Field(description="The name of the collection it points at.")
20
+ created_at: datetime = Field(description="When the alias was created.")
21
+ updated_at: datetime = Field(description="When the alias was last (re-)pointed.")
22
+
23
+
24
+ class SetCollectionAliasRequest(BaseModel):
25
+ """The body of PUT /collection-aliases/{name}: the collection the alias must point at."""
26
+
27
+ model_config = ConfigDict(extra="forbid")
28
+
29
+ collection_id: str = Field(description="The UUID of the target collection.")
30
+
31
+
32
+ class SetCollectionAliasResponse(CollectionAliasModel):
33
+ """The alias after a create / re-point, plus where it pointed before."""
34
+
35
+ previous_collection_id: str | None = Field(
36
+ default=None,
37
+ description="The collection the alias pointed at before this call (null = it was created; "
38
+ "equal to collection_id = a no-op re-point).",
39
+ )
40
+ created: bool = Field(description="True when this call created the alias.")
41
+
42
+
43
+ __all__ = ["CollectionAliasModel", "SetCollectionAliasRequest", "SetCollectionAliasResponse"]
@@ -33,6 +33,7 @@ class CollectionModel(BaseModel):
33
33
  needs_reindex (bool): True when a config change requires reindexing.
34
34
  missing_vectors (list[str] | None): Named vectors the schema's semantic/lexical fields need
35
35
  but the vector store does not declare (an index rebuild is required); None on list rows.
36
+ aliases (list[str] | None): The collection aliases targeting it (single read only).
36
37
  created_at (datetime | None): Creation timestamp.
37
38
  title_field (str | None): Document-scope field used as the display title (None = parsed).
38
39
  pipeline (dict[str, Any]): The ingestion pipeline blob (the graph).
@@ -76,15 +77,25 @@ class CollectionModel(BaseModel):
76
77
  "on the single-collection read and the PATCH response ([] = aligned or never ingested); "
77
78
  "null on the fleet list, which never reads the vector store.",
78
79
  )
80
+ aliases: list[str] | None = Field(
81
+ default=None,
82
+ description="The collection aliases pointing at this collection (each usable in place of the "
83
+ "UUID in every collection route and as an 'alias:<name>' key scope). Computed on the "
84
+ "single-collection read (GET /collections/{id}); null on the other payloads.",
85
+ )
79
86
  created_at: datetime | None = Field(default=None, description="Creation timestamp.")
80
87
  title_field: str | None = Field(
81
88
  default=None,
82
89
  description="Document-scope field whose value is each document's display title (None = the "
83
90
  "parser-derived title).",
84
91
  )
85
- pipeline: dict[str, Any] = Field(description="The ingestion pipeline blob (the graph).")
86
- search: dict[str, Any] = Field(
87
- description="The search pipeline graph blob ({} = use the stock default)."
92
+ pipeline: dict[str, Any] | None = Field(
93
+ description="The ingestion pipeline blob (the graph); null when the calling key lacks the "
94
+ "read_technical capability (withheld, not refused)."
95
+ )
96
+ search: dict[str, Any] | None = Field(
97
+ description="The search pipeline graph blob ({} = use the stock default); null when the "
98
+ "calling key lacks the read_technical capability (withheld, not refused)."
88
99
  )
89
100
  fields: list[FieldSpec] = Field(default_factory=list, description="The metadata schema.")
90
101
  estimate_overrides: EstimateOverrides | None = Field(
@@ -281,6 +292,8 @@ class BulkReingestRequest(BaseModel):
281
292
  in the collection. An empty list is rejected by the API (an ambiguous no-op).
282
293
  force (bool): Bypass the stage cache and recompute every stage from scratch (no cache
283
294
  read/write). Use to rebuild after a code change that did not bump a node's CACHE_VERSION.
295
+ replay_from (str | None): Replay from this post-IR stage (no re-parse); None = full re-run.
296
+ confirm_estimate (bool): Acknowledge the estimate of a reingest above the confirm threshold.
284
297
  """
285
298
 
286
299
  document_ids: list[str] | None = Field(
@@ -292,6 +305,18 @@ class BulkReingestRequest(BaseModel):
292
305
  description="Bypass the stage cache and recompute every stage from scratch (no cache "
293
306
  "read/write). Use to rebuild after a code change that did not bump a node's CACHE_VERSION.",
294
307
  )
308
+ replay_from: str | None = Field(
309
+ default=None,
310
+ description="Replay only this post-IR stage and its downstream from each document's PERSISTED "
311
+ "IR (no re-parse): one of enrich, chunk, metagen_chunk, metagen_document, embed — as the "
312
+ "collection's pipeline allows (422 lists the allowed stages). Omit for a full re-run.",
313
+ )
314
+ confirm_estimate: bool = Field(
315
+ default=False,
316
+ description="Acknowledge the cost estimate of a LARGE reingest (above the server's confirm "
317
+ "threshold). Without it such a call is refused 409 estimate_required carrying the estimate "
318
+ "summary; review it and resend with true.",
319
+ )
295
320
 
296
321
 
297
322
  class BulkReingestAccepted(BaseModel):
@@ -324,6 +349,10 @@ class BulkReingestAccepted(BaseModel):
324
349
  default=0,
325
350
  description="Documents skipped because an ingestion job was already active for them.",
326
351
  )
352
+ skipped_not_replayable: int = Field(
353
+ default=0,
354
+ description="Documents skipped by a replay_from run because they have no persisted IR.",
355
+ )
327
356
  jobs: list[ReingestJobHandle] = Field(description="One handle per enqueued run.")
328
357
 
329
358
 
@@ -0,0 +1,83 @@
1
+ # ====== Code Summary ======
2
+ # Response models of the versioned collection config history, mirrored field-for-field from the
3
+ # DocForge backend (app/backend/routers/config_history/models.py): the paginated version list (author +
4
+ # change summary), one masked snapshot, the structured masked diff, and a restore outcome.
5
+
6
+ # ====== Standard Library Imports ======
7
+ from datetime import datetime
8
+ from typing import Any, Literal
9
+
10
+ # ====== Third-Party Library Imports ======
11
+ from pydantic import BaseModel, Field
12
+
13
+
14
+ class ConfigVersionSummary(BaseModel):
15
+ """One entry of a collection's config history (no config body)."""
16
+
17
+ version: int = Field(description="Per-collection version number (1 = creation).")
18
+ created_at: datetime = Field(description="When this version was written.")
19
+ note: str | None = Field(default=None, description="The note stored with the version.")
20
+ author_label: str | None = Field(
21
+ default=None,
22
+ description='Who wrote it: "<key name> (<key prefix>)" (the root token reads "root (df_…)"), '
23
+ 'or "anonymous" (auth off); null = '
24
+ "unknown (written before authorship was recorded, or a system write such as an import).",
25
+ )
26
+ author_key_id: str | None = Field(
27
+ default=None, description="The authoring API key id (null once that key is deleted)."
28
+ )
29
+ changes: list[str] = Field(
30
+ default_factory=list,
31
+ description='What changed vs the previous version: "pipeline:<node id>", "pipeline:(graph)" '
32
+ '(transitions/bindings), "search". Empty for version 1 or a no-op write.',
33
+ )
34
+
35
+
36
+ class ConfigVersionListResponse(BaseModel):
37
+ """One newest-first page of a collection's config history."""
38
+
39
+ collection_id: str
40
+ total: int = Field(description="Number of versions in the whole history.")
41
+ limit: int
42
+ offset: int
43
+ items: list[ConfigVersionSummary]
44
+
45
+
46
+ class ConfigVersionDetail(ConfigVersionSummary):
47
+ """One config version with its {pipeline, search} snapshot — every provider secret masked."""
48
+
49
+ config: dict[str, Any] = Field(
50
+ description="The snapshot {pipeline, search}; secrets replaced by the mask marker."
51
+ )
52
+
53
+
54
+ class ConfigDiffEntry(BaseModel):
55
+ """One changed path between two versions (values masked)."""
56
+
57
+ path: str = Field(
58
+ description="JSON-pointer style path; graph nodes are keyed by id, e.g. "
59
+ '"/pipeline/nodes/parse/config/max_pages".',
60
+ )
61
+ op: Literal["added", "removed", "changed"]
62
+ before: Any = Field(default=None, description="The masked value in `from` (absent → null).")
63
+ after: Any = Field(default=None, description="The masked value in `to` (absent → null).")
64
+
65
+
66
+ class ConfigVersionDiffResponse(BaseModel):
67
+ """The structured diff from one config version to another (secrets always masked)."""
68
+
69
+ collection_id: str
70
+ from_version: int
71
+ to_version: int
72
+ changes: list[ConfigDiffEntry]
73
+
74
+
75
+ class ConfigVersionRestoreResponse(BaseModel):
76
+ """The outcome of restoring a config version (always a NEW version, never a history rewrite)."""
77
+
78
+ collection_id: str
79
+ restored_from: int = Field(description="The version whose config was re-applied.")
80
+ version: int = Field(description='The new version written (note "restore of v{N}").')
81
+ needs_reindex: bool = Field(
82
+ description="The collection's derived reindex flag after the restore."
83
+ )
@@ -208,6 +208,10 @@ class BulkReingestResponse(BaseModel):
208
208
  default=0,
209
209
  description="Documents skipped because an ingestion job was already active for them.",
210
210
  )
211
+ skipped_not_replayable: int = Field(
212
+ default=0,
213
+ description="Documents skipped by a replay_from run because they have no persisted IR.",
214
+ )
211
215
  jobs: list[ReingestJobHandle] = Field(description="One handle per enqueued run.")
212
216
 
213
217
 
@@ -30,6 +30,7 @@ class JobStatus(BaseModel):
30
30
  collection_name (str | None): The collection's name, joined at read (None if the
31
31
  collection is gone).
32
32
  status (str): queued / running / done / failed / cancelled.
33
+ replay_from (str | None): The stage an ingest job replays from (None = a full run).
33
34
  cancel_requested (bool): A cooperative stop has been requested; the running job stops at
34
35
  its next stage boundary (still 'running' until it does).
35
36
  progress (int): 0–100 (completed pipeline nodes over total).
@@ -83,6 +84,11 @@ class JobStatus(BaseModel):
83
84
  "'rebuild_index' (a collection-level vector-store rebuild — document_id is null). "
84
85
  "Lets a client render a side-job distinctly from a full ingestion."
85
86
  )
87
+ replay_from: str | None = Field(
88
+ default=None,
89
+ description="The stage an ingest job replays from on the persisted IR (no re-parse); null "
90
+ "for a full run.",
91
+ )
86
92
  cancel_requested: bool = Field(
87
93
  default=False,
88
94
  description="A cooperative stop has been requested; the running job stops at its next "
@@ -13,6 +13,9 @@ from .blobs import AsyncBlobs, SyncBlobs
13
13
  # ------------------- Capabilities resource ------------------- #
14
14
  from .capabilities import AsyncCapabilities, SyncCapabilities
15
15
 
16
+ # ------------------- Collection aliases resource ------------------- #
17
+ from .collection_aliases import AsyncCollectionAliases, SyncCollectionAliases
18
+
16
19
  # ------------------- Collections resource ------------------- #
17
20
  from .collections import AsyncCollections, SyncCollections
18
21
 
@@ -47,6 +50,8 @@ __all__ = [
47
50
  "SyncAuth",
48
51
  "AsyncHealth",
49
52
  "SyncHealth",
53
+ "AsyncCollectionAliases",
54
+ "SyncCollectionAliases",
50
55
  "AsyncCollections",
51
56
  "SyncCollections",
52
57
  "AsyncDocuments",
@@ -0,0 +1,91 @@
1
+ # ====== Code Summary ======
2
+ # The collection-alias resource — list the aliases (name → current target), create or atomically
3
+ # re-point one (the "switch": every key scoped `alias:<name>` and every client addressing the alias
4
+ # follow it on the next request), and delete one. URL logic lives once in the pure
5
+ # _CollectionAliasesSpecs mixin so the async/sync shells differ ONLY by ``await``.
6
+
7
+ # ====== Local Project Imports ======
8
+ from .._requestspec import RequestSpec
9
+ from ..models.collection_aliases import (
10
+ CollectionAliasModel,
11
+ SetCollectionAliasRequest,
12
+ SetCollectionAliasResponse,
13
+ )
14
+ from ._base import AsyncResource, SyncResource, _ResourceMixin
15
+
16
+
17
+ class _CollectionAliasesSpecs(_ResourceMixin):
18
+ """Pure ``RequestSpec`` builders for the collection-alias endpoints."""
19
+
20
+ _BASE = "/collection-aliases"
21
+
22
+ def _list_spec(self) -> RequestSpec:
23
+ """A GET of every alias the caller may see."""
24
+ return RequestSpec("GET", self._BASE)
25
+
26
+ def _set_spec(self, name: str, collection_id: str) -> RequestSpec:
27
+ """A PUT creating / re-pointing ``name`` to ``collection_id``."""
28
+ body = SetCollectionAliasRequest(collection_id=collection_id)
29
+ return RequestSpec("PUT", f"{self._BASE}/{name}", json=body.model_dump(mode="json"))
30
+
31
+ def _delete_spec(self, name: str) -> RequestSpec:
32
+ """A DELETE of one alias."""
33
+ return RequestSpec("DELETE", f"{self._BASE}/{name}")
34
+
35
+
36
+ class AsyncCollectionAliases(AsyncResource, _CollectionAliasesSpecs):
37
+ """Asynchronous collection aliases."""
38
+
39
+ async def list(self) -> list[CollectionAliasModel]:
40
+ """
41
+ List the collection aliases whose target the caller may see, by name.
42
+
43
+ Returns:
44
+ list[CollectionAliasModel]: Each alias with its current target (id + name).
45
+ """
46
+ return await self._transport.request(self._list_spec(), list[CollectionAliasModel])
47
+
48
+ async def set(self, name: str, collection_id: str) -> SetCollectionAliasResponse:
49
+ """
50
+ Create the alias, or atomically re-point it to another collection (the "switch").
51
+
52
+ Args:
53
+ name (str): The alias slug (lowercase letters, digits, '-', '_').
54
+ collection_id (str): The UUID of the collection the alias must target.
55
+
56
+ Returns:
57
+ SetCollectionAliasResponse: The alias now + its previous target (None = created).
58
+ """
59
+ return await self._transport.request(
60
+ self._set_spec(name, collection_id), SetCollectionAliasResponse
61
+ )
62
+
63
+ async def delete(self, name: str) -> None:
64
+ """
65
+ Delete a collection alias (keys scoped ``alias:<name>`` then grant nothing through it).
66
+
67
+ Args:
68
+ name (str): The alias name.
69
+ """
70
+ await self._transport.request(self._delete_spec(name), type(None))
71
+
72
+
73
+ class SyncCollectionAliases(SyncResource, _CollectionAliasesSpecs):
74
+ """Synchronous collection aliases."""
75
+
76
+ def list(self) -> list[CollectionAliasModel]:
77
+ """List the collection aliases (see the async twin)."""
78
+ return self._transport.request(self._list_spec(), list[CollectionAliasModel])
79
+
80
+ def set(self, name: str, collection_id: str) -> SetCollectionAliasResponse:
81
+ """Create or atomically re-point an alias (see the async twin)."""
82
+ return self._transport.request(
83
+ self._set_spec(name, collection_id), SetCollectionAliasResponse
84
+ )
85
+
86
+ def delete(self, name: str) -> None:
87
+ """Delete a collection alias (see the async twin)."""
88
+ self._transport.request(self._delete_spec(name), type(None))
89
+
90
+
91
+ __all__ = ["AsyncCollectionAliases", "SyncCollectionAliases"]