mainsequence 6.0.28__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.28/mainsequence.egg-info → mainsequence-6.0.30}/PKG-INFO +1 -4
  2. {mainsequence-6.0.28 → mainsequence-6.0.30}/agent_scaffold/AGENTS.md +4 -3
  3. {mainsequence-6.0.28 → mainsequence-6.0.30}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +28 -14
  4. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/client/models_user.py +3 -2
  5. {mainsequence-6.0.28 → mainsequence-6.0.30/mainsequence.egg-info}/PKG-INFO +1 -4
  6. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence.egg-info/SOURCES.txt +0 -3
  7. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence.egg-info/requires.txt +0 -4
  8. {mainsequence-6.0.28 → mainsequence-6.0.30}/pyproject.toml +1 -5
  9. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_models_user_request_bound_auth.py +1 -1
  10. mainsequence-6.0.28/mainsequence/client/fastapi/__init__.py +0 -3
  11. mainsequence-6.0.28/mainsequence/client/fastapi/auth.py +0 -66
  12. mainsequence-6.0.28/tests/test_fastapi_request_identity.py +0 -108
  13. {mainsequence-6.0.28 → mainsequence-6.0.30}/LICENSE +0 -0
  14. {mainsequence-6.0.28 → mainsequence-6.0.30}/README.md +0 -0
  15. {mainsequence-6.0.28 → mainsequence-6.0.30}/agent_scaffold/skills/a2a_sdk_execution/SKILL.md +0 -0
  16. {mainsequence-6.0.28 → mainsequence-6.0.30}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
  17. {mainsequence-6.0.28 → mainsequence-6.0.30}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md +0 -0
  18. {mainsequence-6.0.28 → mainsequence-6.0.30}/agent_scaffold/skills/data_publishing/meta_table_migrations/SKILL.md +0 -0
  19. {mainsequence-6.0.28 → mainsequence-6.0.30}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +0 -0
  20. {mainsequence-6.0.28 → mainsequence-6.0.30}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
  21. {mainsequence-6.0.28 → mainsequence-6.0.30}/agent_scaffold/skills/maintenance/project-maintenance/SKILL.md +0 -0
  22. {mainsequence-6.0.28 → mainsequence-6.0.30}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
  23. {mainsequence-6.0.28 → mainsequence-6.0.30}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
  24. {mainsequence-6.0.28 → mainsequence-6.0.30}/agent_scaffold/skills/sdk_project_execution/SKILL.md +0 -0
  25. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/__init__.py +0 -0
  26. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/__main__.py +0 -0
  27. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/bootstrap.py +0 -0
  28. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/cli/__init__.py +0 -0
  29. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/cli/api.py +0 -0
  30. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/cli/browser_auth.py +0 -0
  31. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/cli/cli.py +0 -0
  32. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/cli/config.py +0 -0
  33. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/cli/docker_utils.py +0 -0
  34. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/cli/doctor.py +0 -0
  35. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/cli/local_ops.py +0 -0
  36. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/cli/migrations.py +0 -0
  37. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/cli/model_filters.py +0 -0
  38. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/cli/project_status.py +0 -0
  39. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/cli/pydantic_cli.py +0 -0
  40. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/cli/sdk_utils.py +0 -0
  41. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/cli/ssh_utils.py +0 -0
  42. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/cli/ui.py +0 -0
  43. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/client/__init__.py +0 -0
  44. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/client/agent_runtime_models.py +0 -0
  45. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/client/base.py +0 -0
  46. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/client/client.py +0 -0
  47. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/client/compute_validation.py +0 -0
  48. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
  49. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
  50. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/client/data_sources_interfaces/local_paths.py +0 -0
  51. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
  52. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/client/dtype_codec.py +0 -0
  53. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/client/exceptions.py +0 -0
  54. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/client/metatables/__init__.py +0 -0
  55. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/client/metatables/core.py +0 -0
  56. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/client/models_foundry.py +0 -0
  57. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/client/models_helpers.py +0 -0
  58. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/client/utils.py +0 -0
  59. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/defaults.py +0 -0
  60. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/instrumentation/__init__.py +0 -0
  61. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/instrumentation/utils.py +0 -0
  62. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/logconf.py +0 -0
  63. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/meta_tables/__init__.py +0 -0
  64. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/meta_tables/__main__.py +0 -0
  65. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/meta_tables/compiled_sql/__init__.py +0 -0
  66. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/meta_tables/compiled_sql/v1.py +0 -0
  67. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/meta_tables/data_nodes/__init__.py +0 -0
  68. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/meta_tables/data_nodes/build_operations.py +0 -0
  69. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/meta_tables/data_nodes/data_nodes.py +0 -0
  70. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/meta_tables/data_nodes/models.py +0 -0
  71. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/meta_tables/data_nodes/namespacing.py +0 -0
  72. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/meta_tables/data_nodes/persist_managers.py +0 -0
  73. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/meta_tables/data_nodes/run_operations.py +0 -0
  74. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/meta_tables/data_nodes/utils.py +0 -0
  75. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/meta_tables/future_registry.py +0 -0
  76. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/meta_tables/hashing.py +0 -0
  77. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/meta_tables/migrations/__init__.py +0 -0
  78. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/meta_tables/migrations/alembic.py +0 -0
  79. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/meta_tables/migrations/env.py +0 -0
  80. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/meta_tables/migrations/provider.py +0 -0
  81. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/meta_tables/migrations/registry.py +0 -0
  82. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/meta_tables/migrations/scaffold.py +0 -0
  83. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/meta_tables/migrations/templates/__init__.py +0 -0
  84. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/meta_tables/migrations/templates/env.py.mako +0 -0
  85. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/meta_tables/migrations/templates/script.py.mako +0 -0
  86. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/meta_tables/pydantic_metadata.py +0 -0
  87. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/meta_tables/schema_names.py +0 -0
  88. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/meta_tables/sqlalchemy_contracts.py +0 -0
  89. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/project_skills.py +0 -0
  90. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/runtime_context.py +0 -0
  91. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/runtime_flags.py +0 -0
  92. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence/scaffold_skills.py +0 -0
  93. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence.egg-info/dependency_links.txt +0 -0
  94. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence.egg-info/entry_points.txt +0 -0
  95. {mainsequence-6.0.28 → mainsequence-6.0.30}/mainsequence.egg-info/top_level.txt +0 -0
  96. {mainsequence-6.0.28 → mainsequence-6.0.30}/setup.cfg +0 -0
  97. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_auth_precedence.py +0 -0
  98. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_build_operations_hashing.py +0 -0
  99. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_canonical_api_route_contract.py +0 -0
  100. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_cli.py +0 -0
  101. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_cli_browser_auth.py +0 -0
  102. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_cli_migrations.py +0 -0
  103. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_client.py +0 -0
  104. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_data_access_mixin_dimension_audit.py +0 -0
  105. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_data_node_storage_dimension_queries.py +0 -0
  106. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_data_node_update_flow.py +0 -0
  107. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_dependency_extras.py +0 -0
  108. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_duckdb_interface_dimensions.py +0 -0
  109. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_filter_normalization.py +0 -0
  110. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_instrumentation.py +0 -0
  111. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_logconf.py +0 -0
  112. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_meta_table_migrations.py +0 -0
  113. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_meta_tables_client_models.py +0 -0
  114. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_meta_tables_sqlalchemy_contracts.py +0 -0
  115. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_notification_uid_contract.py +0 -0
  116. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_pod_project_resolution.py +0 -0
  117. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_project_batch_jobs_from_file.py +0 -0
  118. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_project_branch_contract.py +0 -0
  119. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_project_skills.py +0 -0
  120. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_run_configuration.py +0 -0
  121. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_scaffold_skills.py +0 -0
  122. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_schema_names.py +0 -0
  123. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_secret_client_model.py +0 -0
  124. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_source_table_configuration.py +0 -0
  125. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_sqlite_interface_dimensions.py +0 -0
  126. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_update_runner_uid_runtime.py +0 -0
  127. {mainsequence-6.0.28 → mainsequence-6.0.30}/tests/test_update_statistics.py +0 -0
  128. {mainsequence-6.0.28 → 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.28
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,25 +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`. This middleware binds request headers
97
- to the SDK request context; it is not an authorization policy by itself. The
98
- application must install it explicitly with
99
- `app.add_middleware(LoggedUserContextMiddleware)`.
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:
100
98
 
101
- The middleware exposes only the human caller's UID-based request identity:
99
+ ```python
100
+ from fastapi import FastAPI, Request
102
101
 
103
- - `request.state.user` is `RequestUserIdentity`
102
+
103
+ app = FastAPI()
104
+
105
+
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
+
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:
117
+
118
+ - `request.state.user` contains canonical `uid` and optional `username`
104
119
  - `request.state.user_uid` is the canonical public user UUID
105
120
  - `request.state.user_id` does not exist
106
121
 
107
- In deployed runtimes, identity comes from the trusted gateway's `X-User-UID`.
108
- For direct local API development, a bearer-authenticated request is validated
109
- through `/api/v1/users/me/`; this supports testing a local service through a
110
- Cloudflare tunnel before deploying it. Never inject `X-User-UID` from frontend code, and
111
- never confuse the requesting human with the release owner, workload principal,
112
- or runtime target. Use the UID for application authorization after identity has
113
- been resolved.
122
+ This identity is the human making the current HTTP request. It is not the
123
+ release creator, deployment owner, runtime workload principal, or runtime
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.
114
128
 
115
129
  ### 2. Implement the provider
116
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.28
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.28"
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