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

Version 1.1.2 #22

Merged
merged 14 commits into from
Nov 21, 2023
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
2 changes: 1 addition & 1 deletion acacore/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.1.1"
__version__ = "1.1.2"
4 changes: 2 additions & 2 deletions acacore/reference_files/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from .ref_files import get_actions
from .ref_files import get_custom_signatures
from .get import get_actions
from .get import get_custom_signatures
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from functools import lru_cache
from http.client import HTTPException
from http.client import HTTPResponse
from urllib import request
from urllib.error import HTTPError
from urllib.request import urlopen

from pydantic import TypeAdapter
from yaml import load
Expand All @@ -10,26 +10,25 @@
from acacore.models.reference_files import Action
from acacore.models.reference_files import CustomSignature

actions_url: str = "https://raw.githubusercontent.com/aarhusstadsarkiv/reference-files/main/fileformats.yml"
custom_signatures_url: str = (
"https://raw.githubusercontent.com/aarhusstadsarkiv/reference-files/main/custom_signatures.json"
)
download_url: str = "https://github.com/aarhusstadsarkiv/reference-files/releases/latest/download/"
actions_file: str = "fileformats.yml"
custom_signatures_file: str = "custom_signatures.json"


@lru_cache
def _get_actions() -> dict[str, Action]:
response: HTTPResponse = request.urlopen(actions_url)
def _get_actions(url: str) -> dict[str, Action]:
response: HTTPResponse = urlopen(url)
if response.getcode() != 200:
raise HTTPException(response.getcode())
raise HTTPError(url, response.getcode(), "", response.headers, response)

return TypeAdapter(dict[str, Action]).validate_python(load(response.read(), Loader))


@lru_cache
def _get_custom_signatures() -> list[CustomSignature]:
response: HTTPResponse = request.urlopen(custom_signatures_url)
def _get_custom_signatures(url: str) -> list[CustomSignature]:
response: HTTPResponse = urlopen(url)
if response.getcode() != 200:
raise HTTPException(response.getcode())
raise HTTPError(url, response.getcode(), "", response.headers, response)

return TypeAdapter(list[CustomSignature]).validate_json(response.read())

Expand All @@ -46,10 +45,17 @@ def get_actions(use_cache: bool = True) -> dict[str, Action]:
Returns:
dict[str, Action]: A dictionary with PUID keys and Action values.

Raises:
urllib.error.HTTPError: If there is an issue with the request.

See Also:
https://github.com/aarhusstadsarkiv/reference-files/blob/main/actions.yml
"""
return _get_actions() if use_cache else _get_actions.__wrapped__()
return (
_get_actions(f"{download_url.rstrip('/')}/{actions_file.lstrip('/')}")
if use_cache
else _get_actions.__wrapped__()
)


def get_custom_signatures(use_cache: bool = True) -> list[CustomSignature]:
Expand All @@ -62,9 +68,16 @@ def get_custom_signatures(use_cache: bool = True) -> list[CustomSignature]:
use_cache (bool): Use cached data if True, otherwise fetch data regardless of cache status

Returns:
list[CustomSignature]: A list of CustomSignature objects
list[CustomSignature]: A list of CustomSignature objects.

Raises:
urllib.error.HTTPError: If there is an issue with the request.

See Also:
https://github.com/aarhusstadsarkiv/reference-files/blob/main/custom_signatures.json
"""
return _get_custom_signatures() if use_cache else _get_custom_signatures.__wrapped__()
return (
_get_custom_signatures(f"{download_url.rstrip('/')}/{custom_signatures_file.lstrip('/')}")
if use_cache
else _get_custom_signatures.__wrapped__()
)
18 changes: 15 additions & 3 deletions acacore/utils/helpers.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
from types import TracebackType
from typing import Optional
from typing import Sequence
from typing import Type


class ExceptionManager:
"""
A context manager class that catches specified exceptions and stores the exception and traceback for later use.

Exceptions whose class is explicitly declared in the 'catch' argument are always caught,
even if they subclass from classes passed int the 'allow' argument.

Args:
*catch (Type[BaseException]): Exception types that should be caught and not allowed to rise.

Attributes:
exception (Optional[BaseException]): The exception that was raised within the context, if any.
traceback (Optional[TracebackType]): The traceback associated with the exception, if any.
catch (tuple[Type[BaseException], ...]): Tuple of exceptions that should be caught instead of letting them rise.
allow (tuple[Type[BaseException], ...]): Tuple of exceptions that should be allowed to rise.
"""

__slots__ = ("exception", "traceback", "catch")
__slots__ = ("exception", "traceback", "catch", "allow")

def __init__(self, *catch: Type[BaseException]) -> None:
def __init__(self, *catch: Type[BaseException], allow: Optional[Sequence[Type[BaseException]]] = None) -> None:
self.exception: Optional[BaseException] = None
self.traceback: Optional[TracebackType] = None
self.catch: tuple[Type[BaseException], ...] = catch
self.allow: tuple[Type[BaseException], ...] = tuple(allow or [])

def __enter__(self) -> "ExceptionManager":
return self
Expand All @@ -34,4 +40,10 @@ def __exit__(
) -> bool:
self.exception = exc_val
self.traceback = exc_tb
return any(issubclass(exc_type, e) for e in self.catch) if exc_type else False

if not exc_type:
return False

return any(issubclass(exc_type, e) for e in self.catch) and (
exc_type in self.catch or not any(issubclass(exc_type, e) for e in self.allow)
)
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "acacore"
version = "1.1.1"
version = "1.1.2"
description = ""
authors = ["Matteo Campinoti <[email protected]>"]
license = "GPL-3.0"
Expand Down
19 changes: 15 additions & 4 deletions tests/test_reference_files.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
from acacore.reference_files import get_actions
from acacore.reference_files import get_custom_signatures
from urllib.error import HTTPError

import pytest

import acacore.reference_files as reference_files


def test_actions():
assert get_actions()
assert reference_files.get_actions()
with pytest.raises(HTTPError) as error:
reference_files.get.actions_file = f"wrong/path/{reference_files.get.actions_file}"
reference_files.get_actions()
assert error.value.code == 404


def test_custom_signatures():
assert get_custom_signatures()
assert reference_files.get_custom_signatures()
with pytest.raises(HTTPError) as error:
reference_files.get.custom_signatures_file = f"wrong/path/{reference_files.get.actions_file}"
reference_files.get_custom_signatures()
assert error.value.code == 404
19 changes: 18 additions & 1 deletion tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,30 @@ def test_helpers():
assert context.exception.code == 3
assert context.traceback is not None

with pytest.raises(KeyboardInterrupt) as raises, ExceptionManager(Exception) as context:
with (
pytest.raises(KeyboardInterrupt) as raises,
ExceptionManager(Exception) as context,
):
raise KeyboardInterrupt

assert isinstance(raises.value, KeyboardInterrupt)
assert isinstance(context.exception, KeyboardInterrupt)
assert context.traceback is not None

with (
pytest.raises(OSError) as raises, # noqa: PT011
ExceptionManager(BaseException, allow=[OSError]) as context,
):
raise FileNotFoundError

assert isinstance(raises.value, FileNotFoundError)
assert isinstance(context.exception, FileNotFoundError)

with ExceptionManager(BaseException, FileNotFoundError, allow=[OSError]) as context:
raise FileNotFoundError

assert isinstance(context.exception, FileNotFoundError)

with ExceptionManager() as context:
pass

Expand Down
Loading