Skip to content

Commit

Permalink
Define public API of datalad_next.archive_operations
Browse files Browse the repository at this point in the history
  • Loading branch information
mih committed Feb 5, 2024
1 parent d5e50fa commit db28841
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 96 deletions.
3 changes: 1 addition & 2 deletions datalad_next/annexremotes/archivist.py
Original file line number Diff line number Diff line change
Expand Up @@ -523,8 +523,7 @@ def _get_local_handler(self, ainfo: _ArchiveInfo) -> ArchiveOperations:
raise NotImplementedError

if ainfo.type == ArchiveType.tar:
from datalad_next.archive_operations.tarfile import (
TarArchiveOperations)
from datalad_next.archive_operations import TarArchiveOperations
return TarArchiveOperations(
ainfo.local_path,
cfg=self._repo.config,
Expand Down
99 changes: 7 additions & 92 deletions datalad_next/archive_operations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@
.. autosummary::
:toctree: generated
tarfile
zipfile
TarArchiveOperations
ZipArchiveOperations
"""
from .tarfile import TarArchiveOperations
from .zipfile import ZipArchiveOperations

# allow for |-type UnionType declarations
from __future__ import annotations

# TODO REMOVE EVERYTHING BELOW FOR V2.0
import logging
from abc import (
ABC,
Expand All @@ -26,97 +27,11 @@
Generator,
IO,
)

# this API is not cooked enough yet to promote it for 3rd-part extensions
from .base import ArchiveOperations
import datalad

from datalad_next.config import ConfigManager
from datalad_next.iter_collections.utils import FileSystemItem


lgr = logging.getLogger('datalad.ext.next.archive_operations')


class ArchiveOperations(ABC):
"""Base class of all archives handlers
Any handler can be used as a context manager to adequately acquire and
release any resources necessary to access an archive. Alternatively,
the :func:`~ArchiveOperations.close()` method can be called, when archive
access is no longer needed.
In addition to the :func:`~ArchiveOperations.open()` method for accessing
archive item content, each handler implements the standard
``__contains__()``, and ``__iter__()`` methods.
``__contains__() -> bool`` reports whether the archive contains an items of
a given identifier.
``__iter__()`` provides an iterator that yields
:class:`~datalad_next.iter_collections.utils.FileSystemItem` instances with
information on each archive item.
"""
def __init__(self, location: Any, *, cfg: ConfigManager | None = None):
"""
Parameters
----------
location:
Archive location identifier (path, URL, etc.) understood by a
particular archive handler.
cfg: ConfigManager, optional
A config manager instance that implementations will consult for
any configuration items they may support.
"""
self._cfg = cfg
self._location = location

def __str__(self) -> str:
return f'{self.__class__.__name__}({self._location})'

def __repr__(self) -> str:
return \
f'{self.__class__.__name__}({self._location}, cfg={self._cfg!r})'

@property
def cfg(self) -> ConfigManager:
"""ConfigManager given to the constructor, or the session default"""
if self._cfg is None:
self._cfg = datalad.cfg
return self._cfg

def __enter__(self):
"""Default implementation that does nothing in particular"""
return self

def __exit__(self, exc_type, exc_value, traceback):
"""Default implementation that only calls ``.close()``"""
self.close()
# we have no desire to suppress exception, indicate standard
# handling by not returning True
return

@contextmanager
@abstractmethod
def open(self, item: Any) -> Generator[IO | None, None, None]:
"""Get a file-like for an archive item
Parameters
----------
item:
Any identifier for an archive item supported by a particular handler
"""
raise NotImplementedError

def close(self) -> None:
"""Default implementation for closing a archive handler
This default implementation does nothing.
"""
pass

@abstractmethod
def __contains__(self, item: Any) -> bool:
raise NotImplementedError

@abstractmethod
def __iter__(self) -> Generator[FileSystemItem, None, None]:
raise NotImplementedError
104 changes: 104 additions & 0 deletions datalad_next/archive_operations/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# allow for |-type UnionType declarations
from __future__ import annotations

from abc import (
ABC,
abstractmethod,
)
from contextlib import contextmanager
from typing import (
Any,
Generator,
IO,
)

import datalad

from datalad_next.config import ConfigManager
from datalad_next.iter_collections import FileSystemItem


class ArchiveOperations(ABC):
"""Base class of all archives handlers
Any handler can be used as a context manager to adequately acquire and
release any resources necessary to access an archive. Alternatively,
the :func:`~ArchiveOperations.close()` method can be called, when archive
access is no longer needed.
In addition to the :func:`~ArchiveOperations.open()` method for accessing
archive item content, each handler implements the standard
``__contains__()``, and ``__iter__()`` methods.
``__contains__() -> bool`` reports whether the archive contains an items of
a given identifier.
``__iter__()`` provides an iterator that yields
:class:`~datalad_next.iter_collections.FileSystemItem` instances with
information on each archive item.
"""
def __init__(self, location: Any, *, cfg: ConfigManager | None = None):
"""
Parameters
----------
location:
Archive location identifier (path, URL, etc.) understood by a
particular archive handler.
cfg: ConfigManager, optional
A config manager instance that implementations will consult for
any configuration items they may support.
"""
self._cfg = cfg
self._location = location

def __str__(self) -> str:
return f'{self.__class__.__name__}({self._location})'

def __repr__(self) -> str:
return \
f'{self.__class__.__name__}({self._location}, cfg={self._cfg!r})'

@property
def cfg(self) -> ConfigManager:
"""ConfigManager given to the constructor, or the session default"""
if self._cfg is None:
self._cfg = datalad.cfg
return self._cfg

def __enter__(self):
"""Default implementation that does nothing in particular"""
return self

def __exit__(self, exc_type, exc_value, traceback):
"""Default implementation that only calls ``.close()``"""
self.close()
# we have no desire to suppress exception, indicate standard
# handling by not returning True
return

@contextmanager
@abstractmethod
def open(self, item: Any) -> Generator[IO | None, None, None]:
"""Get a file-like for an archive item
Parameters
----------
item:
Any identifier for an archive item supported by a particular handler
"""
raise NotImplementedError

def close(self) -> None:
"""Default implementation for closing a archive handler
This default implementation does nothing.
"""
pass

@abstractmethod
def __contains__(self, item: Any) -> bool:
raise NotImplementedError

@abstractmethod
def __iter__(self) -> Generator[FileSystemItem, None, None]:
raise NotImplementedError
2 changes: 1 addition & 1 deletion datalad_next/archive_operations/tarfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
iter_tar,
)

from . import ArchiveOperations
from .base import ArchiveOperations

lgr = logging.getLogger('datalad.ext.next.archive_operations.tarfile')

Expand Down
2 changes: 1 addition & 1 deletion datalad_next/archive_operations/zipfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
ZipfileItem,
iter_zip,
)
from . import ArchiveOperations
from .base import ArchiveOperations


lgr = logging.getLogger('datalad.ext.next.archive_operations.zipfile')
Expand Down

0 comments on commit db28841

Please sign in to comment.