From 7e970f93ee8b3a8e2fe9e5e034bc3b37b9b5edec Mon Sep 17 00:00:00 2001 From: "Aliaksei Yaletski (Tiendil)" Date: Mon, 30 Mar 2026 18:49:15 +0200 Subject: [PATCH] Removed world artifact mutation support --- changes/unreleased.md | 14 ++ donna/artifacts/usage/cli.md | 10 +- donna/artifacts/usage/worlds.md | 9 +- donna/cli/commands/artifacts.py | 120 +------------- donna/cli/types.py | 27 ---- donna/context/artifacts.py | 42 ----- donna/workspaces/artifacts.py | 216 +------------------------- donna/workspaces/config.py | 5 - donna/workspaces/errors.py | 8 - donna/workspaces/worlds/base.py | 9 -- donna/workspaces/worlds/filesystem.py | 40 ----- donna/workspaces/worlds/python.py | 8 - 12 files changed, 27 insertions(+), 481 deletions(-) diff --git a/changes/unreleased.md b/changes/unreleased.md index abd0ae0..87175dd 100644 --- a/changes/unreleased.md +++ b/changes/unreleased.md @@ -1,2 +1,16 @@ +### Migration + +- Update your scripts and specs to use external tools or direct file edits to create, update, move, copy, or delete world artifacts instead using removed Donna commands. + +### Changes - `--tag` option is replaced with `--predicate` in all CLI commands that accept artifact patterns. +- Removed world artifact mutation support. + - Removed `donna artifacts` mutation commands and the supporting artifact-mutation code paths. + - Removed `readonly` world-artifact mutability modeling from workspace config and world abstractions. + - Updated artifact and world usage specs to state that developers and external tools mutate world artifacts while Donna validates them. + +### Breaking Changes + +- `donna artifacts` no longer supports `update`, `copy`, `move`, or `remove`. +- Donna no longer mutates world artifacts through workspace APIs or world configuration. diff --git a/donna/artifacts/usage/cli.md b/donna/artifacts/usage/cli.md index 6070e0a..c4a1832 100644 --- a/donna/artifacts/usage/cli.md +++ b/donna/artifacts/usage/cli.md @@ -75,7 +75,7 @@ There are four sets of commands: - `donna -p workspaces …` — manages workspaces. Most-likely it will be used once per your project to initialize it. - `donna -p sessions …` — manages sessions. You will use these commands to start, push forward, and manage your work. -- `donna -p artifacts …` — manages artifacts. You will use these commands to read and update artifacts you are working with. +- `donna -p artifacts …` — manages artifact discovery, reading, fetching, temporary files, and validation. - `donna -p journal …` — manages session actions journal. You will use these commands to log and inspect the history of actions performed during the session. Use: @@ -146,13 +146,11 @@ Use the next commands to work with artifacts: - `donna -p artifacts view ` — get the meaningful (rendered) content of all matching artifacts. This command shows the rendered information about each artifact. Use this command when you need to read artifact content. - `donna -p artifacts fetch :` — download the original source of the artifact content, outputs the file path to the artifact's copy, you can change. Use this command when you need to change the content of the artifact. - `donna -p artifacts tmp .` — create a temporary file for artifact-related work and output its path. -- `donna -p artifacts update : [--extension ]` — upload content from a file path or from stdin (`-`) as the artifact. Donna gets an extension from three sources: `--extension`, ``, and a stored artifact; if there are multiple extensions or no extensions at all, Donna returns an error. -- `donna -p artifacts copy ` — copy an artifact source to another artifact ID (can be in a different world). This overwrites the destination if it exists. -- `donna -p artifacts move ` — copy an artifact source to another artifact ID and remove the original. This overwrites the destination if it exists. -- `donna -p artifacts remove ` — remove artifacts matching a pattern. Use this command when you need to delete artifacts. - `donna -p artifacts validate []` — validate all artifacts corresponding to the given pattern. If `` is omitted, validate all artifacts in all worlds. -Commands that accept an artifact pattern (`artifacts list`, `artifacts view`, `artifacts remove`, `artifacts validate`) also accept `--predicate/-p ` to filter by artifact primary section. The expression is evaluated as `bool` with `section` global available (for example: `--predicate '"workflow" in section.tags'`). +Donna does not mutate artifacts stored in worlds. Developers and external tools are responsible for creating, updating, moving, copying, or deleting world artifacts before Donna reads or validates them. + +Commands that accept an artifact pattern (`artifacts list`, `artifacts view`, `artifacts validate`) also accept `--predicate/-p ` to filter by artifact primary section. The expression is evaluated as `bool` with `section` global available (for example: `--predicate '"workflow" in section.tags'`). The format of `` is as follows: diff --git a/donna/artifacts/usage/worlds.md b/donna/artifacts/usage/worlds.md index 211b3f9..f027804 100644 --- a/donna/artifacts/usage/worlds.md +++ b/donna/artifacts/usage/worlds.md @@ -28,12 +28,13 @@ Default worlds and there locations are: All worlds have a free layout, defined by developers who own the particular world. -## Write Access +## Artifact Access -By default, worlds are read-only. Besides the next exceptions: +Donna has read access to artifacts stored in worlds. It discovers, fetches, renders, and validates world artifacts, but it does not create, update, move, copy, or delete them. -- `session` in the project world is read-write, Donna stores its current state of work here. -- `project` is read-write when the developer explicitly asks Donna to change it. For example, to add the result of performed work into docs. +Developers and external tools are responsible for mutating world artifacts before Donna reads or validates them. + +Donna still writes its own session state and journal data in the `session` world, but that internal state storage is separate from world-artifact mutation. ## `:intro` artifact diff --git a/donna/cli/commands/artifacts.py b/donna/cli/commands/artifacts.py index 9ce0a54..f374648 100644 --- a/donna/cli/commands/artifacts.py +++ b/donna/cli/commands/artifacts.py @@ -1,6 +1,3 @@ -import builtins -import pathlib -import sys from collections.abc import Iterable import typer @@ -8,10 +5,8 @@ from donna.cli import errors as cli_errors from donna.cli.application import app from donna.cli.types import ( - ExtensionOption, FullArtifactIdArgument, FullArtifactIdPatternArgument, - InputPathArgument, OutputPathOption, PredicateOption, ) @@ -47,31 +42,6 @@ def _log_artifact_operation(message: str) -> None: machine_journal.add(message=message) -def _resolve_update_extension( - input_path: pathlib.Path | None, - extension: str | None, -) -> str | None: - normalized_extension = extension.lstrip(".").lower() if extension is not None else None - if input_path is None: - return normalized_extension - - inferred_extension = input_path.suffix.lstrip(".").lower() or None - if ( - normalized_extension is not None - and inferred_extension is not None - and normalized_extension != inferred_extension - ): - raise typer.BadParameter( - ( - f"Option extension '{normalized_extension}' does not match input file extension " - f"'{inferred_extension}'" - ), - param_hint="--extension", - ) - - return normalized_extension if normalized_extension is not None else inferred_extension - - def _log_operation_on_artifacts( message: str, pattern: FullArtifactIdPattern, @@ -131,9 +101,7 @@ def fetch(id: FullArtifactIdArgument, output: OutputPathOption = None) -> Iterab ] -@artifacts_cli.command( - help="Create a temporary file for artifact-related work and print its path. Use it to create new artifacts" -) +@artifacts_cli.command(help="Create a temporary file for artifact-related work and print its path.") @cells_cli def tmp( slug_with_extension: str = typer.Argument(..., help="Temporary file slug with extension (example: 'draft.md').") @@ -151,90 +119,6 @@ def tmp( ] -@artifacts_cli.command(help="Create or replace an artifact from a file path or stdin.") -@cells_cli -def update( - id: FullArtifactIdArgument, - input: InputPathArgument, - extension: ExtensionOption = None, -) -> Iterable[Cell]: - input_path: pathlib.Path | None - - if input == pathlib.Path("-"): - content_bytes = sys.stdin.buffer.read() - input_display = "stdin" - input_path = None - else: - content_bytes = input.read_bytes() - input_display = str(input) - input_path = input - - resolved_extension = _resolve_update_extension(input_path, extension) - - _log_artifact_operation(f"Update artifact `{id}` from '{input_display}'") - - context().artifacts.update( - id, - content_bytes, - extension=resolved_extension, - ).unwrap() - return [ - operation_succeeded( - f"Artifact `{id}` updated from '{input_display}'", - artifact_id=str(id), - input_path=input_display, - ) - ] - - -@artifacts_cli.command(help="Copy an artifact to another artifact ID (possibly across worlds).") -@cells_cli -def copy(source_id: FullArtifactIdArgument, target_id: FullArtifactIdArgument) -> Iterable[Cell]: - _log_artifact_operation(f"Copy artifact from `{source_id}` to `{target_id}`") - - context().artifacts.copy(source_id, target_id).unwrap() - return [ - operation_succeeded( - f"Artifact `{source_id}` copied to `{target_id}`", - source_id=str(source_id), - target_id=str(target_id), - ) - ] - - -@artifacts_cli.command(help="Move an artifact to another artifact ID (possibly across worlds).") -@cells_cli -def move(source_id: FullArtifactIdArgument, target_id: FullArtifactIdArgument) -> Iterable[Cell]: - _log_artifact_operation(f"Move artifact from `{source_id}` to `{target_id}`") - - context().artifacts.move(source_id, target_id).unwrap() - return [ - operation_succeeded( - f"Artifact `{source_id}` moved to `{target_id}`", - source_id=str(source_id), - target_id=str(target_id), - ) - ] - - -@artifacts_cli.command(help="Remove artifacts matching a pattern.") -@cells_cli -def remove( - pattern: FullArtifactIdPatternArgument, - predicate: PredicateOption = None, -) -> Iterable[Cell]: - _log_operation_on_artifacts("Remove artifacts", pattern, predicate) - - artifacts = context().artifacts.list(pattern, RENDER_CONTEXT_VIEW, predicate=predicate).unwrap() - - cells: builtins.list[Cell] = [] - for artifact in artifacts: - context().artifacts.remove(artifact.id).unwrap() - cells.append(operation_succeeded(f"Artifact `{artifact.id}` removed", artifact_id=str(artifact.id))) - - return cells - - @artifacts_cli.command(help="Validate artifacts matching a pattern (defaults to all artifacts) and return any errors.") @cells_cli def validate( @@ -261,5 +145,5 @@ def validate( app.add_typer( artifacts_cli, name="artifacts", - help="Inspect, fetch, update, and validate stored artifacts across all Donna worlds.", + help="Inspect, fetch, and validate stored artifacts across all Donna worlds.", ) diff --git a/donna/cli/types.py b/donna/cli/types.py index 31d9176..49171fa 100644 --- a/donna/cli/types.py +++ b/donna/cli/types.py @@ -1,5 +1,4 @@ import pathlib -import re from typing import Annotated import typer @@ -71,19 +70,6 @@ def _parse_protocol_mode(value: str) -> Mode: raise typer.BadParameter(f"Unsupported protocol mode '{value}'. Expected one of: {allowed}.") from exc -def _parse_extension(value: str) -> str: - normalized = value.strip().lower().lstrip(".") - if not normalized: - raise typer.BadParameter("Extension must not be empty.") - - if re.fullmatch(r"[a-z0-9][a-z0-9_-]*", normalized) is None: - raise typer.BadParameter( - "Invalid extension format. Use letters, digits, underscore, and dash (for example: md, yaml)." - ) - - return normalized - - def _parse_input_path(value: str) -> pathlib.Path: normalized = value.strip() if normalized == "-": @@ -183,19 +169,6 @@ def _parse_input_path(value: str) -> pathlib.Path: ] -ExtensionOption = Annotated[ - str | None, - typer.Option( - "--extension", - parser=_parse_extension, - help=( - "Optional artifact source extension to use for update " - "(for example: md, yaml). Accepts values with or without leading dot." - ), - ), -] - - InputPathArgument = Annotated[ pathlib.Path, typer.Argument( diff --git a/donna/context/artifacts.py b/donna/context/artifacts.py index c93b1b7..359a1df 100644 --- a/donna/context/artifacts.py +++ b/donna/context/artifacts.py @@ -115,48 +115,6 @@ def resolve_section( artifact = self.load(target_id.full_artifact_id, render_context).unwrap() return Ok(artifact.get_section(target_id.local_id).unwrap()) - @unwrap_to_error - def update( - self, - full_id: FullArtifactId, - content_bytes: bytes, - extension: str | None = None, - ) -> Result[None, ErrorsList]: - from donna.workspaces import artifacts as workspace_artifacts - - workspace_artifacts.update_artifact( - full_id=full_id, - content_bytes=content_bytes, - extension=extension, - ).unwrap() - self.invalidate(full_id) - return Ok(None) - - @unwrap_to_error - def copy(self, source_id: FullArtifactId, target_id: FullArtifactId) -> Result[None, ErrorsList]: - from donna.workspaces import artifacts as workspace_artifacts - - workspace_artifacts.copy_artifact(source_id, target_id).unwrap() - self.invalidate(target_id) - return Ok(None) - - @unwrap_to_error - def move(self, source_id: FullArtifactId, target_id: FullArtifactId) -> Result[None, ErrorsList]: - from donna.workspaces import artifacts as workspace_artifacts - - workspace_artifacts.move_artifact(source_id, target_id).unwrap() - self.invalidate(source_id) - self.invalidate(target_id) - return Ok(None) - - @unwrap_to_error - def remove(self, full_id: FullArtifactId) -> Result[None, ErrorsList]: - from donna.workspaces import artifacts as workspace_artifacts - - workspace_artifacts.remove_artifact(full_id).unwrap() - self.invalidate(full_id) - return Ok(None) - @unwrap_to_error def file_extension(self, full_id: FullArtifactId) -> Result[str, ErrorsList]: from donna.workspaces import artifacts as workspace_artifacts diff --git a/donna/workspaces/artifacts.py b/donna/workspaces/artifacts.py index 8b7005d..cd58d78 100644 --- a/donna/workspaces/artifacts.py +++ b/donna/workspaces/artifacts.py @@ -2,10 +2,9 @@ from donna.core.entities import BaseEntity from donna.core.errors import ErrorsList -from donna.core.result import Err, Ok, Result, unwrap_to_error -from donna.domain.ids import FullArtifactId, WorldId +from donna.core.result import Ok, Result, unwrap_to_error +from donna.domain.ids import FullArtifactId from donna.machine.tasks import Task, WorkUnit -from donna.workspaces import errors from donna.workspaces.config import config from donna.workspaces.templates import RenderMode @@ -19,84 +18,6 @@ class ArtifactRenderContext(BaseEntity): RENDER_CONTEXT_VIEW = ArtifactRenderContext(primary_mode=RenderMode.view) -class ArtifactUpdateError(errors.WorkspaceError): - cell_kind: str = "artifact_update_error" - artifact_id: FullArtifactId - - def content_intro(self) -> str: - return f"Error updating artifact '{self.artifact_id}'" - - -class CanNotUpdateReadonlyWorld(ArtifactUpdateError): - code: str = "donna.workspaces.cannot_update_readonly_world" - message: str = "Cannot upload artifact to the read-only world `{error.world_id}`" - world_id: WorldId - - -class ArtifactRemoveError(errors.WorkspaceError): - cell_kind: str = "artifact_remove_error" - artifact_id: FullArtifactId - - def content_intro(self) -> str: - return f"Error removing artifact '{self.artifact_id}'" - - -class CanNotRemoveReadonlyWorld(ArtifactRemoveError): - code: str = "donna.workspaces.cannot_remove_readonly_world" - message: str = "Cannot remove artifact from the read-only world `{error.world_id}`" - world_id: WorldId - - -class ArtifactExtensionCannotBeInferred(ArtifactUpdateError): - code: str = "donna.workspaces.artifact_extension_cannot_be_inferred" - message: str = "Cannot infer artifact extension." - ways_to_fix: list[str] = [ - "Pass `--extension ` when updating the artifact.", - "Provide an input path with extension (for example `*.md`).", - "Update an artifact that already exists and has a known extension if that is applicable.", - ] - - -class ArtifactExtensionMismatch(ArtifactUpdateError): - code: str = "donna.workspaces.artifact_extension_mismatch" - message: str = ( - "Provided extension `{error.provided_extension}` does not match existing artifact extension " - "`{error.existing_extension}`" - ) - provided_extension: str - existing_extension: str - ways_to_fix: list[str] = [ - "Use the existing artifact extension.", - "Omit `--extension` to use extension inferred from the existing artifact.", - ] - - -class NoSourceForArtifactExtension(ArtifactUpdateError): - code: str = "donna.workspaces.no_source_for_artifact_extension" - message: str = "No source found for artifact extension of input path" - extension: str - - -class ArtifactCopyError(errors.WorkspaceError): - cell_kind: str = "artifact_copy_error" - source_id: FullArtifactId - target_id: FullArtifactId - - def content_intro(self) -> str: - return f"Error copying artifact '{self.source_id}' to '{self.target_id}'" - - -class CanNotCopyToReadonlyWorld(ArtifactCopyError): - code: str = "donna.workspaces.cannot_copy_to_readonly_world" - message: str = "Cannot copy artifact to the read-only world `{error.world_id}`" - world_id: WorldId - - -class SourceArtifactHasNoExtension(ArtifactCopyError): - code: str = "donna.workspaces.source_artifact_has_no_extension" - message: str = "Source artifact has no extension to determine source type" - - @unwrap_to_error def artifact_file_extension(full_id: FullArtifactId) -> Result[str, ErrorsList]: world = config().get_world(full_id.world_id).unwrap() @@ -112,136 +33,3 @@ def fetch_artifact(full_id: FullArtifactId, output: pathlib.Path) -> Result[None f.write(raw_artifact.get_bytes()) return Ok(None) - - -@unwrap_to_error -def update_artifact( # noqa: CCR001 - full_id: FullArtifactId, - content_bytes: bytes, - extension: str | None = None, -) -> Result[None, ErrorsList]: - world = config().get_world(full_id.world_id).unwrap() - - if world.readonly: - return Err([CanNotUpdateReadonlyWorld(artifact_id=full_id, world_id=world.id)]) - - expected_extension = artifact_file_extension(full_id).unwrap_or(None) - - requested_extension = extension.lstrip(".").lower() if extension is not None else None - - def mismatch_error(a: str, b: str) -> Result[None, ErrorsList]: - return Err( - [ - ArtifactExtensionMismatch( - artifact_id=full_id, - provided_extension=a, - existing_extension=b, - ) - ] - ) - - match (expected_extension, requested_extension): - case (None, None): - return Err([ArtifactExtensionCannotBeInferred(artifact_id=full_id)]) - - case (None, requested): - assert requested is not None - source_suffix = requested - - case (expected, None): - assert expected is not None - source_suffix = expected - - case (expected, requested) if expected == requested: - assert expected is not None - source_suffix = expected - - case (expected, requested): - assert expected is not None - assert requested is not None - return mismatch_error(requested, expected) - - normalized_source_suffix = f".{source_suffix}" - - source_config = config().find_source_for_extension(normalized_source_suffix) - if source_config is None: - return Err( - [ - NoSourceForArtifactExtension( - artifact_id=full_id, - extension=normalized_source_suffix, - ) - ] - ) - - render_context = RENDER_CONTEXT_VIEW - test_artifact = source_config.construct_artifact_from_bytes(full_id, content_bytes, render_context).unwrap() - validation_result = test_artifact.validate_artifact() - - validation_result.unwrap() - world.update(full_id.artifact_id, content_bytes, normalized_source_suffix).unwrap() - - return Ok(None) - - -@unwrap_to_error -def copy_artifact(source_id: FullArtifactId, target_id: FullArtifactId) -> Result[None, ErrorsList]: - source_world = config().get_world(source_id.world_id).unwrap() - target_world = config().get_world(target_id.world_id).unwrap() - - if target_world.readonly: - return Err( - [ - CanNotCopyToReadonlyWorld( - source_id=source_id, - target_id=target_id, - world_id=target_world.id, - ) - ] - ) - - source_raw_artifact = source_world.fetch(source_id.artifact_id).unwrap() - content_bytes = source_raw_artifact.get_bytes() - source_extension = source_world.file_extension_for(source_id.artifact_id).unwrap() - - if not source_extension: - return Err([SourceArtifactHasNoExtension(source_id=source_id, target_id=target_id)]) - - source_extension = source_extension.lower() - source_config = config().find_source_for_extension(source_extension) - if source_config is None: - return Err( - [ - NoSourceForArtifactExtension( - artifact_id=source_id, - extension=source_extension, - ) - ] - ) - - render_context = RENDER_CONTEXT_VIEW - test_artifact = source_config.construct_artifact_from_bytes(target_id, content_bytes, render_context).unwrap() - test_artifact.validate_artifact().unwrap() - - target_world.update(target_id.artifact_id, content_bytes, source_extension).unwrap() - return Ok(None) - - -@unwrap_to_error -def move_artifact(source_id: FullArtifactId, target_id: FullArtifactId) -> Result[None, ErrorsList]: - copy_result = copy_artifact(source_id, target_id) - if copy_result.is_err(): - return copy_result - - return remove_artifact(source_id) - - -@unwrap_to_error -def remove_artifact(full_id: FullArtifactId) -> Result[None, ErrorsList]: - world = config().get_world(full_id.world_id).unwrap() - - if world.readonly: - return Err([CanNotRemoveReadonlyWorld(artifact_id=full_id, world_id=world.id)]) - - world.remove(full_id.artifact_id).unwrap() - return Ok(None) diff --git a/donna/workspaces/config.py b/donna/workspaces/config.py index 05b95ca..26521a6 100644 --- a/donna/workspaces/config.py +++ b/donna/workspaces/config.py @@ -29,7 +29,6 @@ class WorldConfig(BaseEntity): kind: PythonImportPath id: WorldId - readonly: bool session: bool model_config = pydantic.ConfigDict(extra="allow") @@ -57,7 +56,6 @@ def _create_default_worlds() -> list[WorldConfig]: { "id": WorldId("donna"), "kind": "donna.lib.worlds.python", - "readonly": True, "session": False, "package": "donna", } @@ -66,7 +64,6 @@ def _create_default_worlds() -> list[WorldConfig]: { "id": WorldId("home"), "kind": "donna.lib.worlds.filesystem", - "readonly": True, "session": False, "path": f"~/{DONNA_DIR_NAME}/{DONNA_WORLD_HOME_DIR_NAME}", } @@ -75,7 +72,6 @@ def _create_default_worlds() -> list[WorldConfig]: { "id": WorldId("project"), "kind": "donna.lib.worlds.filesystem", - "readonly": False, "session": False, "path": pathlib.Path(DONNA_DIR_NAME) / DONNA_WORLD_PROJECT_DIR_NAME, } @@ -84,7 +80,6 @@ def _create_default_worlds() -> list[WorldConfig]: { "id": WorldId("session"), "kind": "donna.lib.worlds.filesystem", - "readonly": False, "session": True, "path": pathlib.Path(DONNA_DIR_NAME) / DONNA_WORLD_SESSION_DIR_NAME, } diff --git a/donna/workspaces/errors.py b/donna/workspaces/errors.py index 07c9c20..e3dc2a4 100644 --- a/donna/workspaces/errors.py +++ b/donna/workspaces/errors.py @@ -64,14 +64,6 @@ class SourceConfigNotConfigured(SourceError): kind: str -class WorldReadonly(WorldError): - code: str = "donna.workspaces.world_readonly" - message: str = "World `{error.world_id}` is read-only" - ways_to_fix: list[str] = [ - "Use a world configured with readonly = false. Most likely they are `project` and `session`.", - ] - - class WorldStateStorageUnsupported(WorldError): code: str = "donna.workspaces.state_storage_unsupported" message: str = "World `{error.world_id}` does not support state storage" diff --git a/donna/workspaces/worlds/base.py b/donna/workspaces/worlds/base.py index 25e69ff..29e2bb3 100644 --- a/donna/workspaces/worlds/base.py +++ b/donna/workspaces/worlds/base.py @@ -30,7 +30,6 @@ def render(self, full_id: FullArtifactId, render_context: "ArtifactRenderContext class World(BaseEntity, ABC): id: WorldId - readonly: bool = True session: bool = False @abstractmethod @@ -43,14 +42,6 @@ def fetch(self, artifact_id: ArtifactId) -> Result[RawArtifact, ErrorsList]: ... def has_artifact_changed(self, artifact_id: ArtifactId, since: Milliseconds) -> Result[bool, ErrorsList]: pass - @abstractmethod - def update( # noqa: E704 - self, artifact_id: ArtifactId, content: bytes, extension: str - ) -> Result[None, ErrorsList]: ... # noqa: E704 - - @abstractmethod - def remove(self, artifact_id: ArtifactId) -> Result[None, ErrorsList]: ... # noqa: E704 - @abstractmethod def file_extension_for(self, artifact_id: ArtifactId) -> Result[str, ErrorsList]: ... # noqa: E704 diff --git a/donna/workspaces/worlds/filesystem.py b/donna/workspaces/worlds/filesystem.py index c4a2953..8f4c327 100644 --- a/donna/workspaces/worlds/filesystem.py +++ b/donna/workspaces/worlds/filesystem.py @@ -51,9 +51,6 @@ def _artifact_listing_root(self) -> ArtifactListingNode | None: return cast(ArtifactListingNode, self.path) - def _artifact_path(self, artifact_id: ArtifactId, extension: str) -> pathlib.Path: - return self.path / f"{artifact_id.replace(':', '/')}{extension}" - def _resolve_artifact_file(self, artifact_id: ArtifactId) -> Result[pathlib.Path | None, ErrorsList]: artifact_path = self.path / artifact_id.replace(":", "/") parent = artifact_path.parent @@ -121,30 +118,6 @@ def has_artifact_changed(self, artifact_id: ArtifactId, since: Milliseconds) -> return Ok((path.stat().st_mtime_ns // 1_000_000) > since) - def update(self, artifact_id: ArtifactId, content: bytes, extension: str) -> Result[None, ErrorsList]: - if self.readonly: - return Err([world_errors.WorldReadonly(world_id=self.id)]) - - path = self._artifact_path(artifact_id, extension) - - path.parent.mkdir(parents=True, exist_ok=True) - path.write_bytes(content) - return Ok(None) - - @unwrap_to_error - def remove(self, artifact_id: ArtifactId) -> Result[None, ErrorsList]: - if self.readonly: - return Err([world_errors.WorldReadonly(world_id=self.id)]) - - path = self._resolve_artifact_file(artifact_id).unwrap() - - if path is None: - return Ok(None) - - path.unlink() - - return Ok(None) - @unwrap_to_error def file_extension_for(self, artifact_id: ArtifactId) -> Result[str, ErrorsList]: path = self._resolve_artifact_file(artifact_id).unwrap() @@ -165,9 +138,6 @@ def read_state(self, name: str) -> Result[bytes | None, ErrorsList]: return Ok(path.read_bytes()) def write_state(self, name: str, content: bytes) -> Result[None, ErrorsList]: - if self.readonly: - return Err([world_errors.WorldReadonly(world_id=self.id)]) - if not self.session: return Err([world_errors.WorldStateStorageUnsupported(world_id=self.id)]) @@ -177,9 +147,6 @@ def write_state(self, name: str, content: bytes) -> Result[None, ErrorsList]: return Ok(None) def journal_reset(self) -> Result[None, ErrorsList]: - if self.readonly: - return Err([world_errors.WorldReadonly(world_id=self.id)]) - if not self.session: return Err([world_errors.WorldStateStorageUnsupported(world_id=self.id)]) @@ -189,9 +156,6 @@ def journal_reset(self) -> Result[None, ErrorsList]: return Ok(None) def journal_add(self, content: bytes) -> Result[None, ErrorsList]: - if self.readonly: - return Err([world_errors.WorldReadonly(world_id=self.id)]) - if not self.session: return Err([world_errors.WorldStateStorageUnsupported(world_id=self.id)]) @@ -296,9 +260,6 @@ def list_artifacts(self, pattern: FullArtifactIdPattern) -> list[ArtifactId]: # ) def initialize(self, reset: bool = False) -> None: - if self.readonly: - return - if self.path.exists() and reset: shutil.rmtree(self.path) @@ -324,6 +285,5 @@ def construct_world(self, config: "WorldConfig") -> World: return World( id=config.id, path=path.resolve(), - readonly=config.readonly, session=config.session, ) diff --git a/donna/workspaces/worlds/python.py b/donna/workspaces/worlds/python.py index 54af009..97e38cc 100644 --- a/donna/workspaces/worlds/python.py +++ b/donna/workspaces/worlds/python.py @@ -38,7 +38,6 @@ def render( class Python(BaseWorld): id: WorldId - readonly: bool = True session: bool = False package: str artifacts_root: str @@ -136,12 +135,6 @@ def fetch(self, artifact_id: ArtifactId) -> Result[RawArtifact, ErrorsList]: # def has_artifact_changed(self, artifact_id: ArtifactId, since: Milliseconds) -> Result[bool, ErrorsList]: return Ok(False) - def update(self, artifact_id: ArtifactId, content: bytes, extension: str) -> Result[None, ErrorsList]: - return Err([world_errors.WorldReadonly(world_id=self.id)]) - - def remove(self, artifact_id: ArtifactId) -> Result[None, ErrorsList]: - return Err([world_errors.WorldReadonly(world_id=self.id)]) - @unwrap_to_error def file_extension_for(self, artifact_id: ArtifactId) -> Result[str, ErrorsList]: resource_path = self._resolve_artifact_file(artifact_id).unwrap() @@ -200,6 +193,5 @@ def construct_world(self, config: "WorldConfig") -> Python: id=config.id, package=str(package), artifacts_root=artifacts_root, - readonly=config.readonly, session=config.session, )