mainsequence 8.1.20.dev4__tar.gz → 8.1.20.dev7__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-8.1.20.dev4/mainsequence.egg-info → mainsequence-8.1.20.dev7}/PKG-INFO +1 -1
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/base.py +14 -1
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/metatables/core.py +48 -65
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/models_foundry.py +0 -29
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7/mainsequence.egg-info}/PKG-INFO +1 -1
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence.egg-info/SOURCES.txt +1 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/pyproject.toml +1 -1
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_canonical_api_route_contract.py +18 -1
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_code_repository_branch_contract.py +6 -2
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_code_repository_runtime_context.py +7 -3
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_environment_response_contracts.py +5 -5
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_filter_normalization.py +63 -32
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_observability_client.py +6 -5
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_source_table_configuration.py +14 -8
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_time_index_table_update_configuration_client.py +6 -3
- mainsequence-8.1.20.dev7/tests/test_tolerant_response_reading.py +137 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_update_uid_guards.py +0 -1
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/LICENSE +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/MANIFEST.in +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/README.md +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/agent_scaffold/AGENTS.md +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/agent_scaffold/skills/a2a_sdk_execution/SKILL.md +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/agent_scaffold/skills/data_publishing/meta_table_migrations/SKILL.md +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/agent_scaffold/skills/data_publishing/time_index_table_updates/SKILL.md +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/agent_scaffold/skills/maintenance/code_repository_maintenance/SKILL.md +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/agent_scaffold/skills/sdk_code_repository_execution/SKILL.md +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/__init__.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/__main__.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/bootstrap.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/cli/__init__.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/cli/api.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/cli/browser_auth.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/cli/cli.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/cli/code_repository_status.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/cli/config.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/cli/docker_utils.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/cli/doctor.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/cli/local_ops.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/cli/migrations.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/cli/model_filters.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/cli/pydantic_cli.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/cli/sdk_utils.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/cli/ssh_utils.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/cli/ui.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/__init__.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/agent_runtime_models.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/client.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/compute_validation.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/data_sources_interfaces/local_paths.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/dtype_codec.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/exceptions.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/github_issues.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/metatables/__init__.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/models_helpers.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/models_user.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/observability.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/utils.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/code_repository_context.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/code_repository_skills.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/defaults.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/instrumentation/__init__.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/instrumentation/utils.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/logconf.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/__init__.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/__main__.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/compiled_sql/__init__.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/compiled_sql/v1.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/future_registry.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/hashing.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/migrations/__init__.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/migrations/alembic.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/migrations/env.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/migrations/provider.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/migrations/registry.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/migrations/scaffold.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/migrations/templates/__init__.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/migrations/templates/env.py.mako +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/migrations/templates/script.py.mako +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/pydantic_metadata.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/schema_names.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/sqlalchemy_contracts.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/time_index_table_refs.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/time_index_table_updates/__init__.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/time_index_table_updates/configuration.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/time_index_table_updates/dependencies.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/time_index_table_updates/managers.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/time_index_table_updates/namespacing.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/time_index_table_updates/runner.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/time_index_table_updates/updaters.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/time_index_table_updates/utils.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/repository_identity_security.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/runtime_flags.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/scaffold_skills.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence.egg-info/dependency_links.txt +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence.egg-info/entry_points.txt +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence.egg-info/requires.txt +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence.egg-info/top_level.txt +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/setup.cfg +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/fixtures/time_index_table_update_hash_v2_golden.json +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_auth_precedence.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_cli.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_cli_browser_auth.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_cli_migrations.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_client.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_code_repository_batch_jobs_from_file.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_code_repository_documentation_contract.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_code_repository_skills.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_dependency_extras.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_dev_release_version.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_duckdb_interface_dimensions.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_environment_resource_context.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_github_issues_client.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_instrumentation.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_local_ops.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_logconf.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_meta_table_migrations.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_meta_tables_client_models.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_meta_tables_sqlalchemy_contracts.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_models_user_request_bound_auth.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_notification_uid_contract.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_pod_project_resolution.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_scaffold_skills.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_schema_names.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_secret_client_model.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_sqlite_interface_dimensions.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_ssh_utils.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_time_index_table_access_dimension_audit.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_time_index_table_dimension_queries.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_time_index_table_update_configuration.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_time_index_table_update_flow.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_time_index_table_update_hard_cut.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_update_runner_uid_runtime.py +0 -0
- {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_update_statistics.py +0 -0
|
@@ -58,7 +58,9 @@ class HtmlSaveException(Exception):
|
|
|
58
58
|
|
|
59
59
|
|
|
60
60
|
class BasePydanticModel(BaseModel):
|
|
61
|
-
|
|
61
|
+
# Tolerant reading (ADR 0032): a backend response that carries a field this
|
|
62
|
+
# SDK release does not declare parses, and the undeclared field is dropped.
|
|
63
|
+
model_config = ConfigDict(extra="ignore")
|
|
62
64
|
orm_class: str = None # This will be set to the class that inherits
|
|
63
65
|
|
|
64
66
|
@classmethod
|
|
@@ -780,10 +782,21 @@ class BaseObjectOrm:
|
|
|
780
782
|
|
|
781
783
|
return raw_key
|
|
782
784
|
|
|
785
|
+
def _declares_field(obj, field_name: str) -> bool:
|
|
786
|
+
if not isinstance(obj, BaseModel):
|
|
787
|
+
return True
|
|
788
|
+
if field_name in type(obj).model_fields:
|
|
789
|
+
return True
|
|
790
|
+
# Tolerant reading (ADR 0032): a model that keeps undeclared keys can
|
|
791
|
+
# take them; every other model drops them, as parsing does.
|
|
792
|
+
return type(obj).model_config.get("extra") == "allow"
|
|
793
|
+
|
|
783
794
|
def recursive_update(obj, update_dict, path=()):
|
|
784
795
|
for k, v in update_dict.items():
|
|
785
796
|
current_path = (*path, k)
|
|
786
797
|
field_name = _resolve_model_field_name(obj, k)
|
|
798
|
+
if not _declares_field(obj, field_name):
|
|
799
|
+
continue
|
|
787
800
|
# Get the existing nested object, defaulting to None if it doesn't exist
|
|
788
801
|
nested_obj = getattr(obj, field_name, None)
|
|
789
802
|
|
{mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/metatables/core.py
RENAMED
|
@@ -1945,54 +1945,6 @@ class MetaTable(
|
|
|
1945
1945
|
raise_for_response(response, payload=error_payload)
|
|
1946
1946
|
return response.json()
|
|
1947
1947
|
|
|
1948
|
-
@classmethod
|
|
1949
|
-
def description_search(
|
|
1950
|
-
cls,
|
|
1951
|
-
q: str,
|
|
1952
|
-
*,
|
|
1953
|
-
q_embedding: Sequence[float] | None = None,
|
|
1954
|
-
trigram_k: int = 200,
|
|
1955
|
-
embed_k: int = 200,
|
|
1956
|
-
w_trgm: float = 0.65,
|
|
1957
|
-
w_emb: float = 0.35,
|
|
1958
|
-
embedding_model: str = "default",
|
|
1959
|
-
**filters,
|
|
1960
|
-
):
|
|
1961
|
-
q = (q or "").strip()
|
|
1962
|
-
if not q:
|
|
1963
|
-
raise ValueError("q is required")
|
|
1964
|
-
operation = f"{cls.__name__}.description_search"
|
|
1965
|
-
cls._reject_sdk_context_overrides(filters, operation=operation)
|
|
1966
|
-
|
|
1967
|
-
body = {
|
|
1968
|
-
"q": q,
|
|
1969
|
-
"trigram_k": trigram_k,
|
|
1970
|
-
"embed_k": embed_k,
|
|
1971
|
-
"w_trgm": w_trgm,
|
|
1972
|
-
"w_emb": w_emb,
|
|
1973
|
-
"embedding_model": embedding_model,
|
|
1974
|
-
}
|
|
1975
|
-
|
|
1976
|
-
if q_embedding is not None:
|
|
1977
|
-
body["q_embedding"] = [float(x) for x in q_embedding]
|
|
1978
|
-
|
|
1979
|
-
if filters:
|
|
1980
|
-
body.update(filters)
|
|
1981
|
-
body.update(cls._sdk_owned_query_context(operation))
|
|
1982
|
-
|
|
1983
|
-
payload = {"params": serialize_to_json(body)}
|
|
1984
|
-
response = make_request(
|
|
1985
|
-
s=cls.build_session(),
|
|
1986
|
-
loaders=cls.LOADERS,
|
|
1987
|
-
r_type="GET",
|
|
1988
|
-
url=f"{cls.get_object_url().rstrip('/')}/description-search/",
|
|
1989
|
-
payload=payload,
|
|
1990
|
-
)
|
|
1991
|
-
if response.status_code != 200:
|
|
1992
|
-
raise_for_response(response, payload=payload)
|
|
1993
|
-
|
|
1994
|
-
return cls._deserialize_search_response(response.json())
|
|
1995
|
-
|
|
1996
1948
|
@classmethod
|
|
1997
1949
|
def column_search(cls, q: str, **filters):
|
|
1998
1950
|
q = (q or "").strip()
|
|
@@ -2768,23 +2720,6 @@ class TimeIndexTableUpdate(TableUpdateNode, BaseObjectOrm):
|
|
|
2768
2720
|
return None
|
|
2769
2721
|
return r.json()
|
|
2770
2722
|
|
|
2771
|
-
def verify_if_direct_dependencies_are_updated(self) -> dict:
|
|
2772
|
-
"""
|
|
2773
|
-
Response({
|
|
2774
|
-
"error_on_update_dependencies": False,
|
|
2775
|
-
"updated": all_success,
|
|
2776
|
-
})
|
|
2777
|
-
"""
|
|
2778
|
-
s = self.build_session()
|
|
2779
|
-
url = (
|
|
2780
|
-
self.get_object_url()
|
|
2781
|
-
+ f"/{self._public_uid()}/verify-if-direct-dependencies-are-updated/"
|
|
2782
|
-
)
|
|
2783
|
-
r = make_request(s=s, loaders=None, r_type="GET", url=url)
|
|
2784
|
-
if r.status_code != 200:
|
|
2785
|
-
raise Exception(f"Error in request: {r.text}")
|
|
2786
|
-
return r.json()
|
|
2787
|
-
|
|
2788
2723
|
def get_data_between_dates_from_api(self, *args, **kwargs):
|
|
2789
2724
|
return self.output_table.get_data_between_dates_from_api(*args, **kwargs)
|
|
2790
2725
|
|
|
@@ -3259,6 +3194,54 @@ class TimeIndexMetaTable(MetaTable):
|
|
|
3259
3194
|
)
|
|
3260
3195
|
return self
|
|
3261
3196
|
|
|
3197
|
+
@classmethod
|
|
3198
|
+
def description_search(
|
|
3199
|
+
cls,
|
|
3200
|
+
q: str,
|
|
3201
|
+
*,
|
|
3202
|
+
q_embedding: Sequence[float] | None = None,
|
|
3203
|
+
trigram_k: int = 200,
|
|
3204
|
+
embed_k: int = 200,
|
|
3205
|
+
w_trgm: float = 0.65,
|
|
3206
|
+
w_emb: float = 0.35,
|
|
3207
|
+
embedding_model: str = "default",
|
|
3208
|
+
**filters,
|
|
3209
|
+
):
|
|
3210
|
+
q = (q or "").strip()
|
|
3211
|
+
if not q:
|
|
3212
|
+
raise ValueError("q is required")
|
|
3213
|
+
operation = f"{cls.__name__}.description_search"
|
|
3214
|
+
cls._reject_sdk_context_overrides(filters, operation=operation)
|
|
3215
|
+
|
|
3216
|
+
body = {
|
|
3217
|
+
"q": q,
|
|
3218
|
+
"trigram_k": trigram_k,
|
|
3219
|
+
"embed_k": embed_k,
|
|
3220
|
+
"w_trgm": w_trgm,
|
|
3221
|
+
"w_emb": w_emb,
|
|
3222
|
+
"embedding_model": embedding_model,
|
|
3223
|
+
}
|
|
3224
|
+
|
|
3225
|
+
if q_embedding is not None:
|
|
3226
|
+
body["q_embedding"] = [float(x) for x in q_embedding]
|
|
3227
|
+
|
|
3228
|
+
if filters:
|
|
3229
|
+
body.update(filters)
|
|
3230
|
+
body.update(cls._sdk_owned_query_context(operation))
|
|
3231
|
+
|
|
3232
|
+
payload = {"params": serialize_to_json(body)}
|
|
3233
|
+
response = make_request(
|
|
3234
|
+
s=cls.build_session(),
|
|
3235
|
+
loaders=cls.LOADERS,
|
|
3236
|
+
r_type="GET",
|
|
3237
|
+
url=f"{cls.get_object_url().rstrip('/')}/description-search/",
|
|
3238
|
+
payload=payload,
|
|
3239
|
+
)
|
|
3240
|
+
if response.status_code != 200:
|
|
3241
|
+
raise_for_response(response, payload=payload)
|
|
3242
|
+
|
|
3243
|
+
return cls._deserialize_search_response(response.json())
|
|
3244
|
+
|
|
3262
3245
|
def _time_indexed_storage_layout(self) -> dict[str, Any]:
|
|
3263
3246
|
profile = self.time_indexed_profile
|
|
3264
3247
|
if profile is not None and isinstance(profile.storage_layout, Mapping):
|
|
@@ -697,13 +697,6 @@ class GitHubRepositoryBranchList(BasePydanticModel):
|
|
|
697
697
|
results: list[GitHubRepositoryBranch]
|
|
698
698
|
|
|
699
699
|
|
|
700
|
-
class GitHubRepositoryBranchImportResult(BasePydanticModel):
|
|
701
|
-
repository_uid: str
|
|
702
|
-
code_repository_branch_uid: str
|
|
703
|
-
repository_branch: str
|
|
704
|
-
is_initialized: bool
|
|
705
|
-
|
|
706
|
-
|
|
707
700
|
class GitHubRepositoryBinding(BasePydanticModel, BaseObjectOrm):
|
|
708
701
|
FILTERSET_FIELDS: ClassVar[dict[str, list[str]]] = {
|
|
709
702
|
"uid": ["in", "exact"],
|
|
@@ -731,28 +724,6 @@ class GitHubRepositoryBinding(BasePydanticModel, BaseObjectOrm):
|
|
|
731
724
|
raise_for_response(r)
|
|
732
725
|
return GitHubRepositoryBranchList.model_validate(r.json())
|
|
733
726
|
|
|
734
|
-
def import_branch(
|
|
735
|
-
self,
|
|
736
|
-
*,
|
|
737
|
-
repository_branch: str,
|
|
738
|
-
metatables_data_source_uid: str | None = None,
|
|
739
|
-
timeout=None,
|
|
740
|
-
) -> GitHubRepositoryBranchImportResult:
|
|
741
|
-
body: dict[str, Any] = {"repository_branch": repository_branch}
|
|
742
|
-
if metatables_data_source_uid is not None:
|
|
743
|
-
body["metatables_data_source_uid"] = str(metatables_data_source_uid)
|
|
744
|
-
url = f"{type(self).get_object_url()}/{self._public_detail_reference()}/import-branch/"
|
|
745
|
-
r = make_request(
|
|
746
|
-
s=type(self).build_session(),
|
|
747
|
-
loaders=type(self).LOADERS,
|
|
748
|
-
r_type="POST",
|
|
749
|
-
url=url,
|
|
750
|
-
payload={"json": body},
|
|
751
|
-
time_out=timeout,
|
|
752
|
-
)
|
|
753
|
-
raise_for_response(r)
|
|
754
|
-
return GitHubRepositoryBranchImportResult.model_validate(r.json())
|
|
755
|
-
|
|
756
727
|
|
|
757
728
|
class CodeRepositoryImageSourceProvenance(BasePydanticModel):
|
|
758
729
|
verification_state: Literal["verified", "unverified", "building", "failed"]
|
|
@@ -133,6 +133,7 @@ tests/test_time_index_table_update_configuration.py
|
|
|
133
133
|
tests/test_time_index_table_update_configuration_client.py
|
|
134
134
|
tests/test_time_index_table_update_flow.py
|
|
135
135
|
tests/test_time_index_table_update_hard_cut.py
|
|
136
|
+
tests/test_tolerant_response_reading.py
|
|
136
137
|
tests/test_update_runner_uid_runtime.py
|
|
137
138
|
tests/test_update_statistics.py
|
|
138
139
|
tests/test_update_uid_guards.py
|
{mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_canonical_api_route_contract.py
RENAMED
|
@@ -129,11 +129,18 @@ def test_client_auth_provider_routes_match_backend_contract(monkeypatch):
|
|
|
129
129
|
|
|
130
130
|
def test_removed_backend_actions_are_not_exposed_by_client_models():
|
|
131
131
|
from mainsequence.client.metatables import DataSource, TimeIndexTableUpdate
|
|
132
|
+
from mainsequence.client.models_foundry import GitHubRepositoryBinding
|
|
132
133
|
from mainsequence.client.models_helpers import Job
|
|
133
134
|
|
|
134
135
|
removed_actions = {
|
|
135
136
|
DataSource: ("get_or_create_sqlite", "create_sqlite"),
|
|
136
|
-
TimeIndexTableUpdate: (
|
|
137
|
+
TimeIndexTableUpdate: (
|
|
138
|
+
"add_tags",
|
|
139
|
+
"filter_by_hash_id",
|
|
140
|
+
"get_upstream_nodes",
|
|
141
|
+
"verify_if_direct_dependencies_are_updated",
|
|
142
|
+
),
|
|
143
|
+
GitHubRepositoryBinding: ("import_branch",),
|
|
137
144
|
Job: ("bulk_get_or_create", "create_from_configuration", "sync_jobs"),
|
|
138
145
|
}
|
|
139
146
|
violations = [
|
|
@@ -146,6 +153,16 @@ def test_removed_backend_actions_are_not_exposed_by_client_models():
|
|
|
146
153
|
assert violations == []
|
|
147
154
|
|
|
148
155
|
|
|
156
|
+
def test_description_search_is_defined_only_where_the_backend_serves_it():
|
|
157
|
+
"""`description-search/` is served for time-index-meta-tables only."""
|
|
158
|
+
from mainsequence.client.metatables import MetaTable, TimeIndexMetaTable
|
|
159
|
+
|
|
160
|
+
assert "description_search" not in vars(MetaTable)
|
|
161
|
+
assert not hasattr(MetaTable, "description_search")
|
|
162
|
+
assert "description_search" in vars(TimeIndexMetaTable)
|
|
163
|
+
assert TimeIndexMetaTable.get_object_url().endswith("/time-index-meta-tables")
|
|
164
|
+
|
|
165
|
+
|
|
149
166
|
def test_cli_does_not_reintroduce_legacy_numeric_detail_routes_or_agent_runs():
|
|
150
167
|
from mainsequence.cli import api
|
|
151
168
|
from mainsequence.client import agent_runtime_models
|
{mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_code_repository_branch_contract.py
RENAMED
|
@@ -82,11 +82,15 @@ def test_code_repository_is_only_the_logical_aggregate_contract():
|
|
|
82
82
|
assert not hasattr(code_repository.branches[0], "code_repository_type")
|
|
83
83
|
assert not hasattr(code_repository, "repository_branch")
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
# Tolerant reading (ADR 0032): the retired field is dropped, not declared.
|
|
86
|
+
assert "repository_branch" not in models_foundry.CodeRepository.model_fields
|
|
87
|
+
assert not hasattr(
|
|
86
88
|
models_foundry.CodeRepository(
|
|
87
89
|
**logical_code_repository_payload(),
|
|
88
90
|
repository_branch="main",
|
|
89
|
-
)
|
|
91
|
+
),
|
|
92
|
+
"repository_branch",
|
|
93
|
+
)
|
|
90
94
|
|
|
91
95
|
|
|
92
96
|
def test_code_repository_requires_canonical_type_and_derived_technology():
|
{mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_code_repository_runtime_context.py
RENAMED
|
@@ -722,7 +722,7 @@ def test_metatable_request_always_submits_git_resolved_context(monkeypatch):
|
|
|
722
722
|
}
|
|
723
723
|
|
|
724
724
|
|
|
725
|
-
def
|
|
725
|
+
def test_code_repository_model_drops_removed_default_data_source_fields():
|
|
726
726
|
payload = {
|
|
727
727
|
"uid": CODE_REPOSITORY_UID,
|
|
728
728
|
"code_repository_name": "Example CodeRepository",
|
|
@@ -737,5 +737,9 @@ def test_code_repository_model_rejects_removed_default_data_source_fields():
|
|
|
737
737
|
"default_metatables_data_source_uid": DATA_SOURCE_UID,
|
|
738
738
|
}
|
|
739
739
|
|
|
740
|
-
|
|
741
|
-
|
|
740
|
+
# Tolerant reading (ADR 0032): the retired field is dropped, not declared.
|
|
741
|
+
assert "default_metatables_data_source_uid" not in models_foundry.CodeRepository.model_fields
|
|
742
|
+
assert not hasattr(
|
|
743
|
+
models_foundry.CodeRepository.model_validate(payload),
|
|
744
|
+
"default_metatables_data_source_uid",
|
|
745
|
+
)
|
{mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_environment_response_contracts.py
RENAMED
|
@@ -87,13 +87,13 @@ RUNTIME_UPDATE_FIELD = "runtime_update"
|
|
|
87
87
|
),
|
|
88
88
|
],
|
|
89
89
|
)
|
|
90
|
-
def
|
|
90
|
+
def test_backend_environment_response_contract_is_declared_by_sdk_model(
|
|
91
91
|
serializer_names,
|
|
92
92
|
model,
|
|
93
93
|
expected_fields,
|
|
94
94
|
):
|
|
95
95
|
"""Keep SDK response models aligned with ADR-0036/ADR-0037 projections."""
|
|
96
|
-
assert model.model_config.get("extra") == "
|
|
96
|
+
assert model.model_config.get("extra") == "ignore", serializer_names
|
|
97
97
|
assert expected_fields <= set(model.model_fields), serializer_names
|
|
98
98
|
|
|
99
99
|
|
|
@@ -106,13 +106,13 @@ def test_backend_environment_response_contract_is_declared_by_strict_sdk_model(
|
|
|
106
106
|
helper_models.ResourceRelease,
|
|
107
107
|
],
|
|
108
108
|
)
|
|
109
|
-
def
|
|
110
|
-
assert model.model_config.get("extra") == "
|
|
109
|
+
def test_owner_observability_code_repositoryion_is_declared_by_sdk_model(model):
|
|
110
|
+
assert model.model_config.get("extra") == "ignore"
|
|
111
111
|
assert "observability" in model.model_fields
|
|
112
112
|
|
|
113
113
|
|
|
114
114
|
def test_agent_session_runtime_capabilities_code_repositoryion_is_declared_and_typed():
|
|
115
|
-
assert agent_models.AgentSession.model_config.get("extra") == "
|
|
115
|
+
assert agent_models.AgentSession.model_config.get("extra") == "ignore"
|
|
116
116
|
assert (
|
|
117
117
|
agent_models.AgentSession.model_fields["runtime_capabilities"].annotation == dict[str, str]
|
|
118
118
|
)
|
|
@@ -67,7 +67,7 @@ def test_agent_runtime_update_parses_every_backend_state(
|
|
|
67
67
|
) == remediation_tool
|
|
68
68
|
|
|
69
69
|
|
|
70
|
-
def
|
|
70
|
+
def test_agent_runtime_update_contract_is_complete_and_read_tolerantly():
|
|
71
71
|
payload = _agent_runtime_update_contract("unknown")
|
|
72
72
|
for field_name in ("state", "needs_redeploy", "remediation"):
|
|
73
73
|
incomplete = dict(payload)
|
|
@@ -75,8 +75,12 @@ def test_agent_runtime_update_contract_is_complete_and_closed():
|
|
|
75
75
|
with pytest.raises(ValidationError, match=field_name):
|
|
76
76
|
agent_models_mod.AgentRuntimeUpdate.model_validate(incomplete)
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
# Tolerant reading (ADR 0032): a field this release does not declare is dropped.
|
|
79
|
+
tolerated = agent_models_mod.AgentRuntimeUpdate.model_validate(
|
|
80
|
+
payload | {"future_state": "value"}
|
|
81
|
+
)
|
|
82
|
+
assert tolerated.state == payload["state"]
|
|
83
|
+
assert not hasattr(tolerated, "future_state")
|
|
80
84
|
|
|
81
85
|
invalid_remediation = _agent_runtime_update_contract("update_required")
|
|
82
86
|
invalid_remediation["remediation"] = {"tool": "agent.update_session"}
|
|
@@ -1178,7 +1182,7 @@ def test_get_by_uid_normalizes_read_query_params(monkeypatch):
|
|
|
1178
1182
|
}
|
|
1179
1183
|
|
|
1180
1184
|
|
|
1181
|
-
def
|
|
1185
|
+
def test_patch_by_uid_ignores_unmapped_response_fields(monkeypatch):
|
|
1182
1186
|
captured = {}
|
|
1183
1187
|
|
|
1184
1188
|
class FakeResponse:
|
|
@@ -1201,14 +1205,15 @@ def test_patch_by_uid_raises_with_context_for_unmapped_response_fields(monkeypat
|
|
|
1201
1205
|
|
|
1202
1206
|
monkeypatch.setattr(base_mod, "make_request", _fake_make_request)
|
|
1203
1207
|
|
|
1204
|
-
|
|
1205
|
-
|
|
1208
|
+
instance = DemoPatchModel(id=9)
|
|
1209
|
+
patched = DemoPatchModel.patch_by_uid("uid-9", _into=instance, label="patched")
|
|
1206
1210
|
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1211
|
+
# Tolerant reading (ADR 0032): a response key the model does not declare is
|
|
1212
|
+
# dropped, exactly as parsing drops it.
|
|
1213
|
+
assert patched is instance
|
|
1214
|
+
assert patched.id == 9
|
|
1215
|
+
assert "schema" not in DemoPatchModel.model_fields
|
|
1216
|
+
assert "schema" not in patched.__dict__
|
|
1212
1217
|
assert captured == {
|
|
1213
1218
|
"r_type": "PATCH",
|
|
1214
1219
|
"url": "https://backend.test/demo-patch/uid-9/",
|
|
@@ -1449,7 +1454,7 @@ def test_shareable_can_edit_parses_permission_state(monkeypatch):
|
|
|
1449
1454
|
}
|
|
1450
1455
|
|
|
1451
1456
|
|
|
1452
|
-
def
|
|
1457
|
+
def test_shareable_access_state_drops_removed_internal_identity():
|
|
1453
1458
|
removed_field = "_".join(("object", "id"))
|
|
1454
1459
|
payload = {
|
|
1455
1460
|
"object_uid": "33333333-3333-4333-8333-333333333333",
|
|
@@ -1460,8 +1465,11 @@ def test_shareable_access_state_rejects_removed_internal_identity():
|
|
|
1460
1465
|
removed_field: 17,
|
|
1461
1466
|
}
|
|
1462
1467
|
|
|
1463
|
-
|
|
1464
|
-
|
|
1468
|
+
state = models_user_mod.ShareableAccessState.model_validate(payload)
|
|
1469
|
+
|
|
1470
|
+
# Tolerant reading (ADR 0032): the retired field is dropped, not declared.
|
|
1471
|
+
assert removed_field not in models_user_mod.ShareableAccessState.model_fields
|
|
1472
|
+
assert not hasattr(state, removed_field)
|
|
1465
1473
|
|
|
1466
1474
|
|
|
1467
1475
|
@pytest.mark.parametrize(
|
|
@@ -1974,13 +1982,22 @@ def test_agent_client_contract_matches_backend_agent_serializer():
|
|
|
1974
1982
|
{**payload, "agent_card": None},
|
|
1975
1983
|
{**payload, "agent_card": {}},
|
|
1976
1984
|
{**payload, "name": "Unrelated label"},
|
|
1977
|
-
{**payload, "agent_type": "custom"},
|
|
1978
|
-
{**payload, "metadata": {}},
|
|
1979
|
-
{**payload, "has_agent_service": False},
|
|
1980
1985
|
):
|
|
1981
1986
|
with pytest.raises(ValidationError):
|
|
1982
1987
|
agent_models_mod.Agent.model_validate(invalid_payload)
|
|
1983
1988
|
|
|
1989
|
+
# Tolerant reading (ADR 0032): retired keys are dropped, never declared.
|
|
1990
|
+
for retired_field, retired_value in (
|
|
1991
|
+
("agent_type", "custom"),
|
|
1992
|
+
("metadata", {}),
|
|
1993
|
+
("has_agent_service", False),
|
|
1994
|
+
):
|
|
1995
|
+
assert retired_field not in agent_models_mod.Agent.model_fields
|
|
1996
|
+
assert not hasattr(
|
|
1997
|
+
agent_models_mod.Agent.model_validate({**payload, retired_field: retired_value}),
|
|
1998
|
+
retired_field,
|
|
1999
|
+
)
|
|
2000
|
+
|
|
1984
2001
|
with pytest.raises(agent_models_mod.ApiError, match="harness_agent workflow"):
|
|
1985
2002
|
agent_models_mod.Agent.create(name="Research Copilot")
|
|
1986
2003
|
with pytest.raises(ValueError, match="Unsupported Agent filter"):
|
|
@@ -2206,10 +2223,13 @@ def test_agent_get_parses_runtime_update_projection(monkeypatch):
|
|
|
2206
2223
|
is None
|
|
2207
2224
|
)
|
|
2208
2225
|
|
|
2209
|
-
|
|
2226
|
+
# Tolerant reading (ADR 0032): an undeclared projection is dropped.
|
|
2227
|
+
assert not hasattr(
|
|
2210
2228
|
agent_models_mod.Agent.model_validate(
|
|
2211
|
-
{**FakeResponse.json(), "unexpected_projection": "
|
|
2212
|
-
)
|
|
2229
|
+
{**FakeResponse.json(), "unexpected_projection": "ignored"}
|
|
2230
|
+
),
|
|
2231
|
+
"unexpected_projection",
|
|
2232
|
+
)
|
|
2213
2233
|
|
|
2214
2234
|
|
|
2215
2235
|
def test_agent_semantic_search_sends_environment_scope_and_parses_code_repositoryion(monkeypatch):
|
|
@@ -2411,10 +2431,13 @@ def test_agent_session_list_and_detail_parse_runtime_capabilities(monkeypatch):
|
|
|
2411
2431
|
assert detailed.organization_environment_name == "production"
|
|
2412
2432
|
assert detailed.catalog_digest == "sha256:" + ("b" * 64)
|
|
2413
2433
|
|
|
2414
|
-
|
|
2434
|
+
# Tolerant reading (ADR 0032): an undeclared projection is dropped.
|
|
2435
|
+
assert not hasattr(
|
|
2415
2436
|
agent_models_mod.AgentSession.model_validate(
|
|
2416
|
-
{**session_payload, "unexpected_projection": "
|
|
2417
|
-
)
|
|
2437
|
+
{**session_payload, "unexpected_projection": "ignored"}
|
|
2438
|
+
),
|
|
2439
|
+
"unexpected_projection",
|
|
2440
|
+
)
|
|
2418
2441
|
|
|
2419
2442
|
|
|
2420
2443
|
def test_agent_session_environment_and_catalog_projection_contract_is_strict():
|
|
@@ -3525,7 +3548,7 @@ def test_deployment_run_billing_parses_every_backend_pricing_state(pricing_state
|
|
|
3525
3548
|
assert billing.components.image_build == billing.total_cost
|
|
3526
3549
|
|
|
3527
3550
|
|
|
3528
|
-
def
|
|
3551
|
+
def test_deployment_run_billing_contract_is_complete_and_read_tolerantly():
|
|
3529
3552
|
payload = _deployment_run_billing_payload()
|
|
3530
3553
|
for field_name in (
|
|
3531
3554
|
"scope",
|
|
@@ -3542,15 +3565,20 @@ def test_deployment_run_billing_contract_is_complete_and_closed():
|
|
|
3542
3565
|
with pytest.raises(ValidationError, match=field_name):
|
|
3543
3566
|
models_helpers_mod.DeploymentRunBilling.model_validate(incomplete)
|
|
3544
3567
|
|
|
3545
|
-
|
|
3568
|
+
# Tolerant reading (ADR 0032): a billing field or component this release does
|
|
3569
|
+
# not declare is dropped instead of failing the whole response.
|
|
3570
|
+
assert not hasattr(
|
|
3546
3571
|
models_helpers_mod.DeploymentRunBilling.model_validate(
|
|
3547
3572
|
payload | {"future_billing_field": "value"}
|
|
3548
|
-
)
|
|
3549
|
-
|
|
3550
|
-
|
|
3573
|
+
),
|
|
3574
|
+
"future_billing_field",
|
|
3575
|
+
)
|
|
3576
|
+
assert not hasattr(
|
|
3551
3577
|
models_helpers_mod.DeploymentRunBilling.model_validate(
|
|
3552
3578
|
payload | {"components": payload["components"] | {"future_component": "0.000000"}}
|
|
3553
|
-
)
|
|
3579
|
+
).components,
|
|
3580
|
+
"future_component",
|
|
3581
|
+
)
|
|
3554
3582
|
|
|
3555
3583
|
with pytest.raises(ValidationError, match="greater_than_equal"):
|
|
3556
3584
|
models_helpers_mod.DeploymentRunBilling.model_validate(payload | {"unpriced_rows": -1})
|
|
@@ -3618,7 +3646,7 @@ def test_deployment_run_cost_summary_parses_nullable_decimal(total_cost):
|
|
|
3618
3646
|
),
|
|
3619
3647
|
],
|
|
3620
3648
|
)
|
|
3621
|
-
def
|
|
3649
|
+
def test_deployment_run_additive_billing_contracts_are_complete_and_read_tolerantly(
|
|
3622
3650
|
model,
|
|
3623
3651
|
payload,
|
|
3624
3652
|
required_fields,
|
|
@@ -3629,8 +3657,11 @@ def test_deployment_run_additive_billing_contracts_are_complete_and_closed(
|
|
|
3629
3657
|
with pytest.raises(ValidationError, match=field_name):
|
|
3630
3658
|
model.model_validate(incomplete)
|
|
3631
3659
|
|
|
3632
|
-
|
|
3633
|
-
|
|
3660
|
+
# Tolerant reading (ADR 0032): an undeclared billing field is dropped.
|
|
3661
|
+
assert not hasattr(
|
|
3662
|
+
model.model_validate(payload | {"future_billing_field": "value"}),
|
|
3663
|
+
"future_billing_field",
|
|
3664
|
+
)
|
|
3634
3665
|
|
|
3635
3666
|
|
|
3636
3667
|
@pytest.mark.parametrize("field_name", ["priced_rows", "unpriced_rows"])
|
|
@@ -320,11 +320,12 @@ def test_agent_logs_support_optional_session_filter_without_environment_override
|
|
|
320
320
|
)
|
|
321
321
|
|
|
322
322
|
|
|
323
|
-
def
|
|
324
|
-
|
|
325
|
-
assert observability_models.
|
|
326
|
-
assert observability_models.
|
|
327
|
-
assert observability_models.
|
|
323
|
+
def test_owner_observability_models_read_tolerantly_and_keep_enriched_rows():
|
|
324
|
+
# Tolerant reading (ADR 0032): pages drop undeclared fields, rows keep them.
|
|
325
|
+
assert observability_models.ObservabilityLinks.model_config["extra"] == "ignore"
|
|
326
|
+
assert observability_models.OwnerLogPage.model_config["extra"] == "ignore"
|
|
327
|
+
assert observability_models.EnvironmentLogSearchPage.model_config["extra"] == "ignore"
|
|
328
|
+
assert observability_models.ResourceUsagePage.model_config["extra"] == "ignore"
|
|
328
329
|
assert observability_models.OwnerLogRow.model_config["extra"] == "allow"
|
|
329
330
|
assert observability_models.EnvironmentLogSearchRow.model_config["extra"] == "allow"
|
|
330
331
|
|
{mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_source_table_configuration.py
RENAMED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
|
-
from pydantic import ValidationError
|
|
5
4
|
|
|
6
5
|
from mainsequence.client import metatables as models_metatables
|
|
7
6
|
|
|
@@ -60,16 +59,20 @@ def test_time_indexed_profile_parses_canonical_response():
|
|
|
60
59
|
assert config.earliest_index_value == datetime.datetime(2026, 5, 1, tzinfo=datetime.UTC)
|
|
61
60
|
|
|
62
61
|
|
|
63
|
-
def
|
|
62
|
+
def test_time_indexed_profile_drops_removed_legacy_profile_uid():
|
|
64
63
|
payload = _source_config_payload()
|
|
65
64
|
removed_field = "_".join(("dynamic", "table", "uid"))
|
|
66
|
-
payload[removed_field] =
|
|
65
|
+
payload[removed_field] = "legacy-uid-44"
|
|
67
66
|
|
|
68
|
-
|
|
69
|
-
models_metatables.TimeIndexedProfile(**payload)
|
|
67
|
+
profile = models_metatables.TimeIndexedProfile(**payload)
|
|
70
68
|
|
|
69
|
+
# Tolerant reading (ADR 0032): the legacy name is dropped, never honoured.
|
|
70
|
+
assert removed_field not in models_metatables.TimeIndexedProfile.model_fields
|
|
71
|
+
assert not hasattr(profile, removed_field)
|
|
72
|
+
assert profile.time_index_meta_table_uid == "storage-uid-44"
|
|
71
73
|
|
|
72
|
-
|
|
74
|
+
|
|
75
|
+
def test_time_indexed_profile_drops_foreign_keys():
|
|
73
76
|
payload = _source_config_payload()
|
|
74
77
|
payload["foreign_keys"] = [
|
|
75
78
|
{
|
|
@@ -81,8 +84,11 @@ def test_time_indexed_profile_rejects_foreign_keys():
|
|
|
81
84
|
}
|
|
82
85
|
]
|
|
83
86
|
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
profile = models_metatables.TimeIndexedProfile(**payload)
|
|
88
|
+
|
|
89
|
+
# Tolerant reading (ADR 0032): foreign keys stay Alembic-owned and undeclared.
|
|
90
|
+
assert "foreign_keys" not in models_metatables.TimeIndexedProfile.model_fields
|
|
91
|
+
assert not hasattr(profile, "foreign_keys")
|
|
86
92
|
|
|
87
93
|
|
|
88
94
|
def test_time_index_meta_table_contract_requires_registered_profile_index_names():
|
|
@@ -354,7 +354,7 @@ def test_output_table_accepts_namespace():
|
|
|
354
354
|
"table_name",
|
|
355
355
|
],
|
|
356
356
|
)
|
|
357
|
-
def
|
|
357
|
+
def test_output_table_drops_removed_backend_fields(removed_field):
|
|
358
358
|
payload = {
|
|
359
359
|
"uid": "time-index-table-storage-1",
|
|
360
360
|
"storage_hash": "hash",
|
|
@@ -370,8 +370,11 @@ def test_output_table_rejects_removed_backend_fields(removed_field):
|
|
|
370
370
|
removed_field: "removed",
|
|
371
371
|
}
|
|
372
372
|
|
|
373
|
-
|
|
374
|
-
|
|
373
|
+
table = TimeIndexMetaTable(**payload)
|
|
374
|
+
|
|
375
|
+
# Tolerant reading (ADR 0032): a retired field is dropped, not declared.
|
|
376
|
+
assert removed_field not in TimeIndexMetaTable.model_fields
|
|
377
|
+
assert not hasattr(table, removed_field)
|
|
375
378
|
|
|
376
379
|
|
|
377
380
|
def test_update_manager_requires_output_table_constructor_argument():
|