From c0af235242c3d8e3f0a7824a863388276c22a643 Mon Sep 17 00:00:00 2001 From: Anto Subash Date: Fri, 6 Nov 2026 02:52:57 +0100 Subject: [PATCH 1/2] refactor(file_storage): follow the module layout (moves only) constants/settings/services/errors, tenant scope, formatting, health, audit labels, the tenant cache, durable tenant teardown, the object reaper and worker access -> core/; the stored-file service and its reads, deletes, visibility, queries, aggregates, downloads, ranges and thumbnails -> files/; resumable multipart uploads -> multipart/ (a name the repo's uploads/ .gitignore entry does not hide from the wheel); backend_guard -> backends/guard.py; deps, body limits, serving, CORS, CSP and endpoints -> web/. Page components, hook and TS helpers move to ui/. tasks.py stays at the root and Celery task names are unchanged. TenantStorageCleanupRequested keeps __module__ = "file_storage.tenant_deletion" so its durable event key (stored in outbox rows) does not change; handler names stay pinned. file_storage.{settings,service,deps,scope,downloads, upload_support,tenant_deletion} stay as deprecated sys.modules aliases (remove_in 0.0.46). Claude-Session: https://claude.ai/code/session_01FXHA3ZicrtZ4C5QPEgxfgq --- CHANGELOG.md | 1 + docs/module-layout.md | 2 +- docs/modules/background_tasks.md | 2 +- host/tests/test_module_layout.py | 11 +- modules/branding/branding/assets/images.py | 4 +- modules/branding/branding/assets/reaper.py | 2 +- modules/branding/branding/core/service.py | 2 +- modules/branding/branding/web/deps.py | 4 +- .../branding/branding/web/endpoints/api.py | 6 +- .../branding/branding/web/endpoints/assets.py | 6 +- .../branding/web/endpoints/tenant_api.py | 4 +- .../assets/images/test_branding_logo_dark.py | 2 +- .../reaper/test_branding_asset_lifecycle.py | 2 +- .../tests/core/module/test_branding.py | 4 +- .../test_branding_tenant_branding_assets.py | 2 +- .../endpoints/test_branding_platform_files.py | 2 +- .../endpoints/test_branding_public_assets.py | 2 +- modules/file_storage/README.md | 4 +- .../file_storage/backends/__init__.py | 2 +- .../file_storage/backends/filesystem.py | 4 +- .../backends/filesystem_multipart.py | 2 +- .../{backend_guard.py => backends/guard.py} | 0 .../file_storage/file_storage/backends/s3.py | 4 +- .../file_storage/backends/s3_put.py | 5 +- .../file_storage/contracts/__init__.py | 6 +- .../file_storage/core/__init__.py | 6 + .../file_storage/{ => core}/audit_labels.py | 0 .../file_storage/{ => core}/constants.py | 0 .../file_storage/{ => core}/errors.py | 4 +- .../file_storage/{ => core}/format.py | 0 .../file_storage/{ => core}/health.py | 0 .../file_storage/{ => core}/object_reaper.py | 3 +- .../file_storage/file_storage/core/scope.py | 72 ++++++ .../file_storage/{ => core}/services.py | 4 +- .../file_storage/core/settings.py | 187 +++++++++++++++ .../file_storage/{ => core}/tenant_cache.py | 0 .../file_storage/core/tenant_deletion.py | 155 +++++++++++++ .../file_storage/{ => core}/worker.py | 10 +- .../file_storage/{ => core}/worker_support.py | 4 +- modules/file_storage/file_storage/deps.py | 59 +---- .../file_storage/file_storage/downloads.py | 64 +----- .../file_storage/files/__init__.py | 5 + .../file_storage/{ => files}/aggregates.py | 0 .../file_storage/{ => files}/deletes.py | 8 +- .../file_storage/files/downloads.py | 63 +++++ .../file_storage/{ => files}/media_types.py | 6 +- .../file_storage/{ => files}/queries.py | 6 +- .../file_storage/{ => files}/ranges.py | 2 +- .../file_storage/{ => files}/reads.py | 11 +- .../file_storage/{ => files}/resolve.py | 2 +- .../file_storage/files/service.py | 217 ++++++++++++++++++ .../file_storage/{ => files}/thumbnails.py | 4 +- .../file_storage/{ => files}/visibility.py | 11 +- modules/file_storage/file_storage/models.py | 2 +- modules/file_storage/file_storage/module.py | 38 +-- .../file_storage/multipart/__init__.py | 4 + .../file_storage/{ => multipart}/checksum.py | 2 +- .../{upload_guards.py => multipart/guards.py} | 12 +- .../{uploads.py => multipart/sessions.py} | 11 +- .../file_storage/multipart/support.py | 78 +++++++ .../file_storage/pages/Browse.tsx | 24 +- modules/file_storage/file_storage/scope.py | 73 +----- modules/file_storage/file_storage/service.py | 217 +----------------- modules/file_storage/file_storage/settings.py | 188 +-------------- modules/file_storage/file_storage/tasks.py | 10 +- .../file_storage/tenant_deletion.py | 152 +----------- .../{pages => ui}/components/BrowseEmpty.tsx | 0 .../components/DropzoneStrip.tsx | 0 .../components/FileFilterBar.tsx | 2 +- .../{pages => ui}/components/FileTable.tsx | 6 +- .../components/SelectionFooter.tsx | 2 +- .../components/UploadDropzone.tsx | 0 .../{pages => ui}/components/UploadsCard.tsx | 2 +- .../{pages => ui/hooks}/use-file-actions.ts | 6 +- .../{pages => ui/utils}/constants.ts | 0 .../{pages => ui/utils}/format.ts | 0 .../{pages => ui/utils}/resumable-upload.ts | 0 .../{pages => ui/utils}/resume-store.ts | 0 .../file_storage/{pages => ui/utils}/types.ts | 0 .../{pages => ui/utils}/upload-errors.ts | 0 .../{pages => ui/utils}/upload-queue.ts | 0 .../file_storage/upload_support.py | 81 +------ .../file_storage/file_storage/web/__init__.py | 6 + .../file_storage/{ => web}/body_limits.py | 4 +- .../file_storage/{ => web}/cookieless.py | 2 +- .../file_storage/{ => web}/cors.py | 4 +- .../file_storage/{ => web}/csp.py | 4 +- modules/file_storage/file_storage/web/deps.py | 58 +++++ .../{ => web}/endpoints/__init__.py | 0 .../file_storage/{ => web}/endpoints/api.py | 19 +- .../{ => web}/endpoints/public.py | 12 +- .../{ => web}/endpoints/upload_errors.py | 10 +- .../{ => web}/endpoints/uploads.py | 36 +-- .../file_storage/{ => web}/endpoints/views.py | 10 +- .../file_storage/{ => web}/serving.py | 13 +- .../file_storage/tests-js/FileTable.test.tsx | 4 +- .../tests-js/SelectionFooter.test.tsx | 2 +- ..._storage_upload_queue_cancellation.test.ts | 6 +- ...ile_storage_upload_queue_lifecycle.test.ts | 6 +- .../file_storage_useFileActions.test.tsx | 4 +- modules/file_storage/tests-js/format.test.ts | 2 +- .../tests-js/resumable-queue.test.ts | 6 +- .../tests-js/resumable-upload.test.ts | 6 +- .../tests-js/upload-queue.test.ts | 2 +- ...rs.py => _file_storage_uploads_support.py} | 2 +- ...ers.py => _file_storage_worker_support.py} | 0 .../test_file_storage_filesystem_backend.py} | 2 +- ..._file_storage_filesystem_stale_objects.py} | 2 +- .../test_file_storage_upload_io.py | 0 .../test_file_storage_backend_mismatch.py | 11 +- .../test_file_storage_backend_registry.py} | 6 +- .../s3/test_file_storage_s3_backend.py} | 10 +- modules/file_storage/tests/conftest.py | 8 +- .../format/test_file_storage_format.py} | 2 +- .../test_file_storage_menu_permission.py | 2 +- .../test_file_storage_optional_ordering.py | 0 ...t_file_storage_without_background_tasks.py | 2 +- .../test_file_storage_quota_setting.py | 4 +- .../test_file_storage_restart_settings.py | 4 +- .../test_file_storage_platform_scope.py} | 4 +- .../tenancy}/test_file_storage_tenancy.py | 4 +- .../test_file_storage_tenancy_unbound.py | 6 +- .../test_file_storage_tenant_migration.py | 2 +- .../test_file_storage_cleanup_chunks.py | 6 +- .../test_file_storage_tenant_deletion.py | 26 ++- .../test_file_storage_object_reaper.py | 2 +- .../worker}/test_file_storage_upload_tasks.py | 7 +- .../worker}/test_file_storage_worker.py | 12 +- .../test_file_storage_worker_failures.py | 4 +- .../browse/test_file_storage_aggregates.py} | 4 +- .../browse}/test_file_storage_browse_index.py | 4 +- .../browse/test_file_storage_browse_props.py} | 2 +- .../test_file_storage_browse_query_shape.py} | 2 +- .../browse}/test_file_storage_filters.py | 6 +- .../test_file_storage_uploader_directory.py | 2 +- .../test_file_storage_uploader_filter.py} | 6 +- .../deletes/test_file_storage_bulk_delete.py} | 6 +- ...t_file_storage_delete_permission_modes.py} | 2 +- .../media}/test_file_storage_media_types.py | 4 +- .../media}/test_file_storage_range_parse.py | 4 +- .../test_file_storage_thumbnail_hardening.py | 3 +- ..._file_storage_thumbnail_session_release.py | 5 +- .../media}/test_file_storage_thumbnails.py | 3 +- .../test_file_storage_commit_cleanup.py | 2 +- .../test_file_storage_delete_variants.py | 7 +- .../service}/test_file_storage_owner_scope.py | 4 +- .../service/test_file_storage_service.py} | 8 +- .../service}/test_file_storage_timestamps.py | 2 +- ...st_file_storage_checksum_reconciliation.py | 3 +- .../test_file_storage_p3_scope_usage.py | 4 +- .../guards}/test_file_storage_plain_quota.py | 10 +- .../guards}/test_file_storage_server_put.py | 6 +- ...test_file_storage_upload_failure_modes.py} | 8 +- .../test_file_storage_upload_guards.py | 8 +- .../test_file_storage_busy_admission.py | 5 +- ...test_file_storage_sqlite_admission_race.py | 8 +- .../test_file_storage_uploads_expiry.py | 4 +- .../sessions}/test_file_storage_uploads_fs.py | 14 +- .../test_file_storage_uploads_minio.py | 6 +- .../sessions}/test_file_storage_uploads_pg.py | 4 +- .../test_file_storage_uploads_quota.py | 4 +- .../sessions}/test_file_storage_uploads_s3.py | 8 +- .../api/test_file_storage_api.py} | 2 +- .../api}/test_file_storage_deps_backend.py | 4 +- ...le_storage_files_api_pagination_bounds.py} | 2 +- .../api}/test_file_storage_page_clamp.py | 2 +- ...test_file_storage_deleted_tenant_public.py | 12 +- .../test_file_storage_p3_download_csp.py | 4 +- .../public}/test_file_storage_public.py | 2 +- .../test_file_storage_public_cookieless.py | 2 +- .../public}/test_file_storage_public_cors.py | 2 +- .../public}/test_file_storage_public_rate.py | 2 +- .../test_file_storage_range_download.py | 2 +- .../test_file_storage_single_tenant_public.py | 2 +- .../fields/test_settings_field_sources.py | 4 +- ...tings_lenient_related_fields_validators.py | 2 +- .../test_settings_module_api_json_fields.py | 2 +- .../test_tenants_teardown_integration.py | 2 +- 178 files changed, 1295 insertions(+), 1163 deletions(-) rename modules/file_storage/file_storage/{backend_guard.py => backends/guard.py} (100%) create mode 100644 modules/file_storage/file_storage/core/__init__.py rename modules/file_storage/file_storage/{ => core}/audit_labels.py (100%) rename modules/file_storage/file_storage/{ => core}/constants.py (100%) rename modules/file_storage/file_storage/{ => core}/errors.py (88%) rename modules/file_storage/file_storage/{ => core}/format.py (100%) rename modules/file_storage/file_storage/{ => core}/health.py (100%) rename modules/file_storage/file_storage/{ => core}/object_reaper.py (97%) create mode 100644 modules/file_storage/file_storage/core/scope.py rename modules/file_storage/file_storage/{ => core}/services.py (90%) create mode 100644 modules/file_storage/file_storage/core/settings.py rename modules/file_storage/file_storage/{ => core}/tenant_cache.py (100%) create mode 100644 modules/file_storage/file_storage/core/tenant_deletion.py rename modules/file_storage/file_storage/{ => core}/worker.py (95%) rename modules/file_storage/file_storage/{ => core}/worker_support.py (81%) create mode 100644 modules/file_storage/file_storage/files/__init__.py rename modules/file_storage/file_storage/{ => files}/aggregates.py (100%) rename modules/file_storage/file_storage/{ => files}/deletes.py (95%) create mode 100644 modules/file_storage/file_storage/files/downloads.py rename modules/file_storage/file_storage/{ => files}/media_types.py (92%) rename modules/file_storage/file_storage/{ => files}/queries.py (97%) rename modules/file_storage/file_storage/{ => files}/ranges.py (98%) rename modules/file_storage/file_storage/{ => files}/reads.py (89%) rename modules/file_storage/file_storage/{ => files}/resolve.py (96%) create mode 100644 modules/file_storage/file_storage/files/service.py rename modules/file_storage/file_storage/{ => files}/thumbnails.py (98%) rename modules/file_storage/file_storage/{ => files}/visibility.py (92%) create mode 100644 modules/file_storage/file_storage/multipart/__init__.py rename modules/file_storage/file_storage/{ => multipart}/checksum.py (98%) rename modules/file_storage/file_storage/{upload_guards.py => multipart/guards.py} (93%) rename modules/file_storage/file_storage/{uploads.py => multipart/sessions.py} (97%) create mode 100644 modules/file_storage/file_storage/multipart/support.py rename modules/file_storage/file_storage/{pages => ui}/components/BrowseEmpty.tsx (100%) rename modules/file_storage/file_storage/{pages => ui}/components/DropzoneStrip.tsx (100%) rename modules/file_storage/file_storage/{pages => ui}/components/FileFilterBar.tsx (99%) rename modules/file_storage/file_storage/{pages => ui}/components/FileTable.tsx (97%) rename modules/file_storage/file_storage/{pages => ui}/components/SelectionFooter.tsx (97%) rename modules/file_storage/file_storage/{pages => ui}/components/UploadDropzone.tsx (100%) rename modules/file_storage/file_storage/{pages => ui}/components/UploadsCard.tsx (98%) rename modules/file_storage/file_storage/{pages => ui/hooks}/use-file-actions.ts (95%) rename modules/file_storage/file_storage/{pages => ui/utils}/constants.ts (100%) rename modules/file_storage/file_storage/{pages => ui/utils}/format.ts (100%) rename modules/file_storage/file_storage/{pages => ui/utils}/resumable-upload.ts (100%) rename modules/file_storage/file_storage/{pages => ui/utils}/resume-store.ts (100%) rename modules/file_storage/file_storage/{pages => ui/utils}/types.ts (100%) rename modules/file_storage/file_storage/{pages => ui/utils}/upload-errors.ts (100%) rename modules/file_storage/file_storage/{pages => ui/utils}/upload-queue.ts (100%) create mode 100644 modules/file_storage/file_storage/web/__init__.py rename modules/file_storage/file_storage/{ => web}/body_limits.py (92%) rename modules/file_storage/file_storage/{ => web}/cookieless.py (98%) rename modules/file_storage/file_storage/{ => web}/cors.py (93%) rename modules/file_storage/file_storage/{ => web}/csp.py (92%) create mode 100644 modules/file_storage/file_storage/web/deps.py rename modules/file_storage/file_storage/{ => web}/endpoints/__init__.py (100%) rename modules/file_storage/file_storage/{ => web}/endpoints/api.py (93%) rename modules/file_storage/file_storage/{ => web}/endpoints/public.py (89%) rename modules/file_storage/file_storage/{ => web}/endpoints/upload_errors.py (90%) rename modules/file_storage/file_storage/{ => web}/endpoints/uploads.py (94%) rename modules/file_storage/file_storage/{ => web}/endpoints/views.py (95%) rename modules/file_storage/file_storage/{ => web}/serving.py (92%) rename modules/file_storage/tests/{file_storage_uploads_helpers.py => _file_storage_uploads_support.py} (97%) rename modules/file_storage/tests/{file_storage_worker_helpers.py => _file_storage_worker_support.py} (100%) rename modules/file_storage/tests/{test_filesystem_backend.py => backends/filesystem/test_file_storage_filesystem_backend.py} (98%) rename modules/file_storage/tests/{test_filesystem_stale_objects.py => backends/filesystem/test_file_storage_filesystem_stale_objects.py} (97%) rename modules/file_storage/tests/{ => backends/filesystem}/test_file_storage_upload_io.py (100%) rename modules/file_storage/tests/{ => backends/registry}/test_file_storage_backend_mismatch.py (91%) rename modules/file_storage/tests/{test_backend_registry.py => backends/registry/test_file_storage_backend_registry.py} (95%) rename modules/file_storage/tests/{test_s3_backend.py => backends/s3/test_file_storage_s3_backend.py} (96%) rename modules/file_storage/tests/{test_format.py => core/format/test_file_storage_format.py} (95%) rename modules/file_storage/tests/{ => core/module}/test_file_storage_menu_permission.py (90%) rename modules/file_storage/tests/{ => core/module}/test_file_storage_optional_ordering.py (100%) rename modules/file_storage/tests/{ => core/module}/test_file_storage_without_background_tasks.py (95%) rename modules/file_storage/tests/{ => core/settings}/test_file_storage_quota_setting.py (91%) rename modules/file_storage/tests/{ => core/settings}/test_file_storage_restart_settings.py (95%) rename modules/file_storage/tests/{test_platform_scope.py => core/tenancy/test_file_storage_platform_scope.py} (97%) rename modules/file_storage/tests/{ => core/tenancy}/test_file_storage_tenancy.py (98%) rename modules/file_storage/tests/{ => core/tenancy}/test_file_storage_tenancy_unbound.py (96%) rename modules/file_storage/tests/{ => core/tenancy}/test_file_storage_tenant_migration.py (98%) rename modules/file_storage/tests/{ => core/tenant_deletion}/test_file_storage_cleanup_chunks.py (91%) rename modules/file_storage/tests/{ => core/tenant_deletion}/test_file_storage_tenant_deletion.py (90%) rename modules/file_storage/tests/{ => core/worker}/test_file_storage_object_reaper.py (96%) rename modules/file_storage/tests/{ => core/worker}/test_file_storage_upload_tasks.py (97%) rename modules/file_storage/tests/{ => core/worker}/test_file_storage_worker.py (96%) rename modules/file_storage/tests/{ => core/worker}/test_file_storage_worker_failures.py (98%) rename modules/file_storage/tests/{test_aggregates.py => files/browse/test_file_storage_aggregates.py} (98%) rename modules/file_storage/tests/{ => files/browse}/test_file_storage_browse_index.py (96%) rename modules/file_storage/tests/{test_browse_props.py => files/browse/test_file_storage_browse_props.py} (99%) rename modules/file_storage/tests/{test_browse_query_shape.py => files/browse/test_file_storage_browse_query_shape.py} (99%) rename modules/file_storage/tests/{ => files/browse}/test_file_storage_filters.py (96%) rename modules/file_storage/tests/{ => files/browse}/test_file_storage_uploader_directory.py (92%) rename modules/file_storage/tests/{test_uploader_filter.py => files/browse/test_file_storage_uploader_filter.py} (96%) rename modules/file_storage/tests/{test_bulk_delete.py => files/deletes/test_file_storage_bulk_delete.py} (97%) rename modules/file_storage/tests/{test_delete_permission_modes.py => files/deletes/test_file_storage_delete_permission_modes.py} (93%) rename modules/file_storage/tests/{ => files/media}/test_file_storage_media_types.py (95%) rename modules/file_storage/tests/{ => files/media}/test_file_storage_range_parse.py (94%) rename modules/file_storage/tests/{ => files/media}/test_file_storage_thumbnail_hardening.py (98%) rename modules/file_storage/tests/{ => files/media}/test_file_storage_thumbnail_session_release.py (94%) rename modules/file_storage/tests/{ => files/media}/test_file_storage_thumbnails.py (98%) rename modules/file_storage/tests/{ => files/service}/test_file_storage_commit_cleanup.py (97%) rename modules/file_storage/tests/{ => files/service}/test_file_storage_delete_variants.py (90%) rename modules/file_storage/tests/{ => files/service}/test_file_storage_owner_scope.py (98%) rename modules/file_storage/tests/{test_service.py => files/service/test_file_storage_service.py} (98%) rename modules/file_storage/tests/{ => files/service}/test_file_storage_timestamps.py (96%) rename modules/file_storage/tests/{ => multipart/guards}/test_file_storage_checksum_reconciliation.py (98%) rename modules/file_storage/tests/{ => multipart/guards}/test_file_storage_p3_scope_usage.py (94%) rename modules/file_storage/tests/{ => multipart/guards}/test_file_storage_plain_quota.py (88%) rename modules/file_storage/tests/{ => multipart/guards}/test_file_storage_server_put.py (97%) rename modules/file_storage/tests/{test_upload_failure_modes.py => multipart/guards/test_file_storage_upload_failure_modes.py} (95%) rename modules/file_storage/tests/{ => multipart/guards}/test_file_storage_upload_guards.py (97%) rename modules/file_storage/tests/{ => multipart/sessions}/test_file_storage_busy_admission.py (85%) rename modules/file_storage/tests/{ => multipart/sessions}/test_file_storage_sqlite_admission_race.py (93%) rename modules/file_storage/tests/{ => multipart/sessions}/test_file_storage_uploads_expiry.py (95%) rename modules/file_storage/tests/{ => multipart/sessions}/test_file_storage_uploads_fs.py (98%) rename modules/file_storage/tests/{ => multipart/sessions}/test_file_storage_uploads_minio.py (94%) rename modules/file_storage/tests/{ => multipart/sessions}/test_file_storage_uploads_pg.py (95%) rename modules/file_storage/tests/{ => multipart/sessions}/test_file_storage_uploads_quota.py (97%) rename modules/file_storage/tests/{ => multipart/sessions}/test_file_storage_uploads_s3.py (97%) rename modules/file_storage/tests/{test_api.py => web/api/test_file_storage_api.py} (99%) rename modules/file_storage/tests/{ => web/api}/test_file_storage_deps_backend.py (73%) rename modules/file_storage/tests/{test_files_api_pagination_bounds.py => web/api/test_file_storage_files_api_pagination_bounds.py} (94%) rename modules/file_storage/tests/{ => web/api}/test_file_storage_page_clamp.py (98%) rename modules/file_storage/tests/{ => web/public}/test_file_storage_deleted_tenant_public.py (89%) rename modules/file_storage/tests/{ => web/public}/test_file_storage_p3_download_csp.py (92%) rename modules/file_storage/tests/{ => web/public}/test_file_storage_public.py (99%) rename modules/file_storage/tests/{ => web/public}/test_file_storage_public_cookieless.py (98%) rename modules/file_storage/tests/{ => web/public}/test_file_storage_public_cors.py (97%) rename modules/file_storage/tests/{ => web/public}/test_file_storage_public_rate.py (91%) rename modules/file_storage/tests/{ => web/public}/test_file_storage_range_download.py (99%) rename modules/file_storage/tests/{ => web/public}/test_file_storage_single_tenant_public.py (96%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ac3e6c3d..451c1517c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -96,6 +96,7 @@ All notable changes to this project are documented in this file. The format is b - `audit_log` follows the module layout: `audit_log.core.{constants,settings,services,capture}`, `audit_log.entries.{service,filters,resolve,activity,export}`, `audit_log.web.{deps,endpoints}`, and the page components move from `pages/components/` to `ui/components/`. The `simple_module.audit_sinks` entry point now targets `audit_log.core.capture:audit_callback`. Nothing outside the module imported the old paths, so no aliases remain. - `auth` follows the module layout: `auth.core.{constants,state}` and `auth.web.{deps,middleware}`; `auth.oidc_common` stays as a declared domain area. `auth.constants`, `auth.state`, `auth.deps` and `auth.middleware` still import, with a `DeprecationWarning`, until 0.0.46. - `tenants` follows the module layout: `tenants.core.{constants,settings,services,errors,service,user_cleanup}` (with the membership, owner-guard and deletion helpers), `tenants.resolution.{resolver,host_resolver,token_tenant}`, `tenants.invitations.{service,mail}`, `tenants.web.{deps,shared_props,endpoints}`, and the page components and hooks move to `ui/components` and `ui/hooks`. The invitation mail template moves to `invitations/templates/invitation.txt`. `tenants.constants`, `tenants.errors`, `tenants.host_resolver`, `tenants.resolver`, `tenants.service` and `tenants.deps` still import, with a `DeprecationWarning`, until 0.0.46. The durable handler names `tenants.user_cleanup.purge_user_memberships` and `tenants.send_invitation_email` are unchanged. +- `file_storage` follows the module layout: `file_storage.core.{constants,settings,services,errors,scope,format,health,audit_labels,tenant_cache,tenant_deletion,object_reaper,worker,worker_support}`, `file_storage.files.*`, `file_storage.multipart.{sessions,guards,support,checksum}`, `file_storage.backends.guard`, `file_storage.web.{deps,serving,cors,csp,cookieless,body_limits,endpoints}`, and the page components, hook and helpers move to `ui/`. `file_storage.settings`, `.service`, `.deps`, `.scope`, `.downloads`, `.upload_support` and `.tenant_deletion` still import, with a `DeprecationWarning`, until 0.0.46. The durable `TenantStorageCleanupRequested` event key and its handler names, and the Celery task names, are unchanged. - Coalesce concurrent tenant membership and permission grant cache misses. In-flight invalidation retries against fresh authorization data; failures and cancellation release waiting readers. Cache unknown tenant subdomains for five seconds and drop the new slug's host lookups (on a new `tenants.hosts` invalidation channel, so no user id is read as a slug) and the owner's memberships after committed tenant creation; other users' caches are not cleared. Services built outside request dependencies pass `invalidate_hosts=make_host_invalidator(app)` beside `invalidate=make_invalidator(app)`. Retries after concurrent invalidation are bounded to three reads (2026-11-05 local performance review). diff --git a/docs/module-layout.md b/docs/module-layout.md index e06c219b4..6084d5658 100644 --- a/docs/module-layout.md +++ b/docs/module-layout.md @@ -219,7 +219,7 @@ when a later module PR moves files. | branding | 2 (+6 shims) | 0 | follows this layout | | dashboard | 2 | 0 | follows this layout | | feature_flags | 3 (+1 shim) | 0 | follows this layout | -| file_storage | 39 | 65 | flat | +| file_storage | 4 (+7 shims) | 0 | follows this layout | | keycloak | 3 (+5 shims) | 0 | follows this layout | | mail | 2 (+2 shims) | 0 | follows this layout | | oidc | 3 (+3 shims) | 0 | follows this layout | diff --git a/docs/modules/background_tasks.md b/docs/modules/background_tasks.md index bb489ba57..23a933f5a 100644 --- a/docs/modules/background_tasks.md +++ b/docs/modules/background_tasks.md @@ -678,7 +678,7 @@ Workers never run the hosting lifespan, so `FileStorageSettings()` (or any `DbBa ```python from background_tasks import settings_for -from file_storage.settings import FileStorageSettings +from file_storage.core.settings import FileStorageSettings @celery.task diff --git a/host/tests/test_module_layout.py b/host/tests/test_module_layout.py index a3627a7dc..7cdd36b9e 100644 --- a/host/tests/test_module_layout.py +++ b/host/tests/test_module_layout.py @@ -37,6 +37,7 @@ "audit_log": frozenset({"entries"}), "auth": frozenset({"oidc_common"}), "branding": frozenset({"assets"}), + "file_storage": frozenset({"backends", "files", "multipart"}), "keycloak": frozenset({"identity"}), "mail": frozenset({"delivery"}), "oidc": frozenset({"identity"}), @@ -68,16 +69,6 @@ workers_state.py """.split() ), - "file_storage": frozenset( - """ - aggregates.py audit_labels.py backend_guard.py backends body_limits.py checksum.py - constants.py cookieless.py cors.py csp.py deletes.py deps.py downloads.py endpoints - errors.py format.py health.py media_types.py object_reaper.py queries.py ranges.py - reads.py resolve.py scope.py service.py services.py serving.py settings.py - tenant_cache.py tenant_deletion.py thumbnails.py upload_guards.py upload_support.py - uploads.py visibility.py worker.py worker_support.py - """.split() - ), "users": frozenset( """ admin administrators.py audit.py auth_local backend.py bootstrap.py constants.py diff --git a/modules/branding/branding/assets/images.py b/modules/branding/branding/assets/images.py index eb1323631..0c9c39aa8 100644 --- a/modules/branding/branding/assets/images.py +++ b/modules/branding/branding/assets/images.py @@ -18,7 +18,7 @@ if TYPE_CHECKING: from file_storage.contracts.schemas import StoredFileOut - from file_storage.service import FileStorageService + from file_storage.files.service import FileStorageService # Raster + icon only. SVG is excluded on purpose: it is an XML document that can # carry