mainsequence 8.1.7__tar.gz → 8.1.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. {mainsequence-8.1.7/mainsequence.egg-info → mainsequence-8.1.8}/PKG-INFO +1 -1
  2. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/client/models_helpers.py +29 -7
  3. {mainsequence-8.1.7 → mainsequence-8.1.8/mainsequence.egg-info}/PKG-INFO +1 -1
  4. {mainsequence-8.1.7 → mainsequence-8.1.8}/pyproject.toml +1 -1
  5. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_filter_normalization.py +154 -3
  6. {mainsequence-8.1.7 → mainsequence-8.1.8}/LICENSE +0 -0
  7. {mainsequence-8.1.7 → mainsequence-8.1.8}/MANIFEST.in +0 -0
  8. {mainsequence-8.1.7 → mainsequence-8.1.8}/README.md +0 -0
  9. {mainsequence-8.1.7 → mainsequence-8.1.8}/agent_scaffold/AGENTS.md +0 -0
  10. {mainsequence-8.1.7 → mainsequence-8.1.8}/agent_scaffold/skills/a2a_sdk_execution/SKILL.md +0 -0
  11. {mainsequence-8.1.7 → mainsequence-8.1.8}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
  12. {mainsequence-8.1.7 → mainsequence-8.1.8}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
  13. {mainsequence-8.1.7 → mainsequence-8.1.8}/agent_scaffold/skills/data_publishing/meta_table_migrations/SKILL.md +0 -0
  14. {mainsequence-8.1.7 → mainsequence-8.1.8}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +0 -0
  15. {mainsequence-8.1.7 → mainsequence-8.1.8}/agent_scaffold/skills/data_publishing/time_index_table_updates/SKILL.md +0 -0
  16. {mainsequence-8.1.7 → mainsequence-8.1.8}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
  17. {mainsequence-8.1.7 → mainsequence-8.1.8}/agent_scaffold/skills/maintenance/code_repository_maintenance/SKILL.md +0 -0
  18. {mainsequence-8.1.7 → mainsequence-8.1.8}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
  19. {mainsequence-8.1.7 → mainsequence-8.1.8}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
  20. {mainsequence-8.1.7 → mainsequence-8.1.8}/agent_scaffold/skills/sdk_code_repository_execution/SKILL.md +0 -0
  21. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/__init__.py +0 -0
  22. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/__main__.py +0 -0
  23. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/bootstrap.py +0 -0
  24. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/cli/__init__.py +0 -0
  25. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/cli/api.py +0 -0
  26. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/cli/browser_auth.py +0 -0
  27. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/cli/cli.py +0 -0
  28. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/cli/code_repository_status.py +0 -0
  29. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/cli/config.py +0 -0
  30. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/cli/docker_utils.py +0 -0
  31. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/cli/doctor.py +0 -0
  32. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/cli/local_ops.py +0 -0
  33. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/cli/migrations.py +0 -0
  34. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/cli/model_filters.py +0 -0
  35. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/cli/pydantic_cli.py +0 -0
  36. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/cli/sdk_utils.py +0 -0
  37. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/cli/ssh_utils.py +0 -0
  38. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/cli/ui.py +0 -0
  39. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/client/__init__.py +0 -0
  40. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/client/agent_runtime_models.py +0 -0
  41. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/client/base.py +0 -0
  42. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/client/client.py +0 -0
  43. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/client/compute_validation.py +0 -0
  44. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
  45. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
  46. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/client/data_sources_interfaces/local_paths.py +0 -0
  47. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
  48. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/client/dtype_codec.py +0 -0
  49. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/client/exceptions.py +0 -0
  50. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/client/metatables/__init__.py +0 -0
  51. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/client/metatables/core.py +0 -0
  52. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/client/models_foundry.py +0 -0
  53. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/client/models_user.py +0 -0
  54. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/client/observability.py +0 -0
  55. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/client/utils.py +0 -0
  56. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/code_repository_context.py +0 -0
  57. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/code_repository_skills.py +0 -0
  58. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/defaults.py +0 -0
  59. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/instrumentation/__init__.py +0 -0
  60. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/instrumentation/utils.py +0 -0
  61. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/logconf.py +0 -0
  62. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/meta_tables/__init__.py +0 -0
  63. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/meta_tables/__main__.py +0 -0
  64. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/meta_tables/compiled_sql/__init__.py +0 -0
  65. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/meta_tables/compiled_sql/v1.py +0 -0
  66. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/meta_tables/future_registry.py +0 -0
  67. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/meta_tables/hashing.py +0 -0
  68. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/meta_tables/migrations/__init__.py +0 -0
  69. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/meta_tables/migrations/alembic.py +0 -0
  70. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/meta_tables/migrations/env.py +0 -0
  71. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/meta_tables/migrations/provider.py +0 -0
  72. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/meta_tables/migrations/registry.py +0 -0
  73. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/meta_tables/migrations/scaffold.py +0 -0
  74. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/meta_tables/migrations/templates/__init__.py +0 -0
  75. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/meta_tables/migrations/templates/env.py.mako +0 -0
  76. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/meta_tables/migrations/templates/script.py.mako +0 -0
  77. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/meta_tables/pydantic_metadata.py +0 -0
  78. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/meta_tables/schema_names.py +0 -0
  79. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/meta_tables/sqlalchemy_contracts.py +0 -0
  80. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/meta_tables/time_index_table_refs.py +0 -0
  81. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/meta_tables/time_index_table_updates/__init__.py +0 -0
  82. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/meta_tables/time_index_table_updates/configuration.py +0 -0
  83. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/meta_tables/time_index_table_updates/dependencies.py +0 -0
  84. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/meta_tables/time_index_table_updates/managers.py +0 -0
  85. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/meta_tables/time_index_table_updates/namespacing.py +0 -0
  86. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/meta_tables/time_index_table_updates/runner.py +0 -0
  87. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/meta_tables/time_index_table_updates/updaters.py +0 -0
  88. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/meta_tables/time_index_table_updates/utils.py +0 -0
  89. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/repository_identity_security.py +0 -0
  90. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/runtime_flags.py +0 -0
  91. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence/scaffold_skills.py +0 -0
  92. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence.egg-info/SOURCES.txt +0 -0
  93. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence.egg-info/dependency_links.txt +0 -0
  94. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence.egg-info/entry_points.txt +0 -0
  95. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence.egg-info/requires.txt +0 -0
  96. {mainsequence-8.1.7 → mainsequence-8.1.8}/mainsequence.egg-info/top_level.txt +0 -0
  97. {mainsequence-8.1.7 → mainsequence-8.1.8}/setup.cfg +0 -0
  98. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/fixtures/time_index_table_update_hash_v2_golden.json +0 -0
  99. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_auth_precedence.py +0 -0
  100. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_canonical_api_route_contract.py +0 -0
  101. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_cli.py +0 -0
  102. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_cli_browser_auth.py +0 -0
  103. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_cli_migrations.py +0 -0
  104. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_client.py +0 -0
  105. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_code_repository_batch_jobs_from_file.py +0 -0
  106. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_code_repository_branch_contract.py +0 -0
  107. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_code_repository_documentation_contract.py +0 -0
  108. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_code_repository_runtime_context.py +0 -0
  109. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_code_repository_skills.py +0 -0
  110. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_dependency_extras.py +0 -0
  111. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_duckdb_interface_dimensions.py +0 -0
  112. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_environment_resource_context.py +0 -0
  113. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_environment_response_contracts.py +0 -0
  114. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_instrumentation.py +0 -0
  115. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_local_ops.py +0 -0
  116. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_logconf.py +0 -0
  117. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_meta_table_migrations.py +0 -0
  118. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_meta_tables_client_models.py +0 -0
  119. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_meta_tables_sqlalchemy_contracts.py +0 -0
  120. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_models_user_request_bound_auth.py +0 -0
  121. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_notification_uid_contract.py +0 -0
  122. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_observability_client.py +0 -0
  123. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_pod_project_resolution.py +0 -0
  124. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_scaffold_skills.py +0 -0
  125. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_schema_names.py +0 -0
  126. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_secret_client_model.py +0 -0
  127. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_source_table_configuration.py +0 -0
  128. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_sqlite_interface_dimensions.py +0 -0
  129. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_ssh_utils.py +0 -0
  130. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_time_index_table_access_dimension_audit.py +0 -0
  131. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_time_index_table_dimension_queries.py +0 -0
  132. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_time_index_table_update_configuration.py +0 -0
  133. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_time_index_table_update_configuration_client.py +0 -0
  134. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_time_index_table_update_flow.py +0 -0
  135. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_time_index_table_update_hard_cut.py +0 -0
  136. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_update_runner_uid_runtime.py +0 -0
  137. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_update_statistics.py +0 -0
  138. {mainsequence-8.1.7 → mainsequence-8.1.8}/tests/test_update_uid_guards.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 8.1.7
3
+ Version: 8.1.8
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License-Expression: LicenseRef-MainSequence-Proprietary
@@ -1403,23 +1403,43 @@ class DeploymentRunBillingComponents(BasePydanticModel):
1403
1403
  image_registry_service: Decimal | None = Field(max_digits=18, decimal_places=6)
1404
1404
 
1405
1405
 
1406
+ DeploymentRunPricingState = Literal[
1407
+ "priced",
1408
+ "partial",
1409
+ "pending",
1410
+ "unavailable",
1411
+ "failed",
1412
+ ]
1413
+
1414
+
1406
1415
  class DeploymentRunBilling(BasePydanticModel):
1407
1416
  scope: Literal["image_lifecycle"]
1408
1417
  total_cost: Decimal | None = Field(max_digits=18, decimal_places=6)
1409
1418
  currency: str = Field(min_length=1)
1410
- pricing_state: Literal[
1411
- "priced",
1412
- "partial",
1413
- "pending",
1414
- "unavailable",
1415
- "failed",
1416
- ]
1419
+ pricing_state: DeploymentRunPricingState
1417
1420
  components: DeploymentRunBillingComponents
1418
1421
  priced_rows: int = Field(ge=0)
1419
1422
  unpriced_rows: int = Field(ge=0)
1420
1423
  reused_image_count: int = Field(ge=0)
1421
1424
 
1422
1425
 
1426
+ class DeploymentRunRuntimeBilling(BasePydanticModel):
1427
+ scope: Literal["knative_runtime"]
1428
+ total_cost: Decimal | None = Field(max_digits=18, decimal_places=6)
1429
+ base_cost: Decimal | None = Field(max_digits=18, decimal_places=6)
1430
+ currency: str = Field(min_length=1)
1431
+ pricing_state: DeploymentRunPricingState
1432
+ priced_rows: int = Field(ge=0)
1433
+ unpriced_rows: int = Field(ge=0)
1434
+ is_complete: bool
1435
+
1436
+
1437
+ class DeploymentRunCostSummary(BasePydanticModel):
1438
+ total_cost: Decimal | None = Field(max_digits=18, decimal_places=6)
1439
+ currency: str = Field(min_length=1)
1440
+ is_complete: bool
1441
+
1442
+
1423
1443
  class DeploymentRunLogEntry(BaseModel):
1424
1444
  sequence: int
1425
1445
  timestamp: datetime.datetime | None = None
@@ -1497,6 +1517,8 @@ class DeploymentRun(CurrentCodeRepositoryBranchCollectionMixin, BaseObjectOrm, B
1497
1517
  logs: DeploymentRunLogReference
1498
1518
  error: DeploymentRunError | None = None
1499
1519
  billing: DeploymentRunBilling
1520
+ runtime_billing: DeploymentRunRuntimeBilling
1521
+ cost_summary: DeploymentRunCostSummary
1500
1522
 
1501
1523
  def get_logs(
1502
1524
  self,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 8.1.7
3
+ Version: 8.1.8
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License-Expression: LicenseRef-MainSequence-Proprietary
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mainsequence"
7
- version = "8.1.7"
7
+ version = "8.1.8"
8
8
  description = "Main Sequence SDK "
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -3217,6 +3217,61 @@ def _deployment_run_billing_payload(
3217
3217
  }
3218
3218
 
3219
3219
 
3220
+ def _deployment_run_runtime_billing_payload(
3221
+ *,
3222
+ pricing_state: str = "priced",
3223
+ total_cost: str | None = "0.000000",
3224
+ base_cost: str | None = "0.000000",
3225
+ is_complete: bool = True,
3226
+ ) -> dict:
3227
+ return {
3228
+ "scope": "knative_runtime",
3229
+ "total_cost": total_cost,
3230
+ "base_cost": base_cost,
3231
+ "currency": "USD",
3232
+ "pricing_state": pricing_state,
3233
+ "priced_rows": 3 if pricing_state == "priced" else 0,
3234
+ "unpriced_rows": 0 if pricing_state == "priced" else 3,
3235
+ "is_complete": is_complete,
3236
+ }
3237
+
3238
+
3239
+ def _deployment_run_cost_summary_payload(
3240
+ *,
3241
+ total_cost: str | None = "0.000000",
3242
+ is_complete: bool = True,
3243
+ ) -> dict:
3244
+ return {
3245
+ "total_cost": total_cost,
3246
+ "currency": "USD",
3247
+ "is_complete": is_complete,
3248
+ }
3249
+
3250
+
3251
+ def _deployment_run_cost_projections(
3252
+ *,
3253
+ pricing_state: str = "priced",
3254
+ total_cost: str | None = "0.000000",
3255
+ is_complete: bool = True,
3256
+ ) -> dict:
3257
+ return {
3258
+ "billing": _deployment_run_billing_payload(
3259
+ pricing_state=pricing_state,
3260
+ total_cost=total_cost,
3261
+ ),
3262
+ "runtime_billing": _deployment_run_runtime_billing_payload(
3263
+ pricing_state=pricing_state,
3264
+ total_cost=total_cost,
3265
+ base_cost=total_cost,
3266
+ is_complete=is_complete,
3267
+ ),
3268
+ "cost_summary": _deployment_run_cost_summary_payload(
3269
+ total_cost=total_cost,
3270
+ is_complete=is_complete,
3271
+ ),
3272
+ }
3273
+
3274
+
3220
3275
  @pytest.mark.parametrize(
3221
3276
  "pricing_state",
3222
3277
  ["priced", "partial", "pending", "unavailable", "failed"],
@@ -3273,6 +3328,91 @@ def test_deployment_run_billing_contract_is_complete_and_closed():
3273
3328
  )
3274
3329
 
3275
3330
 
3331
+ @pytest.mark.parametrize(
3332
+ "pricing_state",
3333
+ ["priced", "partial", "pending", "unavailable", "failed"],
3334
+ )
3335
+ def test_deployment_run_runtime_billing_parses_every_backend_pricing_state(
3336
+ pricing_state,
3337
+ ):
3338
+ total_cost = "1.250000" if pricing_state in {"priced", "partial"} else None
3339
+ base_cost = "0.750000" if total_cost is not None else None
3340
+
3341
+ billing = models_helpers_mod.DeploymentRunRuntimeBilling.model_validate(
3342
+ _deployment_run_runtime_billing_payload(
3343
+ pricing_state=pricing_state,
3344
+ total_cost=total_cost,
3345
+ base_cost=base_cost,
3346
+ is_complete=pricing_state == "priced",
3347
+ )
3348
+ )
3349
+
3350
+ assert billing.pricing_state == pricing_state
3351
+ assert billing.total_cost == (Decimal(total_cost) if total_cost is not None else None)
3352
+ assert billing.base_cost == (Decimal(base_cost) if base_cost is not None else None)
3353
+ assert billing.is_complete is (pricing_state == "priced")
3354
+
3355
+
3356
+ @pytest.mark.parametrize("total_cost", ["2.000000", None])
3357
+ def test_deployment_run_cost_summary_parses_nullable_decimal(total_cost):
3358
+ summary = models_helpers_mod.DeploymentRunCostSummary.model_validate(
3359
+ _deployment_run_cost_summary_payload(
3360
+ total_cost=total_cost,
3361
+ is_complete=total_cost is not None,
3362
+ )
3363
+ )
3364
+
3365
+ assert summary.total_cost == (Decimal(total_cost) if total_cost is not None else None)
3366
+ assert summary.is_complete is (total_cost is not None)
3367
+
3368
+
3369
+ @pytest.mark.parametrize(
3370
+ ("model", "payload", "required_fields"),
3371
+ [
3372
+ (
3373
+ models_helpers_mod.DeploymentRunRuntimeBilling,
3374
+ _deployment_run_runtime_billing_payload(),
3375
+ (
3376
+ "scope",
3377
+ "total_cost",
3378
+ "base_cost",
3379
+ "currency",
3380
+ "pricing_state",
3381
+ "priced_rows",
3382
+ "unpriced_rows",
3383
+ "is_complete",
3384
+ ),
3385
+ ),
3386
+ (
3387
+ models_helpers_mod.DeploymentRunCostSummary,
3388
+ _deployment_run_cost_summary_payload(),
3389
+ ("total_cost", "currency", "is_complete"),
3390
+ ),
3391
+ ],
3392
+ )
3393
+ def test_deployment_run_additive_billing_contracts_are_complete_and_closed(
3394
+ model,
3395
+ payload,
3396
+ required_fields,
3397
+ ):
3398
+ for field_name in required_fields:
3399
+ incomplete = dict(payload)
3400
+ incomplete.pop(field_name)
3401
+ with pytest.raises(ValidationError, match=field_name):
3402
+ model.model_validate(incomplete)
3403
+
3404
+ with pytest.raises(ValidationError, match="extra_forbidden"):
3405
+ model.model_validate(payload | {"future_billing_field": "value"})
3406
+
3407
+
3408
+ @pytest.mark.parametrize("field_name", ["priced_rows", "unpriced_rows"])
3409
+ def test_deployment_run_runtime_billing_rejects_negative_counters(field_name):
3410
+ with pytest.raises(ValidationError, match="greater_than_equal"):
3411
+ models_helpers_mod.DeploymentRunRuntimeBilling.model_validate(
3412
+ _deployment_run_runtime_billing_payload() | {field_name: -1}
3413
+ )
3414
+
3415
+
3276
3416
  def test_resource_release_deploy_current_version_posts_detail_action(monkeypatch):
3277
3417
  captured = {}
3278
3418
  release_uid = "2f4c4c3d-5669-4da5-9d86-b84633c1e6ed"
@@ -3317,7 +3457,7 @@ def test_resource_release_deploy_current_version_posts_detail_action(monkeypatch
3317
3457
  "retention_expires_at": None,
3318
3458
  },
3319
3459
  "error": None,
3320
- "billing": _deployment_run_billing_payload(),
3460
+ **_deployment_run_cost_projections(),
3321
3461
  }
3322
3462
 
3323
3463
  def _fake_make_request(*, s, loaders, r_type, url, payload, time_out=None):
@@ -3343,6 +3483,8 @@ def test_resource_release_deploy_current_version_posts_detail_action(monkeypatch
3343
3483
  assert run.logs.state == "available"
3344
3484
  assert run.error is None
3345
3485
  assert run.billing.total_cost == Decimal("0.000000")
3486
+ assert run.runtime_billing.scope == "knative_runtime"
3487
+ assert run.cost_summary.is_complete is True
3346
3488
  assert captured == {
3347
3489
  "r_type": "POST",
3348
3490
  "url": (
@@ -3386,7 +3528,7 @@ def test_deployment_run_collection_and_detail_use_unified_resource_release_contr
3386
3528
  "retention_expires_at": None,
3387
3529
  },
3388
3530
  "error": None,
3389
- "billing": _deployment_run_billing_payload(),
3531
+ **_deployment_run_cost_projections(),
3390
3532
  }
3391
3533
 
3392
3534
  collection_payload = {
@@ -3444,6 +3586,10 @@ def test_deployment_run_collection_and_detail_use_unified_resource_release_contr
3444
3586
  assert detail.error is None
3445
3587
  assert runs[0].billing.pricing_state == "priced"
3446
3588
  assert detail.billing.components.image_registry_storage == Decimal("0.000000")
3589
+ assert runs[0].runtime_billing.is_complete is True
3590
+ assert detail.runtime_billing.base_cost == Decimal("0.000000")
3591
+ assert runs[0].cost_summary.total_cost == Decimal("0.000000")
3592
+ assert detail.cost_summary.currency == "USD"
3447
3593
  assert captured[0]["payload"] == {
3448
3594
  "params": {
3449
3595
  "code_repository_branch_uid": CODE_REPOSITORY_BRANCH_UID,
@@ -3483,9 +3629,10 @@ def test_unified_deployment_run_models_and_filters(monkeypatch):
3483
3629
  "retention_expires_at": None,
3484
3630
  },
3485
3631
  "error": None,
3486
- "billing": _deployment_run_billing_payload(
3632
+ **_deployment_run_cost_projections(
3487
3633
  pricing_state="pending",
3488
3634
  total_cost=None,
3635
+ is_complete=False,
3489
3636
  ),
3490
3637
  }
3491
3638
  )
@@ -3506,6 +3653,10 @@ def test_unified_deployment_run_models_and_filters(monkeypatch):
3506
3653
  assert run.builder_runtime == "nodejs22"
3507
3654
  assert run.billing.pricing_state == "pending"
3508
3655
  assert run.billing.total_cost is None
3656
+ assert run.runtime_billing.pricing_state == "pending"
3657
+ assert run.runtime_billing.base_cost is None
3658
+ assert run.cost_summary.total_cost is None
3659
+ assert run.cost_summary.is_complete is False
3509
3660
  assert normalized == {
3510
3661
  "code_repository_branch_uid": code_repository_branch_uid,
3511
3662
  "target_type__in": ["resource_release", "static_site"],
File without changes
File without changes
File without changes
File without changes