sediment-api 0.2.0__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.
- {sediment_api-0.2.0 → sediment_api-0.4.0}/.gitignore +1 -3
- {sediment_api-0.2.0 → sediment_api-0.4.0}/PKG-INFO +6 -6
- {sediment_api-0.2.0 → sediment_api-0.4.0}/pyproject.toml +6 -6
- {sediment_api-0.2.0 → sediment_api-0.4.0}/sediment_api/__init__.py +1 -1
- {sediment_api-0.2.0 → sediment_api-0.4.0}/sediment_api/config.py +5 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/sediment_api/main.py +6 -2
- {sediment_api-0.2.0 → sediment_api-0.4.0}/sediment_api/routers/ci_vendor.py +2 -2
- sediment_api-0.4.0/tests/test_database_role_startup.py +136 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_v1.py +1 -1
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_worker_processes.py +10 -6
- {sediment_api-0.2.0 → sediment_api-0.4.0}/LICENSE +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/sediment_api/database.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/sediment_api/deps.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/sediment_api/mirror_gc.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/sediment_api/reports/__init__.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/sediment_api/reports/abandonment_report.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/sediment_api/reports/attribution_share_report.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/sediment_api/reports/dataset_diagnostics.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/sediment_api/reports/label_confidence_inspection.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/sediment_api/reports/lifecycle_report.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/sediment_api/reports/merge_retention_report.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/sediment_api/reports/model_report.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/sediment_api/reports/precision_report.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/sediment_api/reports/recovery_yield_report.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/sediment_api/routers/__init__.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/sediment_api/routers/forge.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/sediment_api/routers/gateway.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/sediment_api/routers/otlp.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/sediment_api/routers/query.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/sediment_api/routers/reports.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/sediment_api/routers/v1.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/sediment_api/services/__init__.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/sediment_api/services/operational_reports.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/sediment_api/worker.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/sediment_api/workers.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/conftest.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_attribution_share_report.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_baseline_gap_repro.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_bug_repro_offset.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_capture_record_isolation.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_ci_vendor.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_config.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_context_discovery_authority.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_context_discovery_query.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_context_discovery_workers.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_context_evidence_query.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_context_query.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_credential_authorities.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_database_lifecycle.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_database_outage.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_dataset_diagnostics_report.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_e2e_smoke.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_evidence_query.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_evidence_worker_imports.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_evidence_workers.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_gateway_capture_receipt.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_gateway_identity.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_lifecycle_report_cli.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_merge_retention_cli.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_one_shot_database.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_operational_reports.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_operational_reports_service.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_postgres_gateway_tracer.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_push_mirror.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_query.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_report_attachment_scope.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_report_routes.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_repository_identity_ingest.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_repository_identity_model_service.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_repository_identity_queries.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_retrieval_authority.py +0 -0
- {sediment_api-0.2.0 → sediment_api-0.4.0}/tests/test_worker_routes.py +0 -0
|
@@ -13,8 +13,7 @@ exports/
|
|
|
13
13
|
!docs/exports/
|
|
14
14
|
.env
|
|
15
15
|
.DS_Store
|
|
16
|
-
.claude/
|
|
17
|
-
.claude/worktrees/
|
|
16
|
+
.claude/
|
|
18
17
|
|
|
19
18
|
# Deployment-local compose overrides (mirror credentials etc.) — never in PRs
|
|
20
19
|
compose.override.yml
|
|
@@ -29,4 +28,3 @@ litellm/config.local.yaml
|
|
|
29
28
|
!.env.example
|
|
30
29
|
.agents/
|
|
31
30
|
skills-lock.json
|
|
32
|
-
.claude/skills/asd-ste100
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sediment-api
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Sediment ingest API: facts in through three doors
|
|
5
5
|
Project-URL: Source, https://github.com/sediment-ai/sediment
|
|
6
6
|
Project-URL: Documentation, https://github.com/sediment-ai/sediment/tree/main/docs
|
|
@@ -13,8 +13,8 @@ Requires-Dist: anyio>=4.15.1
|
|
|
13
13
|
Requires-Dist: fastapi>=0.141.1
|
|
14
14
|
Requires-Dist: httpx>=0.27
|
|
15
15
|
Requires-Dist: pydantic-settings>=2.15.0
|
|
16
|
-
Requires-Dist: sediment-capture==0.
|
|
17
|
-
Requires-Dist: sediment-core==0.
|
|
18
|
-
Requires-Dist: sediment-derive==0.
|
|
19
|
-
Requires-Dist: sediment-export==0.
|
|
20
|
-
Requires-Dist: uvicorn>=0.
|
|
16
|
+
Requires-Dist: sediment-capture==0.4.0
|
|
17
|
+
Requires-Dist: sediment-core==0.4.0
|
|
18
|
+
Requires-Dist: sediment-derive==0.4.0
|
|
19
|
+
Requires-Dist: sediment-export==0.4.0
|
|
20
|
+
Requires-Dist: uvicorn>=0.54.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sediment-api"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
description = "Sediment ingest API: facts in through three doors"
|
|
5
5
|
requires-python = ">=3.12"
|
|
6
6
|
license = "AGPL-3.0-or-later"
|
|
@@ -10,12 +10,12 @@ dependencies = [
|
|
|
10
10
|
"anyio>=4.15.1",
|
|
11
11
|
"fastapi>=0.141.1",
|
|
12
12
|
"httpx>=0.27",
|
|
13
|
-
"uvicorn>=0.
|
|
13
|
+
"uvicorn>=0.54.0",
|
|
14
14
|
"pydantic-settings>=2.15.0",
|
|
15
|
-
"sediment-core==0.
|
|
16
|
-
"sediment-capture==0.
|
|
17
|
-
"sediment-derive==0.
|
|
18
|
-
"sediment-export==0.
|
|
15
|
+
"sediment-core==0.4.0",
|
|
16
|
+
"sediment-capture==0.4.0",
|
|
17
|
+
"sediment-derive==0.4.0",
|
|
18
|
+
"sediment-export==0.4.0",
|
|
19
19
|
]
|
|
20
20
|
|
|
21
21
|
[project.urls]
|
|
@@ -51,6 +51,11 @@ class Settings(BaseSettings):
|
|
|
51
51
|
# PostgreSQL is the sole active fact store. SecretStr prevents settings
|
|
52
52
|
# representations and validation diagnostics from echoing credentials.
|
|
53
53
|
database_url: SecretStr
|
|
54
|
+
# Operator-named database roles (ADR 0027); empty means the default name.
|
|
55
|
+
# The lifespan validates them, keeping postgres_roles out of workers.
|
|
56
|
+
migrator_role: str = ""
|
|
57
|
+
runtime_role: str = ""
|
|
58
|
+
operator_role: str = ""
|
|
54
59
|
|
|
55
60
|
# Raw git substrate: base dir for the per-(org, repo) bare mirrors.
|
|
56
61
|
# None (unset) disables mirror refresh entirely — facts still flow;
|
|
@@ -22,7 +22,7 @@ from sediment_core.postgres_migrations import (
|
|
|
22
22
|
RevisionState,
|
|
23
23
|
inspect_engine_revision,
|
|
24
24
|
)
|
|
25
|
-
from sediment_core.postgres_roles import validate_runtime_privileges
|
|
25
|
+
from sediment_core.postgres_roles import RoleNames, validate_runtime_privileges
|
|
26
26
|
from sqlalchemy.exc import (
|
|
27
27
|
DisconnectionError,
|
|
28
28
|
InterfaceError,
|
|
@@ -67,6 +67,10 @@ for _warning in settings.config_warnings():
|
|
|
67
67
|
async def lifespan(application: FastAPI):
|
|
68
68
|
"""Own one PostgreSQL engine and verify the exact schema before traffic."""
|
|
69
69
|
database_url = settings.database_url.get_secret_value()
|
|
70
|
+
# Invalid role names fail here, with their own message, before any I/O.
|
|
71
|
+
roles = RoleNames.configured(
|
|
72
|
+
settings.migrator_role, settings.runtime_role, settings.operator_role
|
|
73
|
+
)
|
|
70
74
|
engine = None
|
|
71
75
|
workers = None
|
|
72
76
|
try:
|
|
@@ -82,7 +86,7 @@ async def lifespan(application: FastAPI):
|
|
|
82
86
|
f"{HEAD_REVISION}, found {inspection.state.value}"
|
|
83
87
|
)
|
|
84
88
|
if not settings.dev_mode:
|
|
85
|
-
validate_runtime_privileges(engine)
|
|
89
|
+
validate_runtime_privileges(engine, roles)
|
|
86
90
|
application.state.database_engine = engine
|
|
87
91
|
application.state.fact_store = FactStore(engine)
|
|
88
92
|
application.state.database_target = sanitized_database_target(database_url)
|
|
@@ -68,8 +68,8 @@ class VendorCIRequest(BaseModel):
|
|
|
68
68
|
)
|
|
69
69
|
run_id: NonEmptyId = Field(
|
|
70
70
|
description=(
|
|
71
|
-
"The provider
|
|
72
|
-
"
|
|
71
|
+
"The CI provider's pipeline-run ID. Identified runs also carry a "
|
|
72
|
+
"forge provider and host; run_attempt distinguishes attempts."
|
|
73
73
|
)
|
|
74
74
|
)
|
|
75
75
|
run_attempt: int | None = Field(default=None, ge=1, le=2**63 - 1, strict=True)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
"""Production startup against operator-named roles provisioned without Sediment."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from uuid import uuid4
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
from psycopg import sql
|
|
10
|
+
from pydantic import SecretStr
|
|
11
|
+
from sqlalchemy import create_engine
|
|
12
|
+
from sqlalchemy.engine import make_url
|
|
13
|
+
from sqlalchemy.pool import NullPool
|
|
14
|
+
|
|
15
|
+
from sediment_core.postgres_engine import DatabaseOperationError
|
|
16
|
+
from sediment_core.postgres_roles import (
|
|
17
|
+
DatabasePrivilegeError,
|
|
18
|
+
RoleNames,
|
|
19
|
+
administrator_sql,
|
|
20
|
+
migrate_database,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# Creates cluster-wide roles under names no other module uses; see
|
|
24
|
+
# packages/core/tests/test_postgres_roles.py for the default-name contract.
|
|
25
|
+
pytestmark = pytest.mark.cluster_roles
|
|
26
|
+
ROLES = RoleNames("sediment_test_api_m", "sediment_test_api_r", "sediment_test_api_o")
|
|
27
|
+
ADMIN, ADMIN_PASSWORD = "sediment_test_api_admin", "api-admin-test-secret"
|
|
28
|
+
PASSWORDS = {role: f"{role}-secret" for role in ROLES}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.fixture
|
|
32
|
+
def provisioned(postgres_admin_url):
|
|
33
|
+
"""The administrator's printed SQL applied by a non-superuser owner."""
|
|
34
|
+
superuser = create_engine(
|
|
35
|
+
postgres_admin_url, isolation_level="AUTOCOMMIT", poolclass=NullPool
|
|
36
|
+
)
|
|
37
|
+
database = f"sediment_api_role_test_{uuid4().hex}"
|
|
38
|
+
engines = []
|
|
39
|
+
|
|
40
|
+
def url(role, password=None):
|
|
41
|
+
return (
|
|
42
|
+
make_url(postgres_admin_url)
|
|
43
|
+
.set(
|
|
44
|
+
database=database,
|
|
45
|
+
username=role,
|
|
46
|
+
password=password or PASSWORDS.get(role, ADMIN_PASSWORD),
|
|
47
|
+
)
|
|
48
|
+
.render_as_string(hide_password=False)
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def connect(role, password=None):
|
|
52
|
+
engine = create_engine(url(role, password), poolclass=NullPool)
|
|
53
|
+
engines.append(engine)
|
|
54
|
+
return engine
|
|
55
|
+
|
|
56
|
+
with superuser.connect() as connection:
|
|
57
|
+
connection.exec_driver_sql(
|
|
58
|
+
f"CREATE ROLE {ADMIN} LOGIN CREATEROLE CREATEDB PASSWORD '{ADMIN_PASSWORD}'"
|
|
59
|
+
)
|
|
60
|
+
connection.exec_driver_sql(f'CREATE DATABASE "{database}" OWNER {ADMIN}')
|
|
61
|
+
try:
|
|
62
|
+
with connect(ADMIN).connect() as connection:
|
|
63
|
+
driver = connection.connection.driver_connection
|
|
64
|
+
driver.execute(administrator_sql(database, ROLES))
|
|
65
|
+
for role, secret in PASSWORDS.items():
|
|
66
|
+
driver.execute(
|
|
67
|
+
sql.SQL("ALTER ROLE {} PASSWORD {}").format(
|
|
68
|
+
sql.Identifier(role), sql.Literal(secret)
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
driver.commit()
|
|
72
|
+
yield url, connect
|
|
73
|
+
finally:
|
|
74
|
+
for engine in engines:
|
|
75
|
+
engine.dispose()
|
|
76
|
+
with superuser.connect() as connection:
|
|
77
|
+
connection.exec_driver_sql(f'DROP DATABASE "{database}" WITH (FORCE)')
|
|
78
|
+
for role in (*ROLES, ADMIN):
|
|
79
|
+
connection.exec_driver_sql(f'DROP ROLE IF EXISTS "{role}"')
|
|
80
|
+
superuser.dispose()
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def start(monkeypatch, database_url):
|
|
84
|
+
from fastapi.testclient import TestClient
|
|
85
|
+
|
|
86
|
+
from sediment_api.config import settings
|
|
87
|
+
from sediment_api.main import app
|
|
88
|
+
|
|
89
|
+
monkeypatch.setattr(settings, "database_url", SecretStr(database_url))
|
|
90
|
+
monkeypatch.setattr(settings, "dev_mode", False)
|
|
91
|
+
monkeypatch.setattr(settings, "migrator_role", ROLES.migrator)
|
|
92
|
+
monkeypatch.setattr(settings, "runtime_role", ROLES.runtime)
|
|
93
|
+
monkeypatch.setattr(settings, "operator_role", ROLES.operator)
|
|
94
|
+
with TestClient(app) as client:
|
|
95
|
+
assert client.get("/health").status_code == 200
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_startup_refuses_until_the_migrate_step_grants_then_serves(
|
|
99
|
+
provisioned, monkeypatch
|
|
100
|
+
):
|
|
101
|
+
url, connect = provisioned
|
|
102
|
+
with pytest.raises(DatabaseOperationError, match="found absent"):
|
|
103
|
+
start(monkeypatch, url(ROLES.runtime))
|
|
104
|
+
assert migrate_database(url(ROLES.migrator), ROLES)
|
|
105
|
+
start(monkeypatch, url(ROLES.runtime))
|
|
106
|
+
# A table that Alembic created without grants blocks startup again.
|
|
107
|
+
with connect(ROLES.migrator).begin() as connection:
|
|
108
|
+
connection.exec_driver_sql(
|
|
109
|
+
f"REVOKE ALL ON inference_call_aliases FROM {ROLES.runtime}"
|
|
110
|
+
)
|
|
111
|
+
with pytest.raises(DatabasePrivilegeError, match="inference_call_aliases"):
|
|
112
|
+
start(monkeypatch, url(ROLES.runtime))
|
|
113
|
+
assert migrate_database(url(ROLES.migrator), ROLES)
|
|
114
|
+
start(monkeypatch, url(ROLES.runtime))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_startup_uses_a_runtime_password_the_role_rotated_itself(
|
|
118
|
+
provisioned, monkeypatch
|
|
119
|
+
):
|
|
120
|
+
url, connect = provisioned
|
|
121
|
+
assert migrate_database(url(ROLES.migrator), ROLES)
|
|
122
|
+
with connect(ROLES.runtime).connect() as connection:
|
|
123
|
+
driver = connection.connection.driver_connection
|
|
124
|
+
driver.execute("ALTER ROLE CURRENT_USER PASSWORD 'rotated-runtime-secret'")
|
|
125
|
+
driver.commit()
|
|
126
|
+
start(monkeypatch, url(ROLES.runtime, "rotated-runtime-secret"))
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_startup_refuses_the_migrator_and_the_administrator(provisioned, monkeypatch):
|
|
130
|
+
url, _ = provisioned
|
|
131
|
+
assert migrate_database(url(ROLES.migrator), ROLES)
|
|
132
|
+
with pytest.raises(DatabasePrivilegeError, match=ROLES.runtime):
|
|
133
|
+
start(monkeypatch, url(ROLES.migrator))
|
|
134
|
+
# The administrator can't even read the schema revision.
|
|
135
|
+
with pytest.raises(DatabaseOperationError, match="verify database startup"):
|
|
136
|
+
start(monkeypatch, url(ADMIN))
|
|
@@ -42,10 +42,13 @@ def test_deadline_kills_process_group_before_slot_reuse(monkeypatch, tmp_path):
|
|
|
42
42
|
"signal.signal(signal.SIGTERM, signal.SIG_IGN)\n"
|
|
43
43
|
"child = subprocess.Popen([sys.executable, '-c', "
|
|
44
44
|
"'import signal,time; signal.signal(signal.SIGTERM, signal.SIG_IGN); time.sleep(60)'])\n"
|
|
45
|
-
f"
|
|
45
|
+
f"from pathlib import Path; t = Path({str(marker)!r} + '.tmp')\n"
|
|
46
|
+
"t.write_text(f'{os.getpid()} {child.pid}')\n"
|
|
47
|
+
f"t.replace({str(marker)!r})\n"
|
|
46
48
|
"time.sleep(60)\n",
|
|
47
49
|
)
|
|
48
|
-
|
|
50
|
+
# The budget outlasts startup, so the marker exists before the kill.
|
|
51
|
+
monkeypatch.setattr(workers, "QUERY_BUDGET_SECONDS", 1.0)
|
|
49
52
|
monkeypatch.setattr(workers, "TERMINATION_GRACE_SECONDS", 0.1)
|
|
50
53
|
|
|
51
54
|
async def check():
|
|
@@ -98,7 +101,7 @@ def test_cancellation_and_shutdown_reap_child(monkeypatch, tmp_path, operation):
|
|
|
98
101
|
marker = tmp_path / "pid"
|
|
99
102
|
_command(
|
|
100
103
|
monkeypatch,
|
|
101
|
-
f"import os,time;
|
|
104
|
+
f"import os,time; from pathlib import Path; t=Path({str(marker)!r}+'.tmp'); t.write_text(str(os.getpid())); t.replace({str(marker)!r}); time.sleep(60)",
|
|
102
105
|
)
|
|
103
106
|
|
|
104
107
|
async def check():
|
|
@@ -234,7 +237,7 @@ def test_cancellation_during_spawn_does_not_orphan_the_started_process(
|
|
|
234
237
|
marker = tmp_path / "pid"
|
|
235
238
|
_command(
|
|
236
239
|
monkeypatch,
|
|
237
|
-
f"import os,time;
|
|
240
|
+
f"import os,time; from pathlib import Path; t=Path({str(marker)!r}+'.tmp'); t.write_text(str(os.getpid())); t.replace({str(marker)!r}); time.sleep(60)",
|
|
238
241
|
)
|
|
239
242
|
spawn = asyncio.create_subprocess_exec
|
|
240
243
|
|
|
@@ -335,9 +338,10 @@ def test_dying_process_group_permission_race_is_rechecked(monkeypatch, tmp_path)
|
|
|
335
338
|
marker = tmp_path / "pid"
|
|
336
339
|
_command(
|
|
337
340
|
monkeypatch,
|
|
338
|
-
f"import os,time;
|
|
341
|
+
f"import os,time; from pathlib import Path; t=Path({str(marker)!r}+'.tmp'); t.write_text(str(os.getpid())); t.replace({str(marker)!r}); time.sleep(60)",
|
|
339
342
|
)
|
|
340
|
-
|
|
343
|
+
# The budget outlasts startup, so the marker exists before the kill.
|
|
344
|
+
monkeypatch.setattr(workers, "QUERY_BUDGET_SECONDS", 1.0)
|
|
341
345
|
signal_group = os.killpg
|
|
342
346
|
raced = False
|
|
343
347
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sediment_api-0.2.0 → sediment_api-0.4.0}/sediment_api/reports/label_confidence_inspection.py
RENAMED
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|