Skip to content
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

Type annotate storage #362

Merged
merged 5 commits into from
Jun 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion mypy.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
[mypy]
warn_unused_configs = True
files = securesystemslib/util.py
files =
securesystemslib/util.py,
securesystemslib/storage.py

# Supress error messages until enough modules
# are type annotated
follow_imports = silent
70 changes: 27 additions & 43 deletions securesystemslib/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@
import logging
import os
import shutil

from contextlib import contextmanager
from securesystemslib import exceptions
from typing import BinaryIO, IO, Iterator, List

logger = logging.getLogger(__name__)

Expand All @@ -37,7 +38,8 @@ class StorageBackendInterface():


@abc.abstractmethod
def get(self, filepath):
@contextmanager
def get(self, filepath: str) -> Iterator[BinaryIO]:
"""
<Purpose>
A context manager for 'with' statements that is used for retrieving files
Expand All @@ -63,7 +65,7 @@ def get(self, filepath):


@abc.abstractmethod
def put(self, fileobj, filepath):
def put(self, fileobj: IO, filepath: str) -> None:
"""
<Purpose>
Store a file-like object in the storage backend.
Expand All @@ -87,7 +89,7 @@ def put(self, fileobj, filepath):


@abc.abstractmethod
def remove(self, filepath):
def remove(self, filepath: str) -> None:
"""
<Purpose>
Remove the file at 'filepath' from the storage.
Expand All @@ -106,7 +108,7 @@ def remove(self, filepath):


@abc.abstractmethod
def getsize(self, filepath):
def getsize(self, filepath: str) -> int:
"""
<Purpose>
Retrieve the size, in bytes, of the file at 'filepath'.
Expand All @@ -126,7 +128,7 @@ def getsize(self, filepath):


@abc.abstractmethod
def create_folder(self, filepath):
def create_folder(self, filepath: str) -> None:
"""
<Purpose>
Create a folder at filepath and ensure all intermediate components of the
Expand All @@ -149,7 +151,7 @@ def create_folder(self, filepath):


@abc.abstractmethod
def list_folder(self, filepath):
def list_folder(self, filepath: str) -> List[str]:
"""
<Purpose>
List the contents of the folder at 'filepath'.
Expand Down Expand Up @@ -192,39 +194,21 @@ def __new__(cls, *args, **kwargs):
return cls._instance



class GetFile(object):
# Implementing get() as a function with the @contextmanager decorator
# doesn't allow us to cleanly capture exceptions thrown by the underlying
# implementation and bubble up our generic
# securesystemslib.exceptions.StorageError, therefore we implement get as
# a class and also assign the class to the 'get' attribute of the parent
# FilesystemBackend class.

def __init__(self, filepath):
self.filepath = filepath


def __enter__(self):
try:
self.file_object = open(self.filepath, 'rb')
return self.file_object
except (FileNotFoundError, IOError):
raise exceptions.StorageError(
"Can't open %s" % self.filepath)


def __exit__(self, exc_type, exc_val, traceback):
self.file_object.close()



# Map our class ContextManager implementation to the function expected of the
# securesystemslib.storage.StorageBackendInterface.get definition
get = GetFile
@contextmanager
def get(self, filepath:str) -> Iterator[BinaryIO]:
file_object = None
try:
file_object = open(filepath, 'rb')
yield file_object
except OSError:
raise exceptions.StorageError(
"Can't open %s" % filepath)
finally:
if file_object is not None:
file_object.close()


def put(self, fileobj, filepath):
def put(self, fileobj: IO, filepath: str) -> None:
# If we are passed an open file, seek to the beginning such that we are
# copying the entire contents
if not fileobj.closed:
Expand All @@ -237,28 +221,28 @@ def put(self, fileobj, filepath):
# and the operating system's buffers. os.fsync() should follow flush().
destination_file.flush()
os.fsync(destination_file.fileno())
except (OSError, IOError):
except OSError:
raise exceptions.StorageError(
"Can't write file %s" % filepath)


def remove(self, filepath):
def remove(self, filepath: str) -> None:
try:
os.remove(filepath)
except (FileNotFoundError, PermissionError, OSError): # pragma: no cover
raise exceptions.StorageError(
"Can't remove file %s" % filepath)


def getsize(self, filepath):
def getsize(self, filepath: str) -> int:
try:
return os.path.getsize(filepath)
except OSError:
raise exceptions.StorageError(
"Can't access file %s" % filepath)


def create_folder(self, filepath):
def create_folder(self, filepath: str) -> None:
try:
os.makedirs(filepath)
except OSError as e:
Expand All @@ -275,7 +259,7 @@ def create_folder(self, filepath):
"Can't create folder at %s" % filepath)


def list_folder(self, filepath):
def list_folder(self, filepath: str) -> List[str]:
try:
return os.listdir(filepath)
except FileNotFoundError:
Expand Down