Source code for aeat.adapters.persistence.storage.bucket._layout
"""Filesystem provisioning and path resolution for per-bucket directories.The per-bucket on-disk model lives at ``<aeat-root>/buckets/<bucket-id>/``and carries exactly three subdirectories:- ``db/`` relational state (SQLite database files).- ``blobs/`` opaque artefact storage (sealed ciphertext blobs).- ``audit/`` append-only audit-trail log files.Provisioning is fail-closed: a re-attempt against an already-provisionedbucket id raises rather than silently masking a configuration error. Thetyped :class:`BucketPaths` record carries each resolved subpath so callersnever compose the layout themselves."""from__future__importannotationsfrompathlibimportPathfrompydanticimportBaseModelfrom.....coreimportSTRICT_FROZEN_CONFIGas_STRICT_FROZENfrom.....core.identityimportBucketIdfrom.....core.pathsimportis_windows_long_path_errorfrom.._namespace_registryimport(BUCKET_AUDIT_DIRNAME,BUCKET_BLOBS_DIRNAME,BUCKET_DB_DIRNAME,BUCKETS_DIRNAME,)from._errorsimportBucketAlreadyPresentError,BucketPathTooLongError,BucketValidationError
[docs]classBucketPaths(BaseModel):"""Typed record carrying the resolved paths for one bucket directory."""model_config=_STRICT_FROZENbucket_id:BucketIdroot:Pathbucket_dir:Pathdb_dir:Pathblobs_dir:Pathaudit_dir:Path
[docs]defbucket_paths(root:Path,bucket_id:str)->BucketPaths:"""Resolve the typed paths for ``<root>/buckets/<bucket_id>/`` without IO. Args: root: AEAT root directory (the parent of ``buckets/``). bucket_id: The bucket identifier; must be non-empty. Returns: A :class:`BucketPaths` record carrying every resolved subpath. Raises: BucketValidationError: When ``bucket_id`` is empty or contains a path separator. """ifnotbucket_id:raiseBucketValidationError("bucket_id must be non-empty")if"/"inbucket_idor"\\"inbucket_id:raiseBucketValidationError("bucket_id must not contain a path separator")bucket_dir=root/BUCKETS_DIRNAME/bucket_idreturnBucketPaths(bucket_id=bucket_id,root=root,bucket_dir=bucket_dir,db_dir=bucket_dir/BUCKET_DB_DIRNAME,blobs_dir=bucket_dir/BUCKET_BLOBS_DIRNAME,audit_dir=bucket_dir/BUCKET_AUDIT_DIRNAME,)
[docs]defprovision_bucket_directory(root:Path,bucket_id:str)->BucketPaths:"""Materialise the ``<root>/buckets/<bucket_id>/{db,blobs,audit}/`` tree. Provisioning is fail-closed: if the bucket directory already exists, the function raises rather than reusing the partial state. The parent ``<root>/buckets/`` directory is created lazily. Args: root: AEAT root directory (the parent of ``buckets/``). bucket_id: The bucket identifier; must be non-empty. Returns: A :class:`BucketPaths` record carrying every resolved subpath. """paths=bucket_paths(root,bucket_id)try:paths.bucket_dir.parent.mkdir(parents=True,exist_ok=True)paths.bucket_dir.mkdir(parents=False,exist_ok=False)paths.db_dir.mkdir(parents=False,exist_ok=False)paths.blobs_dir.mkdir(parents=False,exist_ok=False)paths.audit_dir.mkdir(parents=False,exist_ok=False)exceptFileExistsErrorasexc:raiseBucketAlreadyPresentError(bucket_id=bucket_id)fromexcexceptOSErrorasexc:ifis_windows_long_path_error(exc):raiseBucketPathTooLongError(bucket_id=bucket_id,path=str(paths.bucket_dir))fromexcraisereturnpaths