-
Notifications
You must be signed in to change notification settings - Fork 0
Defines new path manager #32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| """Declarative description of the local store's directory structure.""" | ||
|
|
||
| from dataclasses import dataclass, field | ||
| from pathlib import Path | ||
| from typing import Final | ||
|
|
||
| from dstrack.paths import STORE_DIRNAME | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class TemplateFile: | ||
| """A file to create, with its literal content.""" | ||
|
|
||
| name: str | ||
| content: str = "" | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class TemplateDir: | ||
| """A directory to create, optionally containing files and subdirectories.""" | ||
|
|
||
| name: str | ||
| entries: "tuple[TemplateDir | TemplateFile, ...]" = field(default_factory=tuple) | ||
|
|
||
|
|
||
| STORE_TEMPLATE: Final[TemplateDir] = TemplateDir( | ||
| name=STORE_DIRNAME, | ||
| entries=( | ||
| TemplateDir(name="datasets"), | ||
| TemplateFile(name=".gitignore", content=".cache/"), | ||
| ), | ||
| ) | ||
|
|
||
|
|
||
| def materialize(template: TemplateDir, parent: Path) -> Path: | ||
| """Recursively create `template`'s directory structure under `parent`. | ||
|
|
||
| Args: | ||
| template: Directory template to materialize, including the | ||
| top-level directory itself. | ||
| parent: Existing directory that `template`'s directory is created in. | ||
|
|
||
| Returns: | ||
| The path created for `template`. | ||
|
|
||
| Raises: | ||
| FileExistsError: If any file or directory in the template already | ||
| exists under `parent`. | ||
| """ | ||
| path = parent / template.name | ||
| path.mkdir() | ||
| for entry in template.entries: | ||
| if isinstance(entry, TemplateDir): | ||
| materialize(entry, path) | ||
| else: | ||
| with open(path / entry.name, "x") as f: | ||
| f.write(entry.content) | ||
| return path | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,6 @@ | ||
| class StoreInitError(Exception): | ||
| """Raised when local store initialization fails partway through.""" | ||
|
|
||
|
|
||
| class StoreNotFoundError(Exception): | ||
| """Raised when no local store root can be resolved.""" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| """Resolution of the local store's directory, `STORE_DIRNAME/`. | ||
|
|
||
| Mirrors how git resolves `.git/`: absent an explicit override, it is found by | ||
| walking up from the current directory until a `STORE_DIRNAME/` directory turns up. | ||
| """ | ||
|
|
||
| import os | ||
| from pathlib import Path | ||
| from typing import Final | ||
|
|
||
| from dstrack.errors import StoreNotFoundError | ||
| from dstrack.utils import get_invocation_path | ||
|
|
||
| STORE_DIRNAME: Final = ".dstrack" | ||
| ROOT_PATH_ENV_VAR: Final = "DSTRACK_ROOT_PATH" | ||
|
|
||
|
|
||
| def _find_store_root(start: Path) -> Path: | ||
| """Walk upwards from `start` looking for a `STORE_DIRNAME/` directory. | ||
|
|
||
| Stops, like git, at the filesystem root or as soon as it would cross | ||
| onto a different filesystem/mount than `start`, rather than wandering | ||
| indefinitely upwards into unrelated system directories. | ||
|
|
||
| Args: | ||
| start: Directory to start the search from. | ||
|
|
||
| Returns: | ||
| The path to the `STORE_DIRNAME/` directory itself, the first one found | ||
| at or above `start`. | ||
|
|
||
| Raises: | ||
| StoreNotFoundError: If no directory at or above `start`, up to the | ||
| nearest filesystem boundary, contains a `STORE_DIRNAME/` directory. | ||
| """ | ||
| current = start.resolve() | ||
| boundary_dev = current.stat().st_dev | ||
|
|
||
| while True: | ||
| candidate = current / STORE_DIRNAME | ||
| if candidate.is_dir(): | ||
| return candidate | ||
|
|
||
| parent = current.parent | ||
| if parent == current or parent.stat().st_dev != boundary_dev: | ||
| break | ||
| current = parent | ||
|
|
||
| raise StoreNotFoundError( | ||
| f"Could not find a `{STORE_DIRNAME}/` directory in `{start}` or any " | ||
| "of its parent directories up to the filesystem boundary. Run " | ||
| "`dstrack init` to create one, or point at one explicitly via " | ||
| f"`--root` or `{ROOT_PATH_ENV_VAR}`." | ||
| ) | ||
|
|
||
|
|
||
| def resolve_store_root(root: Path | str | None = None) -> Path: | ||
| """Resolve the path to the local store, `STORE_DIRNAME/`. | ||
|
|
||
| Checked in order, the first one available wins: | ||
| 1. `root`, e.g. forwarded from a CLI `--root` option: the directory | ||
| `STORE_DIRNAME/` lives (or will be created) in. | ||
| 2. The `DSTRACK_ROOT_PATH` environment variable: same meaning as | ||
| `root`. | ||
| 3. Walking upwards from the current working directory until a | ||
| `STORE_DIRNAME/` directory is found, the same way git resolves `.git/`. | ||
|
|
||
| Args: | ||
| root: Explicit parent directory of `STORE_DIRNAME/`, typically supplied | ||
| by a CLI `--root` option. Taken as-is, without checking that | ||
| `STORE_DIRNAME/` actually exists inside it. | ||
|
|
||
| Returns: | ||
| The path to the `STORE_DIRNAME/` directory itself, as an absolute path. | ||
|
|
||
| Raises: | ||
| StoreNotFoundError: `root` is `None`, `DSTRACK_ROOT_PATH` is | ||
| unset, and no `STORE_DIRNAME/` directory is found by walking up from | ||
| the current directory. | ||
| """ | ||
| if root is not None: | ||
| return Path(root).expanduser().resolve() / STORE_DIRNAME | ||
|
|
||
| env_root = os.environ.get(ROOT_PATH_ENV_VAR) | ||
| if env_root: | ||
| return Path(env_root).expanduser().resolve() / STORE_DIRNAME | ||
|
|
||
| return _find_store_root(get_invocation_path()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.