mainsequence 6.0.29__tar.gz → 6.0.30__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 (128) hide show
  1. {mainsequence-6.0.29/mainsequence.egg-info → mainsequence-6.0.30}/PKG-INFO +1 -4
  2. {mainsequence-6.0.29 → mainsequence-6.0.30}/agent_scaffold/AGENTS.md +4 -3
  3. {mainsequence-6.0.29 → mainsequence-6.0.30}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +21 -20
  4. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/client/models_user.py +3 -2
  5. {mainsequence-6.0.29 → mainsequence-6.0.30/mainsequence.egg-info}/PKG-INFO +1 -4
  6. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence.egg-info/SOURCES.txt +0 -3
  7. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence.egg-info/requires.txt +0 -4
  8. {mainsequence-6.0.29 → mainsequence-6.0.30}/pyproject.toml +1 -5
  9. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_models_user_request_bound_auth.py +1 -1
  10. mainsequence-6.0.29/mainsequence/client/fastapi/__init__.py +0 -3
  11. mainsequence-6.0.29/mainsequence/client/fastapi/auth.py +0 -66
  12. mainsequence-6.0.29/tests/test_fastapi_request_identity.py +0 -108
  13. {mainsequence-6.0.29 → mainsequence-6.0.30}/LICENSE +0 -0
  14. {mainsequence-6.0.29 → mainsequence-6.0.30}/README.md +0 -0
  15. {mainsequence-6.0.29 → mainsequence-6.0.30}/agent_scaffold/skills/a2a_sdk_execution/SKILL.md +0 -0
  16. {mainsequence-6.0.29 → mainsequence-6.0.30}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
  17. {mainsequence-6.0.29 → mainsequence-6.0.30}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md +0 -0
  18. {mainsequence-6.0.29 → mainsequence-6.0.30}/agent_scaffold/skills/data_publishing/meta_table_migrations/SKILL.md +0 -0
  19. {mainsequence-6.0.29 → mainsequence-6.0.30}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +0 -0
  20. {mainsequence-6.0.29 → mainsequence-6.0.30}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
  21. {mainsequence-6.0.29 → mainsequence-6.0.30}/agent_scaffold/skills/maintenance/project-maintenance/SKILL.md +0 -0
  22. {mainsequence-6.0.29 → mainsequence-6.0.30}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
  23. {mainsequence-6.0.29 → mainsequence-6.0.30}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
  24. {mainsequence-6.0.29 → mainsequence-6.0.30}/agent_scaffold/skills/sdk_project_execution/SKILL.md +0 -0
  25. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/__init__.py +0 -0
  26. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/__main__.py +0 -0
  27. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/bootstrap.py +0 -0
  28. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/cli/__init__.py +0 -0
  29. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/cli/api.py +0 -0
  30. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/cli/browser_auth.py +0 -0
  31. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/cli/cli.py +0 -0
  32. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/cli/config.py +0 -0
  33. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/cli/docker_utils.py +0 -0
  34. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/cli/doctor.py +0 -0
  35. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/cli/local_ops.py +0 -0
  36. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/cli/migrations.py +0 -0
  37. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/cli/model_filters.py +0 -0
  38. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/cli/project_status.py +0 -0
  39. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/cli/pydantic_cli.py +0 -0
  40. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/cli/sdk_utils.py +0 -0
  41. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/cli/ssh_utils.py +0 -0
  42. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/cli/ui.py +0 -0
  43. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/client/__init__.py +0 -0
  44. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/client/agent_runtime_models.py +0 -0
  45. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/client/base.py +0 -0
  46. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/client/client.py +0 -0
  47. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/client/compute_validation.py +0 -0
  48. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
  49. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
  50. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/client/data_sources_interfaces/local_paths.py +0 -0
  51. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
  52. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/client/dtype_codec.py +0 -0
  53. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/client/exceptions.py +0 -0
  54. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/client/metatables/__init__.py +0 -0
  55. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/client/metatables/core.py +0 -0
  56. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/client/models_foundry.py +0 -0
  57. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/client/models_helpers.py +0 -0
  58. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/client/utils.py +0 -0
  59. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/defaults.py +0 -0
  60. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/instrumentation/__init__.py +0 -0
  61. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/instrumentation/utils.py +0 -0
  62. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/logconf.py +0 -0
  63. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/meta_tables/__init__.py +0 -0
  64. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/meta_tables/__main__.py +0 -0
  65. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/meta_tables/compiled_sql/__init__.py +0 -0
  66. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/meta_tables/compiled_sql/v1.py +0 -0
  67. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/meta_tables/data_nodes/__init__.py +0 -0
  68. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/meta_tables/data_nodes/build_operations.py +0 -0
  69. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/meta_tables/data_nodes/data_nodes.py +0 -0
  70. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/meta_tables/data_nodes/models.py +0 -0
  71. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/meta_tables/data_nodes/namespacing.py +0 -0
  72. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/meta_tables/data_nodes/persist_managers.py +0 -0
  73. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/meta_tables/data_nodes/run_operations.py +0 -0
  74. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/meta_tables/data_nodes/utils.py +0 -0
  75. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/meta_tables/future_registry.py +0 -0
  76. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/meta_tables/hashing.py +0 -0
  77. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/meta_tables/migrations/__init__.py +0 -0
  78. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/meta_tables/migrations/alembic.py +0 -0
  79. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/meta_tables/migrations/env.py +0 -0
  80. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/meta_tables/migrations/provider.py +0 -0
  81. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/meta_tables/migrations/registry.py +0 -0
  82. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/meta_tables/migrations/scaffold.py +0 -0
  83. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/meta_tables/migrations/templates/__init__.py +0 -0
  84. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/meta_tables/migrations/templates/env.py.mako +0 -0
  85. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/meta_tables/migrations/templates/script.py.mako +0 -0
  86. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/meta_tables/pydantic_metadata.py +0 -0
  87. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/meta_tables/schema_names.py +0 -0
  88. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/meta_tables/sqlalchemy_contracts.py +0 -0
  89. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/project_skills.py +0 -0
  90. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/runtime_context.py +0 -0
  91. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/runtime_flags.py +0 -0
  92. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence/scaffold_skills.py +0 -0
  93. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence.egg-info/dependency_links.txt +0 -0
  94. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence.egg-info/entry_points.txt +0 -0
  95. {mainsequence-6.0.29 → mainsequence-6.0.30}/mainsequence.egg-info/top_level.txt +0 -0
  96. {mainsequence-6.0.29 → mainsequence-6.0.30}/setup.cfg +0 -0
  97. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_auth_precedence.py +0 -0
  98. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_build_operations_hashing.py +0 -0
  99. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_canonical_api_route_contract.py +0 -0
  100. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_cli.py +0 -0
  101. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_cli_browser_auth.py +0 -0
  102. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_cli_migrations.py +0 -0
  103. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_client.py +0 -0
  104. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_data_access_mixin_dimension_audit.py +0 -0
  105. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_data_node_storage_dimension_queries.py +0 -0
  106. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_data_node_update_flow.py +0 -0
  107. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_dependency_extras.py +0 -0
  108. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_duckdb_interface_dimensions.py +0 -0
  109. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_filter_normalization.py +0 -0
  110. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_instrumentation.py +0 -0
  111. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_logconf.py +0 -0
  112. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_meta_table_migrations.py +0 -0
  113. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_meta_tables_client_models.py +0 -0
  114. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_meta_tables_sqlalchemy_contracts.py +0 -0
  115. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_notification_uid_contract.py +0 -0
  116. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_pod_project_resolution.py +0 -0
  117. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_project_batch_jobs_from_file.py +0 -0
  118. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_project_branch_contract.py +0 -0
  119. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_project_skills.py +0 -0
  120. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_run_configuration.py +0 -0
  121. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_scaffold_skills.py +0 -0
  122. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_schema_names.py +0 -0
  123. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_secret_client_model.py +0 -0
  124. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_source_table_configuration.py +0 -0
  125. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_sqlite_interface_dimensions.py +0 -0
  126. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_update_runner_uid_runtime.py +0 -0
  127. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_update_statistics.py +0 -0
  128. {mainsequence-6.0.29 → mainsequence-6.0.30}/tests/test_update_uid_guards.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 6.0.29
3
+ Version: 6.0.30
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License-Expression: LicenseRef-MainSequence-Proprietary
@@ -31,13 +31,10 @@ Requires-Dist: pygments>=2.20.0
31
31
  Requires-Dist: pyyaml
32
32
  Requires-Dist: requests>=2.33.0
33
33
  Requires-Dist: sqlalchemy<3,>=2
34
- Requires-Dist: starlette
35
34
  Requires-Dist: structlog
36
35
  Requires-Dist: tqdm
37
36
  Requires-Dist: typer
38
37
  Requires-Dist: urllib3>=2.7.0
39
- Provides-Extra: fastapi
40
- Requires-Dist: fastapi<1,>=0.115; extra == "fastapi"
41
38
  Provides-Extra: local-data
42
39
  Requires-Dist: duckdb>=1.4.2; extra == "local-data"
43
40
  Requires-Dist: pyarrow>=23.0.1; extra == "local-data"
@@ -107,13 +107,14 @@ Working rules for this role:
107
107
  User-resolution rule for agents:
108
108
 
109
109
  - use `User.get_logged_user()` only when code is running with request-bound identity context:
110
- - FastAPI with `LoggedUserContextMiddleware` installed by the application
111
110
  - Streamlit
112
111
  - code that explicitly binds `_CURRENT_AUTH_HEADERS`
113
112
  - treat its result as `RequestUserIdentity`: the human caller's canonical `uid` and optional
114
113
  `username`, never a numeric id or a full account profile
115
- - in FastAPI handlers, prefer `request.state.user` or `request.state.user_uid`; never use
116
- `request.state.user_id`
114
+ - in FastAPI handlers, use the Main Sequence platform-injected
115
+ `request.state.user` or `request.state.user_uid`; do not use
116
+ `User.get_logged_user()` as the handler entry point
117
+ - never use `request.state.user_id`
117
118
  - use `User.get_authenticated_user_details()` in standalone authenticated CLI or script code that
118
119
  is not request-bound
119
120
  - keep authentication context separate from route-level authorization, deployment ownership,
@@ -92,38 +92,39 @@ Record:
92
92
  - whether the API uses backend transport or a contract-defined direct
93
93
  development transport
94
94
 
95
- Use `LoggedUserContextMiddleware` when the platform-authenticated request user
96
- must be available through `request.state`. Install it once when constructing the
97
- FastAPI application:
95
+ FastAPI releases receive the authenticated human from the Main Sequence
96
+ platform through injected request state. No SDK authentication setup is
97
+ required in project code:
98
98
 
99
99
  ```python
100
- from mainsequence.client.fastapi import LoggedUserContextMiddleware
100
+ from fastapi import FastAPI, Request
101
101
 
102
- app.add_middleware(LoggedUserContextMiddleware)
103
- ```
104
102
 
105
- After installation, route code must not detect local versus deployed execution,
106
- parse authentication headers, or resolve the request user itself. The SDK
107
- automatically handles both sources:
103
+ app = FastAPI()
104
+
108
105
 
109
- - in local development, it validates the request bearer token through
110
- `/api/v1/users/me/`;
111
- - in a deployed runtime, it reads the trusted `X-User-UID` supplied by the
112
- authenticated platform gateway.
106
+ @app.get("/me")
107
+ def get_me(request: Request) -> dict[str, str | None]:
108
+ return {
109
+ "uid": request.state.user_uid,
110
+ "username": request.state.user.username,
111
+ }
112
+ ```
113
113
 
114
- The middleware exposes only the human caller's UID-based request identity:
114
+ Route code must not detect local versus deployed execution, parse
115
+ authentication headers, or resolve the request user itself. Consume only the
116
+ platform-injected human caller identity:
115
117
 
116
- - `request.state.user` is `RequestUserIdentity`
118
+ - `request.state.user` contains canonical `uid` and optional `username`
117
119
  - `request.state.user_uid` is the canonical public user UUID
118
120
  - `request.state.user_id` does not exist
119
121
 
120
122
  This identity is the human making the current HTTP request. It is not the
121
123
  release creator, deployment owner, runtime workload principal, or runtime
122
- target. If a transport supplies both a bearer token and `X-User-UID`, the SDK
123
- validates the token and requires the UIDs to match. Application code must never
124
- manufacture `X-User-UID` as authentication evidence. The middleware establishes
125
- identity, not resource authorization; use `request.state.user_uid` when applying
126
- the endpoint's authorization policy.
124
+ target. Pass request state explicitly to shared services; do not use
125
+ `User.get_logged_user()` as a FastAPI entry point. Platform-injected identity is
126
+ not resource authorization; use `request.state.user_uid` when applying the
127
+ endpoint's authorization policy.
127
128
 
128
129
  ### 2. Implement the provider
129
130
 
@@ -1201,8 +1201,9 @@ class User(UserApiBaseObjectOrm, BasePydanticModel):
1201
1201
 
1202
1202
  if not headers:
1203
1203
  raise RequestIdentityError(
1204
- "No request identity is available. Bind request headers with "
1205
- "LoggedUserContextMiddleware or provide Streamlit request headers."
1204
+ "No request identity is available. Provide an explicitly bound "
1205
+ "request-header context or Streamlit request headers. FastAPI "
1206
+ "handlers should use request.state.user instead."
1206
1207
  )
1207
1208
 
1208
1209
  normalized_headers = _normalize_request_headers(headers)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 6.0.29
3
+ Version: 6.0.30
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License-Expression: LicenseRef-MainSequence-Proprietary
@@ -31,13 +31,10 @@ Requires-Dist: pygments>=2.20.0
31
31
  Requires-Dist: pyyaml
32
32
  Requires-Dist: requests>=2.33.0
33
33
  Requires-Dist: sqlalchemy<3,>=2
34
- Requires-Dist: starlette
35
34
  Requires-Dist: structlog
36
35
  Requires-Dist: tqdm
37
36
  Requires-Dist: typer
38
37
  Requires-Dist: urllib3>=2.7.0
39
- Provides-Extra: fastapi
40
- Requires-Dist: fastapi<1,>=0.115; extra == "fastapi"
41
38
  Provides-Extra: local-data
42
39
  Requires-Dist: duckdb>=1.4.2; extra == "local-data"
43
40
  Requires-Dist: pyarrow>=23.0.1; extra == "local-data"
@@ -58,8 +58,6 @@ mainsequence/client/data_sources_interfaces/__init__.py
58
58
  mainsequence/client/data_sources_interfaces/duckdb.py
59
59
  mainsequence/client/data_sources_interfaces/local_paths.py
60
60
  mainsequence/client/data_sources_interfaces/sqlite.py
61
- mainsequence/client/fastapi/__init__.py
62
- mainsequence/client/fastapi/auth.py
63
61
  mainsequence/client/metatables/__init__.py
64
62
  mainsequence/client/metatables/core.py
65
63
  mainsequence/instrumentation/__init__.py
@@ -102,7 +100,6 @@ tests/test_data_node_storage_dimension_queries.py
102
100
  tests/test_data_node_update_flow.py
103
101
  tests/test_dependency_extras.py
104
102
  tests/test_duckdb_interface_dimensions.py
105
- tests/test_fastapi_request_identity.py
106
103
  tests/test_filter_normalization.py
107
104
  tests/test_instrumentation.py
108
105
  tests/test_logconf.py
@@ -16,15 +16,11 @@ pygments>=2.20.0
16
16
  pyyaml
17
17
  requests>=2.33.0
18
18
  sqlalchemy<3,>=2
19
- starlette
20
19
  structlog
21
20
  tqdm
22
21
  typer
23
22
  urllib3>=2.7.0
24
23
 
25
- [fastapi]
26
- fastapi<1,>=0.115
27
-
28
24
  [local-data]
29
25
  duckdb>=1.4.2
30
26
  pyarrow>=23.0.1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mainsequence"
7
- version = "6.0.29"
7
+ version = "6.0.30"
8
8
  description = "Main Sequence SDK "
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -40,7 +40,6 @@ dependencies = [
40
40
  "pyyaml",
41
41
  "requests>=2.33.0",
42
42
  "sqlalchemy>=2,<3",
43
- "starlette",
44
43
  "structlog",
45
44
  "tqdm",
46
45
  "typer",
@@ -48,9 +47,6 @@ dependencies = [
48
47
  ]
49
48
 
50
49
  [project.optional-dependencies]
51
- "fastapi" = [
52
- "fastapi>=0.115,<1",
53
- ]
54
50
  "local-data" = [
55
51
  "duckdb>=1.4.2",
56
52
  "pyarrow>=23.0.1",
@@ -170,7 +170,7 @@ def test_get_logged_user_rejects_mismatched_bearer_and_gateway_uid(monkeypatch):
170
170
  )
171
171
 
172
172
 
173
- def test_get_logged_user_uses_identity_bound_by_middleware():
173
+ def test_get_logged_user_uses_explicitly_bound_identity_context():
174
174
  expected = models_user_mod.RequestUserIdentity(uid=USER_UID, username="jose")
175
175
  token = models_user_mod._CURRENT_USER.set(expected)
176
176
  try:
@@ -1,3 +0,0 @@
1
- from .auth import LoggedUserContextMiddleware
2
-
3
- __all__ = ["LoggedUserContextMiddleware"]
@@ -1,66 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from starlette.concurrency import run_in_threadpool
4
- from starlette.requests import Request
5
- from starlette.responses import JSONResponse
6
- from starlette.types import ASGIApp, Receive, Scope, Send
7
-
8
- from mainsequence.client.exceptions import AuthenticationError, PermissionDeniedError
9
-
10
-
11
- def _load_auth_bindings():
12
- from mainsequence.client.models_user import (
13
- _CURRENT_AUTH_HEADERS,
14
- _CURRENT_USER,
15
- RequestIdentityError,
16
- User,
17
- )
18
-
19
- return User, RequestIdentityError, _CURRENT_AUTH_HEADERS, _CURRENT_USER
20
-
21
-
22
- class LoggedUserContextMiddleware:
23
- """Resolve and bind the human making each authenticated FastAPI request."""
24
-
25
- def __init__(self, app: ASGIApp) -> None:
26
- self.app = app
27
-
28
- async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
29
- if scope["type"] != "http":
30
- await self.app(scope, receive, send)
31
- return
32
-
33
- request = Request(scope)
34
- if request.method == "OPTIONS":
35
- await self.app(scope, receive, send)
36
- return
37
-
38
- User, RequestIdentityError, current_auth_headers, current_user = (
39
- _load_auth_bindings()
40
- )
41
- headers_token = current_auth_headers.set(request.headers)
42
- user_token = current_user.set(None)
43
- resolved_user_token = None
44
- try:
45
- try:
46
- user = await run_in_threadpool(User.get_logged_user)
47
- except (RequestIdentityError, AuthenticationError, PermissionDeniedError):
48
- response = JSONResponse(
49
- {"detail": "Authenticated request user could not be resolved."},
50
- status_code=401,
51
- )
52
- await response(scope, receive, send)
53
- return
54
-
55
- resolved_user_token = current_user.set(user)
56
- request.state.user = user
57
- request.state.user_uid = user.uid
58
- await self.app(scope, receive, send)
59
- finally:
60
- if resolved_user_token is not None:
61
- current_user.reset(resolved_user_token)
62
- current_user.reset(user_token)
63
- current_auth_headers.reset(headers_token)
64
-
65
-
66
- __all__ = ["LoggedUserContextMiddleware"]
@@ -1,108 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import asyncio
4
- import json
5
-
6
- from starlette.applications import Starlette
7
- from starlette.requests import Request
8
- from starlette.responses import JSONResponse, Response
9
- from starlette.routing import Route
10
-
11
- from mainsequence.client.fastapi import LoggedUserContextMiddleware
12
- from mainsequence.client.models_user import _CURRENT_AUTH_HEADERS, _CURRENT_USER
13
-
14
- USER_UID = "8f5d6b54-2f5e-4a8b-bb10-0b17f3f4c123"
15
-
16
-
17
- def _request(app: Starlette, *, method: str = "GET", headers=None):
18
- messages: list[dict] = []
19
- scope = {
20
- "type": "http",
21
- "asgi": {"version": "3.0"},
22
- "http_version": "1.1",
23
- "method": method,
24
- "scheme": "http",
25
- "path": "/identity",
26
- "raw_path": b"/identity",
27
- "query_string": b"",
28
- "headers": [
29
- (str(key).lower().encode(), str(value).encode())
30
- for key, value in (headers or {}).items()
31
- ],
32
- "client": ("testclient", 50000),
33
- "server": ("testserver", 80),
34
- }
35
-
36
- async def receive():
37
- return {"type": "http.request", "body": b"", "more_body": False}
38
-
39
- async def send(message):
40
- messages.append(message)
41
-
42
- asyncio.run(app(scope, receive, send))
43
- status = next(message["status"] for message in messages if message["type"] == "http.response.start")
44
- body = b"".join(
45
- message.get("body", b"")
46
- for message in messages
47
- if message["type"] == "http.response.body"
48
- )
49
- return status, json.loads(body) if body else None
50
-
51
-
52
- def _build_app(route_calls: list[str]) -> Starlette:
53
- async def identity(request: Request) -> JSONResponse:
54
- route_calls.append("identity")
55
- return JSONResponse(
56
- {
57
- "uid": request.state.user.uid,
58
- "state_uid": request.state.user_uid,
59
- "has_numeric_id": hasattr(request.state, "user_id"),
60
- }
61
- )
62
-
63
- async def preflight(request: Request) -> Response:
64
- route_calls.append("preflight")
65
- return Response(status_code=204)
66
-
67
- app = Starlette(
68
- routes=[
69
- Route("/identity", identity),
70
- Route("/identity", preflight, methods=["OPTIONS"]),
71
- ]
72
- )
73
- app.add_middleware(LoggedUserContextMiddleware)
74
- return app
75
-
76
-
77
- def test_middleware_exposes_uid_only_request_state_and_resets_context():
78
- route_calls: list[str] = []
79
- status, body = _request(
80
- _build_app(route_calls),
81
- headers={"X-User-UID": USER_UID, "X-Username": "jose"},
82
- )
83
-
84
- assert status == 200
85
- assert body == {
86
- "uid": USER_UID,
87
- "state_uid": USER_UID,
88
- "has_numeric_id": False,
89
- }
90
- assert route_calls == ["identity"]
91
- assert _CURRENT_AUTH_HEADERS.get() is None
92
- assert _CURRENT_USER.get() is None
93
-
94
-
95
- def test_middleware_returns_401_without_running_route():
96
- route_calls: list[str] = []
97
- status, _ = _request(_build_app(route_calls))
98
-
99
- assert status == 401
100
- assert route_calls == []
101
-
102
-
103
- def test_middleware_allows_unauthenticated_options_preflight():
104
- route_calls: list[str] = []
105
- status, _ = _request(_build_app(route_calls), method="OPTIONS")
106
-
107
- assert status == 204
108
- assert route_calls == ["preflight"]
File without changes
File without changes
File without changes