docverse 1.0.0b2__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 (78) hide show
  1. docverse-1.0.0b2/CHANGELOG.md +3 -0
  2. docverse-1.0.0b2/PKG-INFO +20 -0
  3. docverse-1.0.0b2/README.md +9 -0
  4. docverse-1.0.0b2/changelog.d/20260512_120000_DM_54688.md +3 -0
  5. docverse-1.0.0b2/changelog.d/20260512_180000_DM_54688.md +3 -0
  6. docverse-1.0.0b2/changelog.d/20260605_120000_DM_55159.md +3 -0
  7. docverse-1.0.0b2/changelog.d/20260605_130000_DM_55159_hateoas.md +3 -0
  8. docverse-1.0.0b2/changelog.d/20260605_140000_DM_55159_subject_url.md +3 -0
  9. docverse-1.0.0b2/changelog.d/20260729_120000_DM_55551_openapi_polish.md +8 -0
  10. docverse-1.0.0b2/changelog.d/20260729_140000_DM_55658.md +3 -0
  11. docverse-1.0.0b2/changelog.d/20260807_120000_DM_54684_tarball_hash.md +3 -0
  12. docverse-1.0.0b2/changelog.d/20260810_200000_DM_55772_edition_autocreation.md +3 -0
  13. docverse-1.0.0b2/changelog.d/20260813_020000_DM_55772_kind_source.md +4 -0
  14. docverse-1.0.0b2/changelog.d/20260901_120000_DM_55762_content_hash_optional.md +3 -0
  15. docverse-1.0.0b2/changelog.d/20260902_120000_DM_56012_terminal_build_statuses.md +3 -0
  16. docverse-1.0.0b2/changelog.d/20260902_170000_DM_56012_deleted_skipped_progress.md +3 -0
  17. docverse-1.0.0b2/changelog.d/20260903_120000_DM_56012_httpx_floor.md +3 -0
  18. docverse-1.0.0b2/changelog.d/20260904_120000_DM_56012_retired_status_progress.md +3 -0
  19. docverse-1.0.0b2/changelog.d/20260908_120000_DM_54691_build_date_purged.md +3 -0
  20. docverse-1.0.0b2/changelog.d/20260908_220000_DM_54691_restore_build.md +3 -0
  21. docverse-1.0.0b2/changelog.d/20260914_120000_DM_56083_public_ids.md +3 -0
  22. docverse-1.0.0b2/changelog.d/20260914_180000_DM_56083_date_deleted.md +3 -0
  23. docverse-1.0.0b2/changelog.d/20260914_200000_DM_56083_list_projects.md +3 -0
  24. docverse-1.0.0b2/changelog.d/20260914_220000_DM_56083_updated_since_overlap.md +3 -0
  25. docverse-1.0.0b2/changelog.d/20260915_020000_DM_56083_validator_guidance.md +3 -0
  26. docverse-1.0.0b2/changelog.d/20260916_230000_DM_56083_listing_default_edition.md +3 -0
  27. docverse-1.0.0b2/changelog.d/_template.md +7 -0
  28. docverse-1.0.0b2/pyproject.toml +69 -0
  29. docverse-1.0.0b2/setup.cfg +4 -0
  30. docverse-1.0.0b2/src/docverse/__init__.py +22 -0
  31. docverse-1.0.0b2/src/docverse/_annotations.py +63 -0
  32. docverse-1.0.0b2/src/docverse/_cli.py +413 -0
  33. docverse-1.0.0b2/src/docverse/_client.py +584 -0
  34. docverse-1.0.0b2/src/docverse/_exceptions.py +47 -0
  35. docverse-1.0.0b2/src/docverse/_tar.py +82 -0
  36. docverse-1.0.0b2/src/docverse/models/__init__.py +196 -0
  37. docverse-1.0.0b2/src/docverse/models/_examples.py +77 -0
  38. docverse-1.0.0b2/src/docverse/models/builds.py +363 -0
  39. docverse-1.0.0b2/src/docverse/models/credentials.py +143 -0
  40. docverse-1.0.0b2/src/docverse/models/dashboard.py +69 -0
  41. docverse-1.0.0b2/src/docverse/models/dashboard_template.py +209 -0
  42. docverse-1.0.0b2/src/docverse/models/editions.py +407 -0
  43. docverse-1.0.0b2/src/docverse/models/infrastructure.py +108 -0
  44. docverse-1.0.0b2/src/docverse/models/keeper_sync.py +712 -0
  45. docverse-1.0.0b2/src/docverse/models/lifecycle.py +196 -0
  46. docverse-1.0.0b2/src/docverse/models/memberships.py +134 -0
  47. docverse-1.0.0b2/src/docverse/models/organizations.py +449 -0
  48. docverse-1.0.0b2/src/docverse/models/projects.py +432 -0
  49. docverse-1.0.0b2/src/docverse/models/queue.py +321 -0
  50. docverse-1.0.0b2/src/docverse/models/queue_enums.py +136 -0
  51. docverse-1.0.0b2/src/docverse/models/services.py +279 -0
  52. docverse-1.0.0b2/src/docverse.egg-info/PKG-INFO +20 -0
  53. docverse-1.0.0b2/src/docverse.egg-info/SOURCES.txt +76 -0
  54. docverse-1.0.0b2/src/docverse.egg-info/dependency_links.txt +1 -0
  55. docverse-1.0.0b2/src/docverse.egg-info/entry_points.txt +2 -0
  56. docverse-1.0.0b2/src/docverse.egg-info/requires.txt +3 -0
  57. docverse-1.0.0b2/src/docverse.egg-info/scm_file_list.json +72 -0
  58. docverse-1.0.0b2/src/docverse.egg-info/scm_version.json +8 -0
  59. docverse-1.0.0b2/src/docverse.egg-info/top_level.txt +1 -0
  60. docverse-1.0.0b2/tests/__init__.py +0 -0
  61. docverse-1.0.0b2/tests/annotations_test.py +121 -0
  62. docverse-1.0.0b2/tests/build_model_test.py +180 -0
  63. docverse-1.0.0b2/tests/cli_test.py +377 -0
  64. docverse-1.0.0b2/tests/client_test.py +629 -0
  65. docverse-1.0.0b2/tests/dashboard_model_test.py +33 -0
  66. docverse-1.0.0b2/tests/dashboard_template_model_test.py +100 -0
  67. docverse-1.0.0b2/tests/deploy_worker_integration_test.py +142 -0
  68. docverse-1.0.0b2/tests/deploy_worker_test.py +521 -0
  69. docverse-1.0.0b2/tests/edition_model_test.py +174 -0
  70. docverse-1.0.0b2/tests/init_test.py +10 -0
  71. docverse-1.0.0b2/tests/keeper_sync_model_test.py +166 -0
  72. docverse-1.0.0b2/tests/lifecycle_model_test.py +197 -0
  73. docverse-1.0.0b2/tests/organization_model_test.py +177 -0
  74. docverse-1.0.0b2/tests/project_model_test.py +274 -0
  75. docverse-1.0.0b2/tests/queue_enums_test.py +21 -0
  76. docverse-1.0.0b2/tests/queue_model_test.py +317 -0
  77. docverse-1.0.0b2/tests/service_configs_test.py +44 -0
  78. docverse-1.0.0b2/tests/tar_test.py +94 -0
@@ -0,0 +1,3 @@
1
+ # Change log
2
+
3
+ <!-- scriv-insert-here -->
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.4
2
+ Name: docverse
3
+ Version: 1.0.0b2
4
+ Summary: Python client for the Docverse documentation hosting platform.
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.12
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: click>=8
9
+ Requires-Dist: httpx>=0.26
10
+ Requires-Dist: pydantic>=2.5
11
+
12
+ # Docverse
13
+
14
+ Docverse is a hosting platform for versioned documentation websites, built by [Rubin Observatory](https://www.lsst.io).
15
+ This package is the Python client and `docverse` command-line tool for uploading documentation builds to a Docverse service.
16
+
17
+ **Docverse is a pre-release.** The API and this client are still changing, and no public Docverse service is available yet.
18
+
19
+ The design is documented in [SQR-112: Docverse documentation hosting platform design](https://sqr-112.lsst.io).
20
+ Development happens in [lsst-sqre/docverse](https://github.com/lsst-sqre/docverse).
@@ -0,0 +1,9 @@
1
+ # Docverse
2
+
3
+ Docverse is a hosting platform for versioned documentation websites, built by [Rubin Observatory](https://www.lsst.io).
4
+ This package is the Python client and `docverse` command-line tool for uploading documentation builds to a Docverse service.
5
+
6
+ **Docverse is a pre-release.** The API and this client are still changing, and no public Docverse service is available yet.
7
+
8
+ The design is documented in [SQR-112: Docverse documentation hosting platform design](https://sqr-112.lsst.io).
9
+ Development happens in [lsst-sqre/docverse](https://github.com/lsst-sqre/docverse).
@@ -0,0 +1,3 @@
1
+ ### New features
2
+
3
+ - New `LifecycleRule` discriminated union (`DraftInactivityRule`, `BuildHistoryOrphanRule`, `RefDeletedRule`) and `LifecycleRuleSet` root model exported from `docverse.models`. The `lifecycle_rules` field on `Organization`, `OrganizationCreate`, `OrganizationUpdate`, `Project`, `ProjectCreate`, and `ProjectUpdate` now uses this typed shape so script-side configuration gets IDE autocomplete and pydantic validation, and the duplicate-rule-type guard runs locally before any request goes out. `ProjectCreate` gains the field so a project can be provisioned with lifecycle rules in a single request instead of requiring a follow-up PATCH.
@@ -0,0 +1,3 @@
1
+ ### New features
2
+
3
+ - New `LifecycleEvalRunStatus` enum (`pending`, `in_progress`, `succeeded`, `partial_failure`, `failed`) describes the lifecycle of a `lifecycle_eval_runs` aggregate row. Shared shape with `KeeperSyncRunStatus` so the dispatcher / per-org / reaper pattern transfers between the two subsystems.
@@ -0,0 +1,3 @@
1
+ ### New features
2
+
3
+ - Typed the `build_processing` queue-job `progress` payload. A new `BuildProcessingProgress` model — with nested `EditionUpdateRef` (the `editions_updated`/`editions_skipped` entries) and `PublishJobRef` (the `publish_jobs` entries) — is exported from `docverse.models`, and `QueueJob.progress` is now `BuildProcessingProgress | None` instead of an untyped dict, so generated clients and `api-types` get real fields. All fields are optional and the models use `extra="allow"`, so other job kinds' progress payloads round-trip unchanged.
@@ -0,0 +1,3 @@
1
+ ### New features
2
+
3
+ - Added optional `edition_url` to `EditionUpdateRef` and `queue_job_url` to `PublishJobRef` in `docverse.models`. These surface the HATEOAS links now embedded in a `build_processing` job's `progress` payload, so consumers can follow `edition_url` / `queue_job_url` instead of reconstructing `…/editions/{slug}` and `/queue/jobs/{id}` paths.
@@ -0,0 +1,3 @@
1
+ ### New features
2
+
3
+ - Added optional `subject_url`, `build_url`, and `edition_url` fields to the `QueueJob` model in `docverse.models`. These carry the HATEOAS back-reference links from a queue job to the API resource it processes (the build for `build_processing` jobs, the edition for publish jobs), so consumers follow a link instead of reconstructing the build/edition path.
@@ -0,0 +1,8 @@
1
+ ### Backwards-incompatible changes
2
+
3
+ - `KeeperSyncTombstone` no longer carries a `docverse_id` field. It exposed an internal Docverse database row id; the entry's Base32 `id` and `display_path` already identify the resource.
4
+
5
+ ### Other changes
6
+
7
+ - Model fields now declare realistic OpenAPI example values: HATEOAS `*_url` fields show full example URLs and public-ID fields show genuine Crockford Base32 identifiers, drawn from shared constants in `docverse.models._examples`.
8
+ - Public enums (`KeeperSyncTombstoneReason`, `KeeperSyncRunKind`, `EditionKind`, `TrackingMode`, `JobKind`, `JobStatus`, `PublishStatus`, `OrgRole`, and others) document each value in their class docstrings, which pydantic surfaces as the enum schema description in generated OpenAPI documents.
@@ -0,0 +1,3 @@
1
+ ### Backwards-incompatible changes
2
+
3
+ - The client distribution is renamed from `docverse-client` to `docverse` and now owns the plain `docverse` import root as a regular package. Import the client as `from docverse import DocverseClient` and models as `from docverse.models import Project`; the `docverse.client` module path is gone. The `docverse` CLI script name is unchanged. Release tags use the `client/` prefix (e.g. `client/0.1.0`).
@@ -0,0 +1,3 @@
1
+ ### Bug fixes
2
+
3
+ - `create_tarball` no longer folds the current time into the digest it reports, so archiving byte-identical content twice now yields the same `content_hash`. The digest covers the compressed bytes, and `tarfile`'s `w:gz` mode stamps `time.time()` into the gzip header's MTIME field, so the hash changed as soon as two calls straddled an integer second. The gzip stream is now built explicitly with `mtime=0`, and with no embedded original filename. Note that the digest is still derived from the tarball rather than from file contents alone, so the same documentation built twice — in separate CI runs, or from a fresh checkout — will still hash differently because each tar member carries its own mtime.
@@ -0,0 +1,3 @@
1
+ ### New features
2
+
3
+ - Added `EditionAutocreationConfig` (exported from `docverse.models`), the shape of the new `edition_autocreation` field on the `Organization`, `OrganizationUpdate`, `Project`, and `ProjectUpdate` models. It carries a single knob for now, `semver_aggregates` (default `true`), controlling whether a stable semver release also auto-creates its major (`N`) and minor (`N.M`) aggregate editions. The model forbids unknown keys, so a typo in a `PATCH` payload fails validation client-side rather than silently doing nothing. Instances are frozen — the server resolves an unconfigured project to a single shared default config, so assignment is rejected; use `model_copy(update=...)` to derive a variant.
@@ -0,0 +1,4 @@
1
+ ### New features
2
+
3
+ - Added `EditionKindSource` (exported from `docverse.models`), the `derived` | `declared` enum behind the new `kind_source` field on `Edition` and `EditionUpdate`. `derived` means Docverse owns the edition's `kind` and recomputes it from the edition's ref and the project's slug-rewrite rules on every sync and tracked build; `declared` means an operator set it and no automated path rewrites it. The field is read-only on `Edition` — a kind becomes `declared` by PATCHing `kind` to a *different* value — and settable on `EditionUpdate` so a declared kind can be handed back to Docverse with `EditionUpdate(kind_source=EditionKindSource.derived)`.
4
+ - `EditionUpdate.kind` no longer pins an edition when it merely echoes the kind the edition already has, so a read-modify-write client that round-trips a full `Edition` no longer opts editions out of automatic kind derivation as a side effect.
@@ -0,0 +1,3 @@
1
+ ### New features
2
+
3
+ - `BuildCreate.content_hash` is now optional and deprecated, and `DocverseClient.create_build`'s `content_hash` argument defaults to `None` — when unset the key is left out of the request body rather than sent as `null`. Docverse now computes a build's content identity itself while processing the upload and overwrites whatever the client supplied, so the field is documented as a transport-integrity and provenance digest of the uploaded tarball that makes no claim about content identity, and it will be removed in a future release. Nothing breaks today: the `sha256:<64 hex>` pattern is still enforced on any value that is supplied, and `docverse upload` still computes the tarball digest with `create_tarball` and sends it. There is no runtime deprecation warning — the notice is carried by the field descriptions and by a `deprecated: true` flag on the field's JSON Schema, both of which surface in the server's OpenAPI schema so client codegen can act on it.
@@ -0,0 +1,3 @@
1
+ ### New features
2
+
3
+ - `BuildStatus` gains two terminal values that a build can now be reported in: `superseded`, for a build that a newer live build for the same `(project, git_ref)` took over before it was processed, and `cancelled`, for a build deleted before processing finished. Neither means anything went wrong — that is still `failed` — but neither build will ever be published. Both are accepted by the `status` filter on `GET /orgs/{org}/projects/{project}/builds`, so `docverse` callers can list them directly. The client dist is unreleased, so there is no compatibility shim: code that exhaustively matches on `BuildStatus` needs arms for the new values.
@@ -0,0 +1,3 @@
1
+ ### New features
2
+
3
+ - `BuildProcessingProgress` gains a `deleted_skipped` field. It is `true` on a `build_processing` job that completed without publishing anything because the build was deleted — whether the delete landed before the worker picked the job up or while the worker was still uploading — so the build is `cancelled` rather than published. The key already round-tripped through `extra="allow"`; declaring it puts the flag in the generated OpenAPI schema so callers can tell this outcome apart from a real publish.
@@ -0,0 +1,3 @@
1
+ ### Other changes
2
+
3
+ - The minimum supported `httpx` version is raised from 0.25 to 0.26. httpx 0.25 pins `httpcore<0.19`, and `httpcore` 0.18's anyio backend evaluates `anyio.abc.ByteStream` eagerly at class-definition time. anyio 4.15.0 made `anyio.abc` a lazily imported submodule, so importing `httpcore` before anything else imports `anyio.abc` raises `AttributeError: module 'anyio' has no attribute 'abc'`. httpx 0.26 requires `httpcore` 1.x, whose anyio backend uses postponed annotations and is unaffected.
@@ -0,0 +1,3 @@
1
+ ### New features
2
+
3
+ - `BuildProcessingProgress` gains a `retired_status` field, typed by a new `RetiredBuildStatus` enum exported from `docverse.models`. It names the state a build was found in when its worker stood down — `cancelled` for a delete or lifecycle reap, `failed` for the stranded-build sweep, `superseded` for a newer build taking over, and `missing` when the row was gone by the time the worker looked. The key already round-tripped through `extra="allow"`, but only for statuses that exist: the vanished-row case wrote `null`, which the model's drop-`None` serializer stripped, leaving that outcome unrepresentable. Declaring the field puts the value in the generated OpenAPI schema as an enum callers can switch on, and the vanished-row close-out now serialises `retired_status: "missing"`. `RetiredBuildStatus` is `BuildStatus` plus `missing`, because a soft-deleted build keeps whatever status it had — `missing` is not a `BuildStatus` since there is no row to persist it on.
@@ -0,0 +1,3 @@
1
+ ### New features
2
+
3
+ - `Build` gains `date_purged: datetime | None`, which reports when the build's object-store content was permanently reclaimed. It is the field to check before assuming a soft-deleted build can be restored: null means the content is still there, a timestamp means the row is a tombstone and the files are gone. The field defaults to `None`, so a response from a server that does not yet send it still parses.
@@ -0,0 +1,3 @@
1
+ ### New features
2
+
3
+ - `DocverseClient.restore_build(org, project, build)` restores a soft-deleted build and returns it. The build's status is not changed — one deleted before it finished comes back `cancelled` — since a restore recovers the row and its content rather than re-running the build. A build whose content has already been reclaimed raises `DocverseClientError` with `status_code` 409; check `Build.date_purged` before assuming a deleted build can be brought back.
@@ -0,0 +1,3 @@
1
+ ### Backwards-incompatible changes
2
+
3
+ - `Project`, `Organization`, and `OrganizationSummary` gain a required `id` field carrying the resource's Crockford Base32 public identifier. Parsing a response from a Docverse server older than this release will fail validation; upgrade the server alongside the client.
@@ -0,0 +1,3 @@
1
+ ### New features
2
+
3
+ - `Project` gains an optional `date_deleted` field carrying the project's soft-deletion timestamp, `None` for a live project. A project is only returned deleted when the request asked for it with `include_deleted=true`, but the field parses either way, so a consumer can tell a deletion from a project that simply stopped appearing in the listing.
@@ -0,0 +1,3 @@
1
+ ### New features
2
+
3
+ - New `DocverseClient.list_projects(org, ...)` returns an organization's whole project listing as a `ProjectList`, walking the `Link rel="next"` chain so paging is the client's problem rather than the caller's. It forwards `updated_since` (as an ISO 8601 timestamp with its offset), `include_deleted`, and `order`, and takes an `if_none_match` validator that it sends on the first request alone — a precondition describes the page the caller already holds, not the cursor URLs that follow. The result carries the first page's `etag` back verbatim for the next poll; a 304 there yields `not_modified=True` with an empty `projects` list and no further requests.
@@ -0,0 +1,3 @@
1
+ ### Other changes
2
+
3
+ - `DocverseClient.list_projects` now backdates `updated_since` by an overlap window before sending it, 60 seconds by default (`DEFAULT_UPDATED_SINCE_OVERLAP`, overridable per call with the new `updated_since_overlap` keyword; `timedelta(0)` restores the old behaviour). A project's `date_updated` is stamped with PostgreSQL's transaction *start* clock and commit order is not start order, so a slow write can become visible after a poll that has already passed its timestamp; asking from slightly earlier is what keeps that row from being skipped forever. The consequence is that a filtered pass re-sends rows, so treat the listing as a set of upserts keyed on each project's `id` rather than as a stream of distinct changes.
@@ -0,0 +1,3 @@
1
+ ### Other changes
2
+
3
+ - `ProjectList.etag` now says plainly that it is the validator to poll with, and the only one Docverse offers: the server sends no `Last-Modified` and ignores an `If-Modified-Since`. The tag is derived from the listing watermark at full precision, so it notices a change however soon after the previous one it landed and whichever order the two writes committed in.
@@ -0,0 +1,3 @@
1
+ ### Other changes
2
+
3
+ - `Project.default_edition` is now populated on every row that `DocverseClient.list_projects` returns, not only on single-project responses; the field's description no longer says it is omitted from list responses. It stays `None` only for a soft-deleted project.
@@ -0,0 +1,7 @@
1
+ ### Backwards-incompatible changes
2
+
3
+ ### New features
4
+
5
+ ### Bug fixes
6
+
7
+ ### Other changes
@@ -0,0 +1,69 @@
1
+ [project]
2
+ name = "docverse"
3
+ description = "Python client for the Docverse documentation hosting platform."
4
+ license = "MIT"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dynamic = ["version"]
8
+ dependencies = [
9
+ "click>=8",
10
+ "httpx>=0.26",
11
+ "pydantic>=2.5",
12
+ ]
13
+
14
+ [project.scripts]
15
+ docverse = "docverse._cli:main"
16
+
17
+ [build-system]
18
+ requires = [
19
+ "setuptools>=75",
20
+ "setuptools_scm[toml]>=8",
21
+ ]
22
+ build-backend = "setuptools.build_meta"
23
+
24
+ [dependency-groups]
25
+ dev = [
26
+ "coverage[toml]",
27
+ "pytest",
28
+ "pytest-asyncio",
29
+ "respx",
30
+ ]
31
+
32
+ [tool.setuptools_scm]
33
+ root = ".."
34
+ # Match client release tags (e.g. "client/0.1.0"). The explicit tag_regex is
35
+ # required because setuptools_scm's default cannot parse slash-prefixed tags.
36
+ tag_regex = '^client/(?P<version>[0-9].*)$'
37
+ git_describe_command = [
38
+ "git",
39
+ "describe",
40
+ "--dirty",
41
+ "--tags",
42
+ "--long",
43
+ "--match",
44
+ "client/[0-9]*",
45
+ ]
46
+ fallback_version = "0.0.0"
47
+
48
+ [tool.setuptools.packages.find]
49
+ where = ["src"]
50
+
51
+ [tool.ruff]
52
+ extend = "../ruff-shared.toml"
53
+
54
+ [tool.ruff.lint.extend-per-file-ignores]
55
+ "src/docverse/_cli.py" = [
56
+ "PLR0917", # Too many positional arguments (Click builds the CLI from
57
+ # the decorated command signature; nothing calls it directly).
58
+ ]
59
+
60
+ [tool.coverage.run]
61
+ parallel = true
62
+ branch = true
63
+ source = ["docverse"]
64
+
65
+ [tool.coverage.paths]
66
+ source = ["src"]
67
+
68
+ [tool.coverage.report]
69
+ show_missing = true
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,22 @@
1
+ """Docverse client library."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ from ._client import DEFAULT_UPDATED_SINCE_OVERLAP, DocverseClient, ProjectList
6
+ from ._exceptions import BuildProcessingError, DocverseClientError
7
+ from ._tar import create_tarball
8
+
9
+ __all__ = [
10
+ "DEFAULT_UPDATED_SINCE_OVERLAP",
11
+ "BuildProcessingError",
12
+ "DocverseClient",
13
+ "DocverseClientError",
14
+ "ProjectList",
15
+ "__version__",
16
+ "create_tarball",
17
+ ]
18
+
19
+ try:
20
+ __version__ = version("docverse")
21
+ except PackageNotFoundError:
22
+ __version__ = "0.0.0"
@@ -0,0 +1,63 @@
1
+ """Auto-detection of build provenance annotations from CI environments."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+
7
+ from .models.builds import BuildAnnotations
8
+
9
+ __all__ = [
10
+ "detect_github_actions_annotations",
11
+ "merge_annotations",
12
+ ]
13
+
14
+
15
+ def detect_github_actions_annotations() -> BuildAnnotations | None:
16
+ """Detect build provenance from GitHub Actions environment variables.
17
+
18
+ Returns a populated ``BuildAnnotations`` if running inside GitHub
19
+ Actions (``GITHUB_ACTIONS == "true"``), otherwise ``None``.
20
+ """
21
+ if os.environ.get("GITHUB_ACTIONS") != "true":
22
+ return None
23
+
24
+ server_url = os.environ.get("GITHUB_SERVER_URL", "https://github.com")
25
+ repository = os.environ.get("GITHUB_REPOSITORY")
26
+ run_id = os.environ.get("GITHUB_RUN_ID")
27
+
28
+ run_url: str | None = None
29
+ if repository and run_id:
30
+ run_url = f"{server_url}/{repository}/actions/runs/{run_id}"
31
+
32
+ return BuildAnnotations(
33
+ commit_sha=os.environ.get("GITHUB_SHA"),
34
+ github_repository=repository,
35
+ github_run_id=run_id,
36
+ github_run_url=run_url,
37
+ github_run_attempt=os.environ.get("GITHUB_RUN_ATTEMPT"),
38
+ github_workflow=os.environ.get("GITHUB_WORKFLOW"),
39
+ github_actor=os.environ.get("GITHUB_ACTOR"),
40
+ github_event_name=os.environ.get("GITHUB_EVENT_NAME"),
41
+ ci_platform="github-actions",
42
+ )
43
+
44
+
45
+ def merge_annotations(
46
+ auto: BuildAnnotations | None,
47
+ manual: dict[str, str] | None,
48
+ ) -> BuildAnnotations | None:
49
+ """Merge auto-detected and manual annotations.
50
+
51
+ Manual entries take precedence over auto-detected values. Returns
52
+ ``None`` if both inputs are empty/None.
53
+ """
54
+ result: dict[str, str] = {}
55
+ if auto is not None:
56
+ result.update(
57
+ {k: v for k, v in auto.model_dump().items() if v is not None}
58
+ )
59
+ if manual:
60
+ result.update(manual)
61
+ if not result:
62
+ return None
63
+ return BuildAnnotations.model_validate(result)