Skip to content
Snippets Groups Projects
Commit ef24d9cf authored by Piotr Maślanka's avatar Piotr Maślanka
Browse files

add returns to silence_excs

parent b1815c0a
No related branches found
No related tags found
No related merge requests found
......@@ -2,6 +2,10 @@
* `LockedStructure` is now generic
* added `returns` to `rethrow_as`
* patched `DictObject` to inherit from `UserDict`
instead of `dict`
* imports will log it's problem via warning, and now
to the log anymore
# v2.5.12
......
......@@ -13,6 +13,8 @@ Satella is an almost-zero-requirements Python 3.5+ library for writing
server applications, especially those dealing with mundane but
useful things. It also runs on PyPy.
Satella uses [semantic versioning 2.0](https://semver.org/spec/v2.0.0.html).
Satella contains, among other things:
* things to help you manage your [application's configuration](satella/configuration)
......
import typing as tp
import threading
from .decorators import wraps
__all__ = [
......@@ -48,14 +48,6 @@ class rethrow_as:
>>> rethrow_as((NameError, ValueError), (OSError, IOError))
If the second value is a None, exception will be silenced.
If you are using it as a decorator, you can specify what value should the function return
by using the returns kwarg:
>>> @rethrow_as(KeyError, None, returns=5)
>>> def returns_5():
>>> raise KeyError()
>>> assert returns_5() == 5
"""
__slots__ = ('mapping', 'exception_preprocessor', 'returns', '__exception_remapped')
......@@ -83,14 +75,18 @@ class rethrow_as:
self.mapping = list(pairs)
self.exception_preprocessor = exception_preprocessor or repr
self.returns = returns
self.__exception_remapped = False
def __call__(self, fun: tp.Callable) -> tp.Any:
# this is threading.local because two threads may execute the same function at the
# same time, and exceptions from one function would leak to another
self.__exception_remapped = threading.local()
def __call__(self, fun: tp.Callable) -> tp.Callable:
@wraps(fun)
def inner(*args, **kwargs):
with self:
v = fun(*args, **kwargs)
if self.__exception_remapped:
if self.__exception_remapped.was_raised:
# This means that the normal flow of execution was interrupted
return self.returns
else:
return v
......@@ -98,13 +94,14 @@ class rethrow_as:
return inner
def __enter__(self):
self.__exception_remapped.was_raised = False
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is not None:
for from_, to in self.mapping:
if issubclass(exc_type, from_):
self.__exception_remapped = True
self.__exception_remapped.was_raised = True
if to is None:
return True
else:
......
import collections.abc
import collections
import copy
import typing as tp
from satella.coding.recast_exceptions import rethrow_as
from satella.configuration.schema import Descriptor, descriptor_from_dict
from satella.exceptions import ConfigurationValidationError
from ..decorators import for_argument
__all__ = ['DictObject', 'apply_dict_object', 'DictionaryView', 'TwoWayDictionary']
K, V, T = tp.TypeVar('K'), tp.TypeVar('V'), tp.TypeVar('T')
class DictObject(dict, tp.Generic[T]):
class DictObject(tp.MutableMapping[str, T]):
"""
A dictionary wrapper that can be accessed by attributes.
......@@ -25,18 +25,48 @@ class DictObject(dict, tp.Generic[T]):
>>> self.assertEqual(a.test, 5)
"""
def __init__(self, *args, **kwargs):
self.__data = dict(*args, **kwargs)
def __delitem__(self, k: str) -> None:
del self.__data[k]
def __setitem__(self, k: str, v: T) -> None:
self.__data[k] = v
def __getitem__(self, item: str) -> T:
return self.__data[item]
def __iter__(self) -> tp.Iterator[str]:
return iter(self.__data)
def __len__(self) -> int:
return len(self.__data)
def __copy__(self) -> 'DictObject':
return DictObject(copy.copy(dict(self)))
return DictObject(self.__data.copy())
def __eq__(self, other: dict):
if isinstance(other, DictObject):
return self.__data == other.__data
else:
return self.__data == other
def copy(self) -> 'DictObject':
return DictObject(self.__data.copy())
def __deepcopy__(self, memodict={}) -> 'DictObject':
return DictObject(copy.deepcopy(dict(self), memo=memodict))
return DictObject(copy.deepcopy(self.__data, memo=memodict))
@rethrow_as(KeyError, AttributeError)
def __getattr__(self, item: str) -> T:
return self[item]
def __setattr__(self, key: str, value: T) -> None:
self[key] = value
if key == '_DictObject__data':
return super().__setattr__(key, value)
else:
self[key] = value
@rethrow_as(KeyError, AttributeError)
def __delattr__(self, key: str) -> None:
......@@ -63,7 +93,7 @@ class DictObject(dict, tp.Generic[T]):
descriptor = descriptor_from_dict(schema)
try:
descriptor(self)
descriptor(self.__data)
except ConfigurationValidationError:
return False
else:
......@@ -123,27 +153,6 @@ class DictionaryView(collections.abc.MutableMapping, tp.Generic[K, V]):
self.dictionaries = [master_dict, *rest_of_dicts]
self.propagate_deletes = propagate_deletes
@for_argument(returns=list)
def keys(self) -> tp.AbstractSet[K]:
"""
Returns all keys found in this view
"""
seen_already = set()
for dictionary in self.dictionaries:
for key in dictionary:
if key not in seen_already:
yield key
seen_already.add(key)
@for_argument(returns=list)
def values(self) -> tp.AbstractSet[V]:
seen_already = set()
for dictionary in self.dictionaries:
for key, value in dictionary.items():
if key not in seen_already:
yield value
seen_already.add(key)
def __contains__(self, item: K) -> bool:
for dictionary in self.dictionaries:
if item in dictionary:
......@@ -158,15 +167,6 @@ class DictionaryView(collections.abc.MutableMapping, tp.Generic[K, V]):
yield key
seen_already.add(key)
@for_argument(returns=list)
def items(self) -> tp.AbstractSet[tp.Tuple[K, V]]:
seen_already = set()
for dictionary in self.dictionaries:
for key, value in dictionary.items():
if key not in seen_already:
yield key, value
seen_already.add(key)
def __len__(self) -> int:
seen_already = set()
i = 0
......
import importlib
import logging
import os
import pkgutil
import warnings
import typing as tp
__all__ = ['import_from', 'import_class']
logger = logging.getLogger(__name__)
def import_class(path: str) -> type:
"""
......@@ -76,8 +74,8 @@ def import_from(path: tp.List[str], package_prefix: str, all_: tp.List[str],
try:
package_ref = module.__all__
except AttributeError:
logger.warning('Module %s does not contain __all__, enumerating it instead',
package_prefix + '.' + modname)
warnings.warn('Module %s does not contain __all__, enumerating it instead' %
(package_prefix + '.' + modname, ), RuntimeWarning)
package_ref = dir(module)
for item in package_ref:
......
......@@ -2,6 +2,7 @@ import logging
import unittest
from satella.imports import import_class
import subprocess
import warnings
logger = logging.getLogger(__name__)
......@@ -9,7 +10,9 @@ logger = logging.getLogger(__name__)
class TestImports(unittest.TestCase):
def test_imports(self):
import tests.test_imports.importa
tests.test_imports.importa.do_import()
with warnings.catch_warnings() as warns:
tests.test_imports.importa.do_import()
self.assertGreater(len(warns), 0)
# this as well checks for the namespace's pollution
self.assertEqual(set(tests.test_imports.importa.importb.__all__),
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment