smith.domain.ports

Port definitions — interfaces that infrastructure adapters must implement.

 1"""Port definitions — interfaces that infrastructure adapters must implement."""
 2
 3from pathlib import Path
 4from typing import Protocol
 5
 6from smith.domain.value_objects import (
 7    FileSpec,
 8    TemplateSource,
 9)
10
11
12class TemplateSourceError(Exception):
13    """Raised when a template source cannot be resolved."""
14
15
16class TemplateSourcePort(Protocol):
17    """Interface for resolving template file specifications."""
18
19    def resolve(self) -> list[FileSpec]:
20        """Return the list of file specs from this template source."""
21        ...
22
23    def gitignore_patterns(self) -> list[str]:
24        """Return the gitignore patterns for this template source."""
25        ...
26
27
28class FileSystemPort(Protocol):
29    """Interface for atomic file-system operations."""
30
31    def check_conflicts(self, paths: list[Path]) -> list[Path]:
32        """Return the subset of *paths* that already exist on disk."""
33        ...
34
35    def write_atomic(self, specs: list[FileSpec]) -> None:
36        """Write all specs atomically; roll back on failure."""
37        ...
38
39    def remove(self, paths: list[Path]) -> None:
40        """Remove the given paths from the project directory."""
41        ...
42
43    def exists(self, paths: list[Path]) -> dict[Path, bool]:
44        """Return a mapping of each path to whether it exists on disk."""
45        ...
46
47
48class GitignorePort(Protocol):
49    """Interface for managing the smith-managed .gitignore section."""
50
51    def add_section(self, patterns: list[str]) -> None:
52        """Add or replace the smith-managed section in .gitignore."""
53        ...
54
55    def has_section(self) -> bool:
56        """Return whether a smith-managed section exists in .gitignore."""
57        ...
58
59    def get_patterns(self) -> list[str]:
60        """Return the patterns listed inside the smith-managed section."""
61        ...
62
63
64class MetadataPort(Protocol):
65    """Interface for persisting and loading template source metadata."""
66
67    def save_source(self, source: TemplateSource) -> None:
68        """Write the source identifier into the smith-managed section header."""
69        ...
70
71    def load_source(self) -> TemplateSource | None:
72        """Read the source identifier from the smith-managed section header."""
73        ...
class TemplateSourceError(builtins.Exception):
13class TemplateSourceError(Exception):
14    """Raised when a template source cannot be resolved."""

Raised when a template source cannot be resolved.

class TemplateSourcePort(typing.Protocol):
17class TemplateSourcePort(Protocol):
18    """Interface for resolving template file specifications."""
19
20    def resolve(self) -> list[FileSpec]:
21        """Return the list of file specs from this template source."""
22        ...
23
24    def gitignore_patterns(self) -> list[str]:
25        """Return the gitignore patterns for this template source."""
26        ...

Interface for resolving template file specifications.

TemplateSourcePort(*args, **kwargs)
1960def _no_init_or_replace_init(self, *args, **kwargs):
1961    cls = type(self)
1962
1963    if cls._is_protocol:
1964        raise TypeError('Protocols cannot be instantiated')
1965
1966    # Already using a custom `__init__`. No need to calculate correct
1967    # `__init__` to call. This can lead to RecursionError. See bpo-45121.
1968    if cls.__init__ is not _no_init_or_replace_init:
1969        return
1970
1971    # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`.
1972    # The first instantiation of the subclass will call `_no_init_or_replace_init` which
1973    # searches for a proper new `__init__` in the MRO. The new `__init__`
1974    # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent
1975    # instantiation of the protocol subclass will thus use the new
1976    # `__init__` and no longer call `_no_init_or_replace_init`.
1977    for base in cls.__mro__:
1978        init = base.__dict__.get('__init__', _no_init_or_replace_init)
1979        if init is not _no_init_or_replace_init:
1980            cls.__init__ = init
1981            break
1982    else:
1983        # should not happen
1984        cls.__init__ = object.__init__
1985
1986    cls.__init__(self, *args, **kwargs)
def resolve(self) -> list[smith.domain.value_objects.FileSpec]:
20    def resolve(self) -> list[FileSpec]:
21        """Return the list of file specs from this template source."""
22        ...

Return the list of file specs from this template source.

def gitignore_patterns(self) -> list[str]:
24    def gitignore_patterns(self) -> list[str]:
25        """Return the gitignore patterns for this template source."""
26        ...

Return the gitignore patterns for this template source.

class FileSystemPort(typing.Protocol):
29class FileSystemPort(Protocol):
30    """Interface for atomic file-system operations."""
31
32    def check_conflicts(self, paths: list[Path]) -> list[Path]:
33        """Return the subset of *paths* that already exist on disk."""
34        ...
35
36    def write_atomic(self, specs: list[FileSpec]) -> None:
37        """Write all specs atomically; roll back on failure."""
38        ...
39
40    def remove(self, paths: list[Path]) -> None:
41        """Remove the given paths from the project directory."""
42        ...
43
44    def exists(self, paths: list[Path]) -> dict[Path, bool]:
45        """Return a mapping of each path to whether it exists on disk."""
46        ...

Interface for atomic file-system operations.

FileSystemPort(*args, **kwargs)
1960def _no_init_or_replace_init(self, *args, **kwargs):
1961    cls = type(self)
1962
1963    if cls._is_protocol:
1964        raise TypeError('Protocols cannot be instantiated')
1965
1966    # Already using a custom `__init__`. No need to calculate correct
1967    # `__init__` to call. This can lead to RecursionError. See bpo-45121.
1968    if cls.__init__ is not _no_init_or_replace_init:
1969        return
1970
1971    # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`.
1972    # The first instantiation of the subclass will call `_no_init_or_replace_init` which
1973    # searches for a proper new `__init__` in the MRO. The new `__init__`
1974    # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent
1975    # instantiation of the protocol subclass will thus use the new
1976    # `__init__` and no longer call `_no_init_or_replace_init`.
1977    for base in cls.__mro__:
1978        init = base.__dict__.get('__init__', _no_init_or_replace_init)
1979        if init is not _no_init_or_replace_init:
1980            cls.__init__ = init
1981            break
1982    else:
1983        # should not happen
1984        cls.__init__ = object.__init__
1985
1986    cls.__init__(self, *args, **kwargs)
def check_conflicts(self, paths: list[pathlib._local.Path]) -> list[pathlib._local.Path]:
32    def check_conflicts(self, paths: list[Path]) -> list[Path]:
33        """Return the subset of *paths* that already exist on disk."""
34        ...

Return the subset of paths that already exist on disk.

def write_atomic(self, specs: list[smith.domain.value_objects.FileSpec]) -> None:
36    def write_atomic(self, specs: list[FileSpec]) -> None:
37        """Write all specs atomically; roll back on failure."""
38        ...

Write all specs atomically; roll back on failure.

def remove(self, paths: list[pathlib._local.Path]) -> None:
40    def remove(self, paths: list[Path]) -> None:
41        """Remove the given paths from the project directory."""
42        ...

Remove the given paths from the project directory.

def exists( self, paths: list[pathlib._local.Path]) -> dict[pathlib._local.Path, bool]:
44    def exists(self, paths: list[Path]) -> dict[Path, bool]:
45        """Return a mapping of each path to whether it exists on disk."""
46        ...

Return a mapping of each path to whether it exists on disk.

class GitignorePort(typing.Protocol):
49class GitignorePort(Protocol):
50    """Interface for managing the smith-managed .gitignore section."""
51
52    def add_section(self, patterns: list[str]) -> None:
53        """Add or replace the smith-managed section in .gitignore."""
54        ...
55
56    def has_section(self) -> bool:
57        """Return whether a smith-managed section exists in .gitignore."""
58        ...
59
60    def get_patterns(self) -> list[str]:
61        """Return the patterns listed inside the smith-managed section."""
62        ...

Interface for managing the smith-managed .gitignore section.

GitignorePort(*args, **kwargs)
1960def _no_init_or_replace_init(self, *args, **kwargs):
1961    cls = type(self)
1962
1963    if cls._is_protocol:
1964        raise TypeError('Protocols cannot be instantiated')
1965
1966    # Already using a custom `__init__`. No need to calculate correct
1967    # `__init__` to call. This can lead to RecursionError. See bpo-45121.
1968    if cls.__init__ is not _no_init_or_replace_init:
1969        return
1970
1971    # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`.
1972    # The first instantiation of the subclass will call `_no_init_or_replace_init` which
1973    # searches for a proper new `__init__` in the MRO. The new `__init__`
1974    # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent
1975    # instantiation of the protocol subclass will thus use the new
1976    # `__init__` and no longer call `_no_init_or_replace_init`.
1977    for base in cls.__mro__:
1978        init = base.__dict__.get('__init__', _no_init_or_replace_init)
1979        if init is not _no_init_or_replace_init:
1980            cls.__init__ = init
1981            break
1982    else:
1983        # should not happen
1984        cls.__init__ = object.__init__
1985
1986    cls.__init__(self, *args, **kwargs)
def add_section(self, patterns: list[str]) -> None:
52    def add_section(self, patterns: list[str]) -> None:
53        """Add or replace the smith-managed section in .gitignore."""
54        ...

Add or replace the smith-managed section in .gitignore.

def has_section(self) -> bool:
56    def has_section(self) -> bool:
57        """Return whether a smith-managed section exists in .gitignore."""
58        ...

Return whether a smith-managed section exists in .gitignore.

def get_patterns(self) -> list[str]:
60    def get_patterns(self) -> list[str]:
61        """Return the patterns listed inside the smith-managed section."""
62        ...

Return the patterns listed inside the smith-managed section.

class MetadataPort(typing.Protocol):
65class MetadataPort(Protocol):
66    """Interface for persisting and loading template source metadata."""
67
68    def save_source(self, source: TemplateSource) -> None:
69        """Write the source identifier into the smith-managed section header."""
70        ...
71
72    def load_source(self) -> TemplateSource | None:
73        """Read the source identifier from the smith-managed section header."""
74        ...

Interface for persisting and loading template source metadata.

MetadataPort(*args, **kwargs)
1960def _no_init_or_replace_init(self, *args, **kwargs):
1961    cls = type(self)
1962
1963    if cls._is_protocol:
1964        raise TypeError('Protocols cannot be instantiated')
1965
1966    # Already using a custom `__init__`. No need to calculate correct
1967    # `__init__` to call. This can lead to RecursionError. See bpo-45121.
1968    if cls.__init__ is not _no_init_or_replace_init:
1969        return
1970
1971    # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`.
1972    # The first instantiation of the subclass will call `_no_init_or_replace_init` which
1973    # searches for a proper new `__init__` in the MRO. The new `__init__`
1974    # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent
1975    # instantiation of the protocol subclass will thus use the new
1976    # `__init__` and no longer call `_no_init_or_replace_init`.
1977    for base in cls.__mro__:
1978        init = base.__dict__.get('__init__', _no_init_or_replace_init)
1979        if init is not _no_init_or_replace_init:
1980            cls.__init__ = init
1981            break
1982    else:
1983        # should not happen
1984        cls.__init__ = object.__init__
1985
1986    cls.__init__(self, *args, **kwargs)
def save_source(self, source: smith.domain.value_objects.TemplateSource) -> None:
68    def save_source(self, source: TemplateSource) -> None:
69        """Write the source identifier into the smith-managed section header."""
70        ...

Write the source identifier into the smith-managed section header.

def load_source(self) -> smith.domain.value_objects.TemplateSource | None:
72    def load_source(self) -> TemplateSource | None:
73        """Read the source identifier from the smith-managed section header."""
74        ...

Read the source identifier from the smith-managed section header.