mainsequence 6.0.20__tar.gz → 6.0.22__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.
- {mainsequence-6.0.20/mainsequence.egg-info → mainsequence-6.0.22}/PKG-INFO +1 -1
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/cli/api.py +1 -1
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/cli/cli.py +8 -6
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/cli/migrations.py +0 -9
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/client/metatables/core.py +84 -54
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/client/utils.py +16 -33
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/logconf.py +23 -39
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/meta_tables/migrations/__init__.py +2 -51
- mainsequence-6.0.22/mainsequence/runtime_context.py +145 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22/mainsequence.egg-info}/PKG-INFO +1 -1
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence.egg-info/SOURCES.txt +1 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/pyproject.toml +1 -1
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_auth_precedence.py +90 -5
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_cli.py +1 -1
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_cli_migrations.py +7 -8
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_filter_normalization.py +32 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_logconf.py +18 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_meta_table_migrations.py +4 -8
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_meta_tables_client_models.py +28 -5
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_pod_project_resolution.py +150 -3
- {mainsequence-6.0.20 → mainsequence-6.0.22}/LICENSE +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/README.md +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/agent_scaffold/AGENTS.md +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/agent_scaffold/skills/a2a_sdk_execution/SKILL.md +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/agent_scaffold/skills/data_publishing/meta_table_migrations/SKILL.md +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/agent_scaffold/skills/maintenance/project-maintenance/SKILL.md +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/agent_scaffold/skills/sdk_project_execution/SKILL.md +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/__init__.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/__main__.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/bootstrap.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/cli/__init__.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/cli/browser_auth.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/cli/config.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/cli/docker_utils.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/cli/doctor.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/cli/local_ops.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/cli/model_filters.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/cli/project_status.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/cli/pydantic_cli.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/cli/sdk_utils.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/cli/ssh_utils.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/cli/ui.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/client/__init__.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/client/agent_runtime_models.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/client/base.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/client/client.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/client/compute_validation.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/client/data_sources_interfaces/local_paths.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/client/dtype_codec.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/client/exceptions.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/client/fastapi/__init__.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/client/fastapi/auth.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/client/metatables/__init__.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/client/models_foundry.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/client/models_helpers.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/client/models_user.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/defaults.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/instrumentation/__init__.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/instrumentation/utils.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/meta_tables/__init__.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/meta_tables/__main__.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/meta_tables/compiled_sql/__init__.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/meta_tables/compiled_sql/v1.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/meta_tables/data_nodes/__init__.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/meta_tables/data_nodes/build_operations.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/meta_tables/data_nodes/data_nodes.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/meta_tables/data_nodes/models.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/meta_tables/data_nodes/namespacing.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/meta_tables/data_nodes/persist_managers.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/meta_tables/data_nodes/run_operations.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/meta_tables/data_nodes/utils.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/meta_tables/future_registry.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/meta_tables/hashing.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/meta_tables/migrations/alembic.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/meta_tables/migrations/env.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/meta_tables/migrations/provider.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/meta_tables/migrations/registry.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/meta_tables/migrations/scaffold.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/meta_tables/migrations/templates/__init__.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/meta_tables/migrations/templates/env.py.mako +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/meta_tables/migrations/templates/script.py.mako +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/meta_tables/pydantic_metadata.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/meta_tables/schema_names.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/meta_tables/sqlalchemy_contracts.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/project_skills.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/runtime_flags.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence/scaffold_skills.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence.egg-info/dependency_links.txt +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence.egg-info/entry_points.txt +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence.egg-info/requires.txt +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/mainsequence.egg-info/top_level.txt +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/setup.cfg +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_build_operations_hashing.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_canonical_api_route_contract.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_cli_browser_auth.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_client.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_data_access_mixin_dimension_audit.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_data_node_storage_dimension_queries.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_data_node_update_flow.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_dependency_extras.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_duckdb_interface_dimensions.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_instrumentation.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_meta_tables_sqlalchemy_contracts.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_models_user_request_bound_auth.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_project_batch_jobs_from_file.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_project_branch_contract.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_project_skills.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_run_configuration.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_scaffold_skills.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_schema_names.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_secret_client_model.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_source_table_configuration.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_sqlite_interface_dimensions.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_update_runner_uid_runtime.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_update_statistics.py +0 -0
- {mainsequence-6.0.20 → mainsequence-6.0.22}/tests/test_update_uid_guards.py +0 -0
|
@@ -4689,7 +4689,7 @@ def list_project_job_runs(
|
|
|
4689
4689
|
ClientJobRun.ROOT_URL = root_url
|
|
4690
4690
|
|
|
4691
4691
|
merged_filters = dict(filters or {})
|
|
4692
|
-
merged_filters["job__uid"] =
|
|
4692
|
+
merged_filters["job__uid"] = str(job_uid)
|
|
4693
4693
|
runs = ClientJobRun.filter(timeout=timeout, **merged_filters)
|
|
4694
4694
|
out: list[dict[str, Any]] = []
|
|
4695
4695
|
for run in runs:
|
|
@@ -2163,7 +2163,8 @@ def _current_project_runtime_auth_env(backend_url: str) -> dict[str, str]:
|
|
|
2163
2163
|
Return auth environment entries for local project `.env` provisioning.
|
|
2164
2164
|
|
|
2165
2165
|
The output follows the active auth mode:
|
|
2166
|
-
- runtime credential mode
|
|
2166
|
+
- a backend-injected runtime credential mode preserves its credential keys
|
|
2167
|
+
and an exchanged access token
|
|
2167
2168
|
- default JWT mode writes the current CLI session access/refresh token pair
|
|
2168
2169
|
"""
|
|
2169
2170
|
if _runtime_credential_mode_enabled():
|
|
@@ -2308,9 +2309,11 @@ def login(
|
|
|
2308
2309
|
Interactive login uses browser-based authentication and finishes with
|
|
2309
2310
|
standard JWT access/refresh tokens persisted by the CLI.
|
|
2310
2311
|
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2312
|
+
When a backend-launched process already has
|
|
2313
|
+
`MAINSEQUENCE_AUTH_MODE=runtime_credential`, login exchanges the injected
|
|
2314
|
+
runtime credential for a short-lived access token instead of opening the
|
|
2315
|
+
browser or persisting CLI JWT tokens. This is not a user branch/runtime
|
|
2316
|
+
selection mechanism.
|
|
2314
2317
|
|
|
2315
2318
|
Parameters
|
|
2316
2319
|
----------
|
|
@@ -2345,7 +2348,6 @@ def login(
|
|
|
2345
2348
|
mainsequence login --access-token "$TOKEN" --refresh-token "$REFRESH"
|
|
2346
2349
|
mainsequence login --access-token "$TOKEN" --refresh-token "$REFRESH" --backend http://127.0.0.1:8000 --projects-base mainsequence-dev
|
|
2347
2350
|
mainsequence login --export
|
|
2348
|
-
MAINSEQUENCE_AUTH_MODE=runtime_credential mainsequence login
|
|
2349
2351
|
```
|
|
2350
2352
|
"""
|
|
2351
2353
|
using_jwt = bool((access_token or "").strip() or (refresh_token or "").strip())
|
|
@@ -9222,7 +9224,7 @@ def project_job_runs_list_cmd(
|
|
|
9222
9224
|
"""
|
|
9223
9225
|
List runs for a specific job.
|
|
9224
9226
|
|
|
9225
|
-
Uses SDK client `JobRun.filter(job__uid=
|
|
9227
|
+
Uses SDK client `JobRun.filter(job__uid=job_uid)` as the single source of truth.
|
|
9226
9228
|
|
|
9227
9229
|
Examples
|
|
9228
9230
|
--------
|
|
@@ -18,7 +18,6 @@ from mainsequence.client.metatables import (
|
|
|
18
18
|
MetaTableMigrationConnectionRequest,
|
|
19
19
|
TimeIndexMetaTable,
|
|
20
20
|
)
|
|
21
|
-
from mainsequence.client.metatables.core import _current_metatable_project_context
|
|
22
21
|
from mainsequence.meta_tables.migrations import (
|
|
23
22
|
AlembicMetaTableMigration,
|
|
24
23
|
alembic_config_for_provider,
|
|
@@ -432,10 +431,8 @@ def _prepare_alembic_config(
|
|
|
432
431
|
alembic_output: _AlembicOutput,
|
|
433
432
|
prepare_provider_metatables: bool = True,
|
|
434
433
|
) -> tuple[Any, Any]:
|
|
435
|
-
project_context = _current_metatable_project_context()
|
|
436
434
|
_emit_status("Ensuring Alembic registry MetaTable...")
|
|
437
435
|
registry_meta_table = migration.ensure_alembic_registry(
|
|
438
|
-
project_context=project_context,
|
|
439
436
|
timeout=timeout,
|
|
440
437
|
on_metatable_reserved=_emit_metatable_reservation,
|
|
441
438
|
)
|
|
@@ -443,7 +440,6 @@ def _prepare_alembic_config(
|
|
|
443
440
|
if prepare_provider_metatables:
|
|
444
441
|
_emit_status("Preparing platform-managed MetaTable reservations...")
|
|
445
442
|
prepared = migration.prepare_for_alembic(
|
|
446
|
-
project_context=project_context,
|
|
447
443
|
timeout=timeout,
|
|
448
444
|
on_metatable_reservation_request=_emit_metatable_reservation_request,
|
|
449
445
|
on_metatable_reservation_status=_emit_status,
|
|
@@ -455,7 +451,6 @@ def _prepare_alembic_config(
|
|
|
455
451
|
data_source_uid=migration._resolve_provider_data_source_uid(),
|
|
456
452
|
meta_table_uids=[],
|
|
457
453
|
owner_role_name=None,
|
|
458
|
-
project_context=project_context,
|
|
459
454
|
)
|
|
460
455
|
_emit_status(
|
|
461
456
|
"Prepared migration scope "
|
|
@@ -472,7 +467,6 @@ def _prepare_alembic_config(
|
|
|
472
467
|
migration_namespace=migration.migration_namespace,
|
|
473
468
|
migration_provider_key=migration.migration_provider_key,
|
|
474
469
|
ttl_seconds=ttl_seconds,
|
|
475
|
-
project_context=prepared.project_context,
|
|
476
470
|
),
|
|
477
471
|
timeout=timeout,
|
|
478
472
|
)
|
|
@@ -501,10 +495,8 @@ def _build_revision_alembic_config(
|
|
|
501
495
|
_emit_status("Building local Alembic config for revision...")
|
|
502
496
|
owner_role_name = None
|
|
503
497
|
if requires_database and sqlalchemy_url in (None, ""):
|
|
504
|
-
project_context = _current_metatable_project_context()
|
|
505
498
|
_emit_status("Ensuring Alembic registry MetaTable for revision...")
|
|
506
499
|
registry_meta_table = migration.ensure_alembic_registry(
|
|
507
|
-
project_context=project_context,
|
|
508
500
|
timeout=timeout,
|
|
509
501
|
on_metatable_reserved=_emit_metatable_reservation,
|
|
510
502
|
)
|
|
@@ -518,7 +510,6 @@ def _build_revision_alembic_config(
|
|
|
518
510
|
migration_namespace=migration.migration_namespace,
|
|
519
511
|
migration_provider_key=migration.migration_provider_key,
|
|
520
512
|
ttl_seconds=ttl_seconds,
|
|
521
|
-
project_context=project_context,
|
|
522
513
|
),
|
|
523
514
|
timeout=timeout,
|
|
524
515
|
)
|
|
@@ -31,6 +31,7 @@ from pydantic import (
|
|
|
31
31
|
)
|
|
32
32
|
|
|
33
33
|
from mainsequence.logconf import logger
|
|
34
|
+
from mainsequence.runtime_context import _get_backend_runtime_project_context_state
|
|
34
35
|
|
|
35
36
|
from ..base import BaseObjectOrm, BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin
|
|
36
37
|
from ..data_sources_interfaces import get_duckdb_interface_class, get_sqlite_interface_class
|
|
@@ -53,6 +54,7 @@ from ..utils import (
|
|
|
53
54
|
bios_uuid,
|
|
54
55
|
get_network_ip,
|
|
55
56
|
is_process_running,
|
|
57
|
+
loaders,
|
|
56
58
|
make_request,
|
|
57
59
|
serialize_to_json,
|
|
58
60
|
)
|
|
@@ -868,7 +870,6 @@ class ManagedMetaTableCollectionCreateRow(BasePydanticModel):
|
|
|
868
870
|
physical_schema: str = Field(..., min_length=1)
|
|
869
871
|
physical_table_name: str = Field(..., min_length=1)
|
|
870
872
|
protect_from_deletion: bool = False
|
|
871
|
-
project_context: MetaTableProjectContextRequest
|
|
872
873
|
table_contract: dict[str, Any]
|
|
873
874
|
|
|
874
875
|
@model_validator(mode="before")
|
|
@@ -930,7 +931,6 @@ class MetaTableMigrationConnectionRequest(BasePydanticModel):
|
|
|
930
931
|
migration_namespace: str = ""
|
|
931
932
|
migration_provider_key: str = ""
|
|
932
933
|
ttl_seconds: int = Field(default=900, ge=60, le=3600)
|
|
933
|
-
project_context: MetaTableProjectContextRequest | None = None
|
|
934
934
|
|
|
935
935
|
|
|
936
936
|
class MetaTableMigrationConnection(BasePydanticModel):
|
|
@@ -947,7 +947,6 @@ class MetaTableMigrationConnection(BasePydanticModel):
|
|
|
947
947
|
|
|
948
948
|
|
|
949
949
|
class ManagedMetaTableFinalizeRequest(BasePydanticModel):
|
|
950
|
-
project_context: MetaTableProjectContextRequest | None = None
|
|
951
950
|
meta_table_uids: list[str] = Field(
|
|
952
951
|
...,
|
|
953
952
|
min_length=1,
|
|
@@ -1406,10 +1405,7 @@ class MetaTable(BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin, B
|
|
|
1406
1405
|
)
|
|
1407
1406
|
if isinstance(payload, Mapping):
|
|
1408
1407
|
payload = MetaTableMigrationConnectionRequest(**payload)
|
|
1409
|
-
|
|
1410
|
-
payload = payload.model_copy(
|
|
1411
|
-
update={"project_context": _current_metatable_project_context()}
|
|
1412
|
-
)
|
|
1408
|
+
payload = _with_current_metatable_project_context(payload)
|
|
1413
1409
|
response = self._post_detail_action(
|
|
1414
1410
|
"migration-connection",
|
|
1415
1411
|
payload,
|
|
@@ -1597,13 +1593,13 @@ class MetaTable(BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin, B
|
|
|
1597
1593
|
if request is not None and kwargs:
|
|
1598
1594
|
raise ValueError("Pass either request or keyword fields, not both.")
|
|
1599
1595
|
unvalidated_payload = _payload_json(request) if request is not None else dict(kwargs)
|
|
1600
|
-
|
|
1596
|
+
if "project_context" in unvalidated_payload:
|
|
1597
|
+
raise ValueError(
|
|
1598
|
+
"project_context is SDK-controlled and cannot be supplied by the caller."
|
|
1599
|
+
)
|
|
1601
1600
|
payload = MetaTableRegistrationRequest.model_validate(unvalidated_payload)
|
|
1602
1601
|
if payload.management_mode != "external_registered":
|
|
1603
|
-
|
|
1604
|
-
if project_context is not None:
|
|
1605
|
-
payload_json["project_context"] = project_context
|
|
1606
|
-
payload = _with_current_metatable_project_context(payload_json)
|
|
1602
|
+
payload = _with_current_metatable_project_context(payload)
|
|
1607
1603
|
response_json = cls._post_action(
|
|
1608
1604
|
"register",
|
|
1609
1605
|
payload,
|
|
@@ -1624,10 +1620,10 @@ class MetaTable(BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin, B
|
|
|
1624
1620
|
if on_status is not None:
|
|
1625
1621
|
on_status(f"Serializing POST {url} payload...")
|
|
1626
1622
|
payload_json = _payload_json_sequence(rows)
|
|
1627
|
-
payload_json = _with_current_metatable_project_context_collection(payload_json)
|
|
1628
1623
|
_ = [
|
|
1629
1624
|
cls.COLLECTION_CREATE_ROW_MODEL.model_validate(row) for row in payload_json
|
|
1630
1625
|
]
|
|
1626
|
+
payload_json = _with_current_metatable_project_context_collection(payload_json)
|
|
1631
1627
|
if on_status is not None:
|
|
1632
1628
|
payload_size = len(json.dumps(payload_json, default=str))
|
|
1633
1629
|
on_status(f"Serialized POST {url} payload bytes={payload_size}.")
|
|
@@ -1667,10 +1663,7 @@ class MetaTable(BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin, B
|
|
|
1667
1663
|
payload = request if request is not None else ManagedMetaTableFinalizeRequest(**kwargs)
|
|
1668
1664
|
if isinstance(payload, Mapping):
|
|
1669
1665
|
payload = ManagedMetaTableFinalizeRequest(**payload)
|
|
1670
|
-
|
|
1671
|
-
payload = payload.model_copy(
|
|
1672
|
-
update={"project_context": _current_metatable_project_context()}
|
|
1673
|
-
)
|
|
1666
|
+
payload = _with_current_metatable_project_context(payload)
|
|
1674
1667
|
response_json = cls._post_action(
|
|
1675
1668
|
"finalize-managed",
|
|
1676
1669
|
payload,
|
|
@@ -2458,15 +2451,26 @@ class DataNodeUpdate(TableUpdateNode, BaseObjectOrm):
|
|
|
2458
2451
|
@classmethod
|
|
2459
2452
|
def get_or_create(cls, **kwargs):
|
|
2460
2453
|
url = cls.get_object_url() + "/get-or-create/"
|
|
2454
|
+
if "current_project_branch_uid" in kwargs:
|
|
2455
|
+
raise ValueError(
|
|
2456
|
+
"current_project_branch_uid is SDK-controlled and cannot be supplied "
|
|
2457
|
+
"by the caller."
|
|
2458
|
+
)
|
|
2461
2459
|
kwargs = serialize_to_json(kwargs)
|
|
2462
|
-
|
|
2460
|
+
resolution = _resolve_local_pod_project()
|
|
2461
|
+
project_branch = resolution.project_branch
|
|
2462
|
+
if project_branch is None:
|
|
2463
|
+
project_branch = _require_local_pod_project_branch(
|
|
2464
|
+
"DataNodeUpdate.get_or_create"
|
|
2465
|
+
)
|
|
2463
2466
|
project_branch_uid = str(getattr(project_branch, "uid", "") or "").strip()
|
|
2464
2467
|
if not project_branch_uid:
|
|
2465
2468
|
raise RuntimeError(
|
|
2466
2469
|
"DataNodeUpdate.get_or_create requires a ProjectBranch uid, "
|
|
2467
2470
|
"but the active branch does not expose one."
|
|
2468
2471
|
)
|
|
2469
|
-
|
|
2472
|
+
if not resolution.backend_runtime_context:
|
|
2473
|
+
kwargs["current_project_branch_uid"] = project_branch_uid
|
|
2470
2474
|
payload = {"json": kwargs}
|
|
2471
2475
|
s = cls.build_session()
|
|
2472
2476
|
r = make_request(s=s, loaders=cls.LOADERS, r_type="POST", url=url, payload=payload)
|
|
@@ -3406,14 +3410,14 @@ class TimeIndexMetaTable(MetaTable):
|
|
|
3406
3410
|
if request is not None and kwargs:
|
|
3407
3411
|
raise ValueError("Pass either request or keyword fields, not both.")
|
|
3408
3412
|
unvalidated_payload = _payload_json(request) if request is not None else dict(kwargs)
|
|
3409
|
-
|
|
3413
|
+
if "project_context" in unvalidated_payload:
|
|
3414
|
+
raise ValueError(
|
|
3415
|
+
"project_context is SDK-controlled and cannot be supplied by the caller."
|
|
3416
|
+
)
|
|
3410
3417
|
payload = TimeIndexMetaTableRegistrationRequest.model_validate(
|
|
3411
3418
|
unvalidated_payload
|
|
3412
3419
|
)
|
|
3413
|
-
payload_json =
|
|
3414
|
-
if project_context is not None:
|
|
3415
|
-
payload_json["project_context"] = project_context
|
|
3416
|
-
payload_json = _with_current_metatable_project_context(payload_json)
|
|
3420
|
+
payload_json = _with_current_metatable_project_context(payload)
|
|
3417
3421
|
request_payload = {"json": serialize_to_json(payload_json)}
|
|
3418
3422
|
response = make_request(
|
|
3419
3423
|
s=cls.build_session(),
|
|
@@ -4780,6 +4784,7 @@ class _PodProjectResolution:
|
|
|
4780
4784
|
repository_branch: str | None
|
|
4781
4785
|
status: str
|
|
4782
4786
|
detail: str = ""
|
|
4787
|
+
backend_runtime_context: bool = False
|
|
4783
4788
|
|
|
4784
4789
|
@property
|
|
4785
4790
|
def remote_cache_key(self) -> tuple[str, str, str]:
|
|
@@ -4808,11 +4813,9 @@ def _reset_local_pod_project_resolution_cache() -> None:
|
|
|
4808
4813
|
|
|
4809
4814
|
def _current_repository_branch() -> str | None:
|
|
4810
4815
|
runtime_auth_mode = (os.environ.get("MAINSEQUENCE_AUTH_MODE") or "").strip()
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
if runtime_project_branch_uid:
|
|
4815
|
-
return (os.environ.get("MAINSEQUENCE_REPOSITORY_BRANCH") or "").strip() or None
|
|
4816
|
+
runtime_state = _get_backend_runtime_project_context_state()
|
|
4817
|
+
if runtime_state.verified and runtime_state.context is not None:
|
|
4818
|
+
return runtime_state.context.repository_branch
|
|
4816
4819
|
if runtime_auth_mode in {"session_jwt", "runtime_credential"}:
|
|
4817
4820
|
return None
|
|
4818
4821
|
try:
|
|
@@ -4837,11 +4840,11 @@ def _build_local_pod_project_resolution(
|
|
|
4837
4840
|
running_project_uid: str,
|
|
4838
4841
|
running_project_branch_uid: str = "",
|
|
4839
4842
|
repository_branch: str | None,
|
|
4843
|
+
backend_runtime_context: bool = False,
|
|
4840
4844
|
) -> _PodProjectResolution:
|
|
4841
4845
|
from ..models_foundry import Project, ProjectBranch
|
|
4842
4846
|
|
|
4843
|
-
|
|
4844
|
-
if runtime_auth_mode in {"session_jwt", "runtime_credential"} and (
|
|
4847
|
+
if backend_runtime_context and (
|
|
4845
4848
|
not running_project_uid or not running_project_branch_uid
|
|
4846
4849
|
):
|
|
4847
4850
|
return _PodProjectResolution(
|
|
@@ -4855,6 +4858,7 @@ def _build_local_pod_project_resolution(
|
|
|
4855
4858
|
"Deployed runtime authentication did not provide a complete "
|
|
4856
4859
|
"backend-issued ProjectBranch context."
|
|
4857
4860
|
),
|
|
4861
|
+
backend_runtime_context=True,
|
|
4858
4862
|
)
|
|
4859
4863
|
|
|
4860
4864
|
if not running_project_uid:
|
|
@@ -4994,6 +4998,7 @@ def _build_local_pod_project_resolution(
|
|
|
4994
4998
|
project_branch=project_branch,
|
|
4995
4999
|
repository_branch=resolved_repository_branch,
|
|
4996
5000
|
status="resolved",
|
|
5001
|
+
backend_runtime_context=backend_runtime_context,
|
|
4997
5002
|
)
|
|
4998
5003
|
|
|
4999
5004
|
|
|
@@ -5001,11 +5006,29 @@ def _resolve_local_pod_project(*, refresh: bool = False) -> _PodProjectResolutio
|
|
|
5001
5006
|
global _POD_PROJECT_RESOLUTION_CACHE, _POD_PROJECT_RESOLUTION_CACHE_KEY
|
|
5002
5007
|
global POD_PROJECT, POD_PROJECT_BRANCH
|
|
5003
5008
|
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
+
runtime_auth_mode = (os.environ.get("MAINSEQUENCE_AUTH_MODE") or "").strip()
|
|
5010
|
+
runtime_state = _get_backend_runtime_project_context_state()
|
|
5011
|
+
if runtime_auth_mode == "runtime_credential" and not runtime_state.verified:
|
|
5012
|
+
loaders.refresh_headers()
|
|
5013
|
+
runtime_state = _get_backend_runtime_project_context_state()
|
|
5014
|
+
|
|
5015
|
+
backend_runtime_context = runtime_auth_mode in {
|
|
5016
|
+
"session_jwt",
|
|
5017
|
+
"runtime_credential",
|
|
5018
|
+
}
|
|
5019
|
+
runtime_context = runtime_state.context if runtime_state.verified else None
|
|
5020
|
+
if backend_runtime_context and runtime_context is not None:
|
|
5021
|
+
running_project_uid = runtime_context.project_uid
|
|
5022
|
+
running_project_branch_uid = runtime_context.project_branch_uid
|
|
5023
|
+
repository_branch = runtime_context.repository_branch
|
|
5024
|
+
elif backend_runtime_context:
|
|
5025
|
+
running_project_uid = ""
|
|
5026
|
+
running_project_branch_uid = ""
|
|
5027
|
+
repository_branch = None
|
|
5028
|
+
else:
|
|
5029
|
+
running_project_uid = (os.environ.get("MAIN_SEQUENCE_PROJECT_UID") or "").strip()
|
|
5030
|
+
running_project_branch_uid = ""
|
|
5031
|
+
repository_branch = _current_repository_branch()
|
|
5009
5032
|
cache_key = (
|
|
5010
5033
|
running_project_uid,
|
|
5011
5034
|
running_project_branch_uid,
|
|
@@ -5022,6 +5045,7 @@ def _resolve_local_pod_project(*, refresh: bool = False) -> _PodProjectResolutio
|
|
|
5022
5045
|
running_project_uid=running_project_uid,
|
|
5023
5046
|
running_project_branch_uid=running_project_branch_uid,
|
|
5024
5047
|
repository_branch=repository_branch,
|
|
5048
|
+
backend_runtime_context=backend_runtime_context,
|
|
5025
5049
|
)
|
|
5026
5050
|
_POD_PROJECT_RESOLUTION_CACHE_KEY = cache_key
|
|
5027
5051
|
POD_PROJECT = _POD_PROJECT_RESOLUTION_CACHE.project
|
|
@@ -5089,18 +5113,28 @@ def _current_metatable_project_context() -> MetaTableProjectContextRequest:
|
|
|
5089
5113
|
return _metatable_project_context_from_resolution(_resolve_local_pod_project())
|
|
5090
5114
|
|
|
5091
5115
|
|
|
5116
|
+
def _metatable_project_context_for_request() -> MetaTableProjectContextRequest | None:
|
|
5117
|
+
resolution = _resolve_local_pod_project()
|
|
5118
|
+
if resolution.backend_runtime_context:
|
|
5119
|
+
if resolution.project_branch is None:
|
|
5120
|
+
_metatable_project_context_from_resolution(resolution)
|
|
5121
|
+
return None
|
|
5122
|
+
return _current_metatable_project_context()
|
|
5123
|
+
|
|
5124
|
+
|
|
5092
5125
|
def _with_current_metatable_project_context(
|
|
5093
5126
|
payload: Mapping[str, Any] | BasePydanticModel,
|
|
5094
5127
|
) -> dict[str, Any]:
|
|
5095
5128
|
payload_json = _payload_json(payload)
|
|
5129
|
+
if "project_context" in payload_json:
|
|
5130
|
+
raise ValueError(
|
|
5131
|
+
"project_context is SDK-controlled and cannot be supplied by the caller."
|
|
5132
|
+
)
|
|
5096
5133
|
if payload_json.get("management_mode") == "external_registered":
|
|
5097
5134
|
return payload_json
|
|
5098
|
-
project_context =
|
|
5099
|
-
if project_context is None:
|
|
5100
|
-
project_context =
|
|
5101
|
-
else:
|
|
5102
|
-
project_context = MetaTableProjectContextRequest.model_validate(project_context)
|
|
5103
|
-
payload_json["project_context"] = project_context.model_dump(mode="json")
|
|
5135
|
+
project_context = _metatable_project_context_for_request()
|
|
5136
|
+
if project_context is not None:
|
|
5137
|
+
payload_json["project_context"] = project_context.model_dump(mode="json")
|
|
5104
5138
|
return payload_json
|
|
5105
5139
|
|
|
5106
5140
|
|
|
@@ -5108,25 +5142,21 @@ def _with_current_metatable_project_context_collection(
|
|
|
5108
5142
|
payloads: Sequence[Mapping[str, Any]],
|
|
5109
5143
|
) -> list[dict[str, Any]]:
|
|
5110
5144
|
payload_rows = [dict(payload) for payload in payloads]
|
|
5145
|
+
if any("project_context" in row for row in payload_rows):
|
|
5146
|
+
raise ValueError(
|
|
5147
|
+
"project_context is SDK-controlled and cannot be supplied by the caller."
|
|
5148
|
+
)
|
|
5111
5149
|
managed_rows = [
|
|
5112
5150
|
row for row in payload_rows if row.get("management_mode") != "external_registered"
|
|
5113
5151
|
]
|
|
5114
5152
|
if not managed_rows:
|
|
5115
5153
|
return payload_rows
|
|
5116
5154
|
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
if current_project_context is None:
|
|
5122
|
-
current_project_context = _current_metatable_project_context().model_dump(
|
|
5123
|
-
mode="json"
|
|
5124
|
-
)
|
|
5155
|
+
project_context = _metatable_project_context_for_request()
|
|
5156
|
+
if project_context is not None:
|
|
5157
|
+
current_project_context = project_context.model_dump(mode="json")
|
|
5158
|
+
for row in managed_rows:
|
|
5125
5159
|
row["project_context"] = current_project_context
|
|
5126
|
-
else:
|
|
5127
|
-
row["project_context"] = MetaTableProjectContextRequest.model_validate(
|
|
5128
|
-
project_context
|
|
5129
|
-
).model_dump(mode="json")
|
|
5130
5160
|
return payload_rows
|
|
5131
5161
|
|
|
5132
5162
|
|
|
@@ -23,6 +23,11 @@ from urllib3.util.retry import Retry
|
|
|
23
23
|
|
|
24
24
|
from mainsequence.defaults import resolve_backend_endpoint
|
|
25
25
|
from mainsequence.logconf import logger
|
|
26
|
+
from mainsequence.runtime_context import (
|
|
27
|
+
BackendRuntimeProjectContextError,
|
|
28
|
+
_get_backend_runtime_project_context_state,
|
|
29
|
+
_install_backend_runtime_project_context,
|
|
30
|
+
)
|
|
26
31
|
|
|
27
32
|
# ---- Backend defaults (single source of truth) ----
|
|
28
33
|
MAINSEQUENCE_ENDPOINT = resolve_backend_endpoint()
|
|
@@ -62,40 +67,16 @@ class AuthError(Exception):
|
|
|
62
67
|
pass
|
|
63
68
|
|
|
64
69
|
|
|
65
|
-
|
|
66
|
-
"project_uid": "MAIN_SEQUENCE_PROJECT_UID",
|
|
67
|
-
"project_branch_uid": "MAIN_SEQUENCE_PROJECT_BRANCH_UID",
|
|
68
|
-
"repository_branch": "MAINSEQUENCE_REPOSITORY_BRANCH",
|
|
69
|
-
"organization_project_environment_uid": (
|
|
70
|
-
"MAIN_SEQUENCE_ORGANIZATION_PROJECT_ENVIRONMENT_UID"
|
|
71
|
-
),
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def apply_runtime_project_context(payload: object) -> None:
|
|
70
|
+
def _apply_runtime_project_context_from_exchange(payload: object) -> None:
|
|
76
71
|
if not isinstance(payload, dict):
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
raise AuthError("runtime_project_context must be an object.")
|
|
83
|
-
missing = [key for key in RUNTIME_PROJECT_CONTEXT_ENV if not context.get(key)]
|
|
84
|
-
if missing:
|
|
85
|
-
raise AuthError(
|
|
86
|
-
"runtime_project_context is incomplete: " + ", ".join(sorted(missing))
|
|
72
|
+
raise AuthError("Runtime credential exchange response must be an object.")
|
|
73
|
+
try:
|
|
74
|
+
_install_backend_runtime_project_context(
|
|
75
|
+
payload,
|
|
76
|
+
source="runtime_credential_exchange",
|
|
87
77
|
)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
current = str(os.environ.get(environment_name) or "").strip()
|
|
91
|
-
if current and current != expected:
|
|
92
|
-
raise AuthError(
|
|
93
|
-
"runtime_project_context does not match the existing deployed "
|
|
94
|
-
f"context for {key}."
|
|
95
|
-
)
|
|
96
|
-
for key, environment_name in RUNTIME_PROJECT_CONTEXT_ENV.items():
|
|
97
|
-
expected = str(context[key])
|
|
98
|
-
os.environ[environment_name] = expected
|
|
78
|
+
except BackendRuntimeProjectContextError as exc:
|
|
79
|
+
raise AuthError(str(exc)) from exc
|
|
99
80
|
|
|
100
81
|
|
|
101
82
|
def set_mainsequence_endpoint(endpoint: str) -> None:
|
|
@@ -238,6 +219,8 @@ class RuntimeCredentialAuthProvider(BaseAuthProvider):
|
|
|
238
219
|
return (os.getenv("MAINSEQUENCE_ACCESS_TOKEN") or "").strip() or None
|
|
239
220
|
|
|
240
221
|
def _needs_exchange(self) -> bool:
|
|
222
|
+
if not _get_backend_runtime_project_context_state().verified:
|
|
223
|
+
return True
|
|
241
224
|
access_token = self._current_access_token()
|
|
242
225
|
if not access_token:
|
|
243
226
|
return True
|
|
@@ -294,7 +277,7 @@ class RuntimeCredentialAuthProvider(BaseAuthProvider):
|
|
|
294
277
|
)
|
|
295
278
|
|
|
296
279
|
data = response.json()
|
|
297
|
-
|
|
280
|
+
_apply_runtime_project_context_from_exchange(data)
|
|
298
281
|
access = str(data.get("access") or "").strip()
|
|
299
282
|
if not access:
|
|
300
283
|
raise AuthError(
|
|
@@ -21,42 +21,26 @@ from structlog.stdlib import BoundLogger
|
|
|
21
21
|
|
|
22
22
|
from .defaults import resolve_backend_endpoint
|
|
23
23
|
from .instrumentation import OTelJSONRenderer
|
|
24
|
+
from .runtime_context import (
|
|
25
|
+
RUNTIME_PROJECT_CONTEXT_ENV,
|
|
26
|
+
BackendRuntimeContextSource,
|
|
27
|
+
BackendRuntimeProjectContextError,
|
|
28
|
+
_get_backend_runtime_project_context_state,
|
|
29
|
+
_install_backend_runtime_project_context,
|
|
30
|
+
)
|
|
24
31
|
from .runtime_flags import is_running_in_pod
|
|
25
32
|
|
|
26
33
|
logger = None
|
|
27
34
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def _apply_runtime_project_context(payload: Mapping[str, Any]) -> None:
|
|
39
|
-
context = payload.get("runtime_project_context")
|
|
40
|
-
if context is None:
|
|
41
|
-
return
|
|
42
|
-
if not isinstance(context, Mapping):
|
|
43
|
-
raise RuntimeError("runtime_project_context must be an object.")
|
|
44
|
-
missing = [key for key in RUNTIME_PROJECT_CONTEXT_ENV if not context.get(key)]
|
|
45
|
-
if missing:
|
|
46
|
-
raise RuntimeError(
|
|
47
|
-
"runtime_project_context is incomplete: " + ", ".join(sorted(missing))
|
|
48
|
-
)
|
|
49
|
-
for key, environment_name in RUNTIME_PROJECT_CONTEXT_ENV.items():
|
|
50
|
-
expected = str(context[key])
|
|
51
|
-
current = str(os.environ.get(environment_name) or "").strip()
|
|
52
|
-
if current and current != expected:
|
|
53
|
-
raise RuntimeError(
|
|
54
|
-
"runtime_project_context does not match the existing deployed "
|
|
55
|
-
f"context for {key}."
|
|
56
|
-
)
|
|
57
|
-
for key, environment_name in RUNTIME_PROJECT_CONTEXT_ENV.items():
|
|
58
|
-
expected = str(context[key])
|
|
59
|
-
os.environ[environment_name] = expected
|
|
35
|
+
def _apply_runtime_project_context(
|
|
36
|
+
payload: Mapping[str, Any],
|
|
37
|
+
*,
|
|
38
|
+
source: BackendRuntimeContextSource,
|
|
39
|
+
) -> None:
|
|
40
|
+
try:
|
|
41
|
+
_install_backend_runtime_project_context(payload, source=source)
|
|
42
|
+
except BackendRuntimeProjectContextError as exc:
|
|
43
|
+
raise RuntimeError(str(exc)) from exc
|
|
60
44
|
|
|
61
45
|
|
|
62
46
|
def _get_sdk_version() -> str:
|
|
@@ -157,7 +141,7 @@ def _request_job_startup_state(*, timeout_s: float = 10.0) -> dict[str, Any]:
|
|
|
157
141
|
if not access_token:
|
|
158
142
|
return False
|
|
159
143
|
|
|
160
|
-
_apply_runtime_project_context(data)
|
|
144
|
+
_apply_runtime_project_context(data, source="runtime_credential_exchange")
|
|
161
145
|
os.environ["MAINSEQUENCE_ACCESS_TOKEN"] = access_token
|
|
162
146
|
return True
|
|
163
147
|
|
|
@@ -256,7 +240,8 @@ def _apply_additional_environment(startup_state: Mapping[str, Any]) -> None:
|
|
|
256
240
|
"runtime_project_context_missing: JobRun startup state did not "
|
|
257
241
|
"include backend-issued ProjectBranch context."
|
|
258
242
|
)
|
|
259
|
-
|
|
243
|
+
if "runtime_project_context" in startup_state:
|
|
244
|
+
_apply_runtime_project_context(startup_state, source="job_run_startup")
|
|
260
245
|
extra = startup_state.get("additional_environment")
|
|
261
246
|
if isinstance(extra, dict):
|
|
262
247
|
for k, v in extra.items():
|
|
@@ -274,11 +259,10 @@ def _build_backend_bindings(
|
|
|
274
259
|
"job_run_uid": startup_state.get("job_run_uid"),
|
|
275
260
|
"command_id": startup_state.get("command_id"),
|
|
276
261
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
bindings[field] = runtime_context.get(field) or os.environ.get(environment_name)
|
|
262
|
+
runtime_state = _get_backend_runtime_project_context_state()
|
|
263
|
+
runtime_context = runtime_state.context.as_dict() if runtime_state.context else {}
|
|
264
|
+
for field in RUNTIME_PROJECT_CONTEXT_ENV:
|
|
265
|
+
bindings[field] = runtime_context.get(field)
|
|
282
266
|
|
|
283
267
|
if not bindings.get("project_branch_uid"):
|
|
284
268
|
if "user_id" in startup_state:
|