ftrotta.pycolib package
common_tests
Write less test code; get more tests.
The module provides one main class, CallableTest, and the module
function get_test_path().
- class ftrotta.pycolib.common_tests.CallableTest
Bases:
objectBase class that provides facilities to test a single callable.
This class allows automated input type and value checking for any callable, being it:
a module function, or
an instance method, or
a class method, or
a class constructor.
Warning
A new test case is needed for every function/method. A single case can test only one function/method, the so-called “callable under test” (CUT).
The CUT is automatically tested for:
correct checking of input types (see
test_input_types());correct checking of input values (see
test_input_values());correct functioning of basic behaviour (see
test_basic_behaviour()).
A fixture callable_test_config method that returns a
CallableTestConfigmust be implemented in the derived test case, as shown in the following examples.Examples
>>> from apackage import amodule as mut >>> from ftrotta.pycolib.common_tests import ( >>> CallableTest, >>> MultipleTypeArg, OptionalArg, OptionalMultipleTypeArg >>> ) >>> from ftrotta.pycolib.log import get_configured_root_logger >>> >>> _logger = get_configured_root_logger() >>> >>> class TestModuleFunction(CallableTest): >>> >>> @classmethod >>> @pytest.fixture(scope="class") >>> def callable_test_config(cls): >>> config = CallableTestConfig( >>> callable_under_test=(mut.afunction,), # MIND THE COMMA! >>> default_arg_values_for_tests={ >>> 'str_or_int': 5, >>> 'f': 1.0, >>> 's': 'ciao', >>> 'd': {}, >>> 'int_or_dict': MultipleTypeArg(23, {'a': 23}), >>> 'optional': OptionalArg(1), >>> 'float_or_int_optional': >>> OptionalMultipleTypeArg(0.1, 12), >>> }, >>> wrong_value_lists = { >>> 'str_or_int': [-1], >>> 'f': [-1.0], >>> 's': [], # any string is valid >>> 'd': [], # any dictionary is valid >>> 'int_or_dict': [-1], >>> 'optional': [], # any int is valid, >>> 'float_or_int_optional': [], >>> }, >>> ) >>> return config >>> >>> # test_input_types is automatically provided by >>> # CallableTest >>> >>> # test_input_values is automatically provided by >>> # CallableTest >>> >>> # test_basic_behaviour is automatically provided by >>> # CallableTest
>>> class TestModuleFunctionWithOutput(CallableTest): >>> >>> @classmethod >>> @pytest.fixture(scope="class") >>> def callable_test_config(cls): >>> config = CallableTestConfig( >>> callable_under_test=(am.double_float,), >>> default_arg_values_for_tests={ >>> "value": 2. >>> }, >>> wrong_value_lists = { >>> "value": [] >>> }, >>> # By setting expected_output, the corresponding >>> # verification is triggered. This is applicable >>> # only in case of no multiple/optional arguments are >>> # are present. >>> expected_output=4.0, >>> ) >>> return config
>>> class TestInstanceMethod(CallableTest): >>> >>> @classmethod >>> @pytest.fixture(scope="class") >>> def callable_test_config(cls): >>> # The same as config above, but for the scope. >>> # Remark: function scope is used (rather than >>> # class) in order to have a fresh object for each >>> # test. >>> an_instance = mut.AClass() >>> config = CallableTestConfig( >>> callable_under_test=\ >>> (an_instance.a_method,), # MIND THE COMMA! >>> ... >>> ) >>> return config >>>
>>> class TestConstructor(CallableTest): >>> >>> @classmethod >>> @pytest.fixture(scope="class") >>> def callable_test_config(cls): >>> # The same as config above, but for the scope. >>> # Remark: function scope is used (rather than >>> # class) in order to have a fresh object for each >>> # test. >>> config = CallableTestConfig( >>> callable_under_test=\ >>> (mut.AClass,), # MIND THE COMMA! >>> ... >>> ) >>> return config >>>
>>> class TestClassMethod(CallableTest): >>> >>> @classmethod >>> @pytest.fixture(scope="class") >>> def callable_test_config(cls): >>> # The same as config above, but for the scope. >>> # Remark: function scope is used (rather than >>> # class) in order to have a fresh object for each >>> # test. >>> config = CallableTestConfig( >>> callable_under_test=\ >>> (mut.AClass.a_class_method,), # MIND THE COMMA! >>> ... >>> ) >>> return config
- classmethod get_some_types() List
- Returns
List of items with different types for
test_input_types().It can be overridden by the child class, to provide different types to check against.
Examples
>>> @classmethod >>> def get_some_types(self): >>> l = CallableTest.get_some_types() >>> l.append(np.zeros([2, 2])) >>> return l
- test_basic_behaviour(callable_test_config: ftrotta.pycolib.common_tests.CallableTestConfig)
Check that no exception is raised when the callable is invoked with inputs defined in
default_arg_values_for_tests.Warning
In case
OptionalArgorMultipleTypeArgorOptionalMultipleTypeArgare used, this leads to multiple effective inputs to be checked. In doing so, no copies of the inputs are created. This means that any side effect of the callable on any of its inputs could impair the test.If your callable under test has side effects that could influence the output, please: (1) skip this test by setting
skip_test_basic_behaviourto True; and (2) perform custom behavioural testing, by implementing your test functions.If
CallableTestConfig.expected_outputis not None, output checking is triggered.
- test_input_types(callable_test_config: ftrotta.pycolib.common_tests.CallableTestConfig)
Check that the callable raises a
TypeErrorwhen an input with type different from that indefault_arg_values_for_testsis given.The values that are used as wrong type values can be managed in
get_some_types().
- test_input_values(callable_test_config: ftrotta.pycolib.common_tests.CallableTestConfig)
Check that the callable raises a
ValueErrorwhen an input with value defined inwrong_value_listsis given.
- class ftrotta.pycolib.common_tests.CallableTestConfig(*, callable_under_test: Tuple[Callable], default_arg_values_for_tests: Dict[str, Any], wrong_value_lists: Dict[str, List], expected_output: Optional[Any] = None, skip_test_basic_behaviour: bool = False)
Bases:
objectConfiguration for test callable classes.
Please refer to
CallableTest.- Parameters
callable_under_test – Mind the syntax of the tuple:
(callable,).default_arg_values_for_tests –
wrong_value_lists –
expected_output – defaults to None.
skip_test_basic_behaviour – defaults to False.
- callable_under_test: Tuple[Callable]
The callable under test.
Mind the syntax of the tuple:
(callable,).See also the examples of
CallableTest.
- default_arg_values_for_tests: Dict[str, Any]
Every argument of the callable needs to be defined.
OptionalArg,MultipleTypeArgandOptionalMultipleTypeArgcan be used.See also the examples of
CallableTest.
- expected_output: Optional[Any]
See also
CallableTest.test_basic_behaviour().
- skip_test_basic_behaviour: bool
See also
CallableTest.test_basic_behaviour().
- wrong_value_lists: Dict[str, List]
See also the examples of
CallableTest.
- exception ftrotta.pycolib.common_tests.CallableUnderTestNotTupleError
- exception ftrotta.pycolib.common_tests.CommonTestsException
Bases:
ExceptionBase class for exceptions.
- exception ftrotta.pycolib.common_tests.MissingArgumentError
- exception ftrotta.pycolib.common_tests.MissingConfigurationError
- class ftrotta.pycolib.common_tests.MultipleTypeArg(*args)
Bases:
object
- exception ftrotta.pycolib.common_tests.NotCallableError
- class ftrotta.pycolib.common_tests.OptionalArg(arg=None)
Bases:
object
- class ftrotta.pycolib.common_tests.OptionalMultipleTypeArg(*args)
Bases:
object
- ftrotta.pycolib.common_tests.get_test_path(module_name: str, relative_path: Union[str, pathlib.Path]) Union[str, pathlib.Path]
Computes paths for testing.
When running tests from the IDE, the working directory changes. This makes it difficult to define paths to files/directories, without knowing the project path, as it is the case when running in different environments.
This function allows to define a path that will work for unittest and pytest regardless of the current working directory.
- Parameters
module_name – As obtained by __name__.
relative_path – The path to file/dir relative to the test definition file.
- Returns
The absolute path to file/dir. It has the same type of relative_path.
Examples
>>> import os >>> from typing import Union >>> from pathlib import Path >>> from ftrotta.pycolib.common_tests import get_test_path >>> >>> >>> def gtp(relative_path: Union[str, Path]) -> Union[str, Path]: >>> return get_test_path(__name__, relative_path) >>> >>> >>> def test(self): >>> fname = gtp(Path('files/')) / 'foo.png' >>> fname = gtp('files/foo.png') >>>
input_checks
A module with facilities to check the type and range of input parameters.
- ftrotta.pycolib.input_checks.check_dict_has_key(par: Any, par_name: str, key: Any) None
Check par to be a dictionary that includes a given key.
- Parameters
par –
par_name –
key –
- Raises
TypeError – if par type is not
dict.ValueError – if par has not the key.
- ftrotta.pycolib.input_checks.check_dict_has_key_with_type(par: Any, par_name: str, key: Any, key_type: Type) None
Check par to be a dictionary that includes a given key of a given type.
- Parameters
par –
par_name –
key –
key_type –
- Raises
TypeError – if par type is not
dict.ValueError – if par has not the key or the key type is not key_type.
- ftrotta.pycolib.input_checks.check_homogeneous_list(par: Any, par_name: str, expected_type: Type) None
Check that all elements have same type.
- Parameters
par –
par_name –
expected_type –
- Raises
TypeError – if par is not a list.
ValueError – if at least one element has not type expected_type.
- ftrotta.pycolib.input_checks.check_in_range(par: Any, par_name: str, min_value: Any, max_value: Any, include_min: bool, include_max: bool) None
Check par to be within (min_value, max_value).
- Parameters
par – Same type as value_min.
par_name –
min_value – Same type as value_max, less than it.
max_value – Same type as value_min, grater than it.
include_min –
include_max –
- Raises
AssertionError – If input pars different from par do not have the right type/value.
TypeError – If not isinstance(par, type(min_value)).
ValueError – If par is not within the range.
- ftrotta.pycolib.input_checks.check_isdir(par: Any, par_name: str) None
Checks par to be a
pathlib.Pathand an existing directory.- Parameters
par (any) –
par_name (str) –
- Raises
TypeError – if par type is not pathlib.Path.
ValueError – if par value is not an existing directory.
- ftrotta.pycolib.input_checks.check_isfile(par: Any, par_name: str) None
Checks par to be a
pathlib.Pathand an existing file.- Parameters
par –
par_name –
- Raises
TypeError – if par type is not :class:`pathlib.Path.
ValueError – if par value is not an existing file.
- ftrotta.pycolib.input_checks.check_multiple_type(par: Any, par_name: str, expected_types: Tuple[Type, ...]) None
Check the parameter against a list of possible types.
Examples
>>> from ftrotta.pycolib import input_checks as ics >>> >>> def f(a): >>> ics.check_multiple_type(a, 'a', (int, double)) >>> pass
- Parameters
par –
par_name –
expected_types –
- Raises
TypeError –
- ftrotta.pycolib.input_checks.check_ndarray_with_type(par: Any, par_name: str, np_type: numpy.dtype) None
Check that par is a ndarray whose type is np_type.
- Parameters
par –
par_name –
np_type –
- Raises
TypeError – if par type is not ndarray.
ValueError – if the type of the elements of par is not np_type.
- ftrotta.pycolib.input_checks.check_noneable_multiple_type(par: Any, par_name: str, expected_type: Tuple[Type, ...]) None
Check the parameter against a tuple of possible types, if not None.
Examples
>>> from ftrotta.pycolib import input_checks as ics >>> >>> def f(a): >>> ics.check_multiple_type(a, 'a', (int, double)) >>> pass
- Parameters
par –
par_name –
expected_type –
- Raises
TypeError –
- ftrotta.pycolib.input_checks.check_noneable_type(par: Any, par_name: str, expected_type: Type) None
Check the type of par, if it is not None.
- Parameters
par –
par_name –
expected_type –
- Raises
TypeError –
- ftrotta.pycolib.input_checks.check_nonempty_str(par: Any, par_name: str) None
- Parameters
par (str) –
par_name (str) –
- Raises
TypeError – if par is not a string.
ValueError – if par is an empty string.
- ftrotta.pycolib.input_checks.check_nonnegativefloat(par: Any, par_name: str) None
Check par to be float and non-negative.
- Parameters
par –
par_name –
- Raises
TypeError – if par type is not float.
ValueError – if par value is not non-negative.
- ftrotta.pycolib.input_checks.check_nonnegativeint(par: Any, par_name: str) None
Check par to be int and non-negative.
- Parameters
par –
par_name –
- Raises
TypeError – if par type is not int.
ValueError – if par value is not non-negative.
- ftrotta.pycolib.input_checks.check_positivefloat(par: Any, par_name: str) None
Check par to be float and positive.
- Parameters
par –
par_name –
- Raises
TypeError – if par type if not float.
ValueError – if par value is not positive.
- ftrotta.pycolib.input_checks.check_positiveint(par: Any, par_name: str) None
Checks par to be int and positive.
- Parameters
par (any) –
par_name (str) –
- Raises
TypeError – if par type if not
int.ValueError – if par value is not positive.
- ftrotta.pycolib.input_checks.check_tuple_with_len(par: Any, par_name: str, length: int) None
Check par to be a tuple with given length.
- Parameters
par –
par_name –
length – positive.
- Raises
AssertionError – if length is not positive int.
TypeError – if par is not a tuple.
ValueError – if len(par) != length.
- ftrotta.pycolib.input_checks.check_type(par: Any, par_name: str, expected_type: Type) None
Check the parameters against a single type.
Examples
>>> from ftrotta.pycolib import input_checks as ics >>> >>> def f(a): >>> ics.check_type(a, 'a', int) >>> pass
- Parameters
par –
par_name –
expected_type –
- Raises
TypeError –
log
Logging facilities.
The name prevents conflicts with default logging package.
- ftrotta.pycolib.log.get_configured_root_logger(level: int = 10) logging.Logger
Get the root logger with configured handler and formatter.
It can be used in any top level module, to handle logging on stderr. The stream handler on stderr is added only in case the root logger has no other handlers. This prevents logging message repetitions when used in test modules.
Example
>>> from ftrotta.pycolib.log import get_configured_root_logger >>> >>> _logger = get_configured_root_logger() >>> >>> _logger.debug('Debug message')
- Parameters
level – The logging level (set to the handler). Defaults to logging.DEBUG.
- Returns
The root logger, with configured handler on stderr and formatter.
setup
Module with facilities to simplify the managments of setup.py.
The idea is to write as few code as possible, extensively relaying on automation, to allow for easy refactoring of your projects.
PEP 518 compliancy is not relevant at the moment.
Examples
>>> # setup.py
>>> from setuptools import setup
>>> from ftrotta.pycolib.setup import infer_package_info
>>>
>>> SRC_PATH = 'src/'
>>>
>>> name, project_urls, packages = infer_package_info(
>>> where=SRC_PATH, group='sbrin', rtfd=True)
>>>
>>> with open('README.md', 'r', encoding='utf-8') as fh:
>>> long_description = fh.read()
>>>
>>> install_requires = get_install_requires('requirements.txt', 'gitlab')
>>>
>>> setup(
>>> name=name,
>>> use_scm_version=True,
>>> project_urls=project_urls,
>>> author='Sergey Brin',
>>> description='A new Internet serach engine.',
>>> long_description=long_description,
>>> long_description_content_type='text/markdown',
>>> package_dir={'': SRC_PATH},
>>> packages=packages,
>>> setup_requires=[
>>> 'setuptools_scm',
>>> ],
>>> install_requires=install_requires,
>>> classifiers=[
>>> 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
>>> 'Operating System :: OS Independent',
>>> 'Programming Langusphinx-build -W . _build/html/age :: Python :: 3',
>>> ],
>>> python_requires='>=3.6',
>>> )
- ftrotta.pycolib.setup.get_install_requires(fname: str, schema: str) List[str]
Get the install_requires list from requirements.txt file.
Warning
This function is deprecated since v1.2.2 as there is no motivation for it. The syntax package@git+https://gitlab.com/group/project.git is, indeed, accepted by pip as well.
The main goal is to manage the git+ requirements, such as git+https://gitlab.com/foo/bar.git. Such a requirement is accepted in requirements.txt, but not in `install_requires of setup.py. For this to happen it must be converted to <package>@git+<url>. Please refer to https://stackoverflow.com/questions/55385900/pip3-setup-py-install-requires-pep-508-git-url-for-private-repo.
The function also parses files included via the ‘-r’ syntax. It ignores the comment and empty lines.
- Parameters
fname –
schema – only ‘gitlab’ is accepted. It converts git+https://gitlab.com/foo/bar.git to foo.bar@git+https://gitlab.com/foo/bar.git.
- Returns
The list of requirements to be used as install_requires in setup.py.
- ftrotta.pycolib.setup.infer_package_info(where: str, group: str, rtfd: bool) Tuple[str, Dict[str, str], List[str]]
Infer package information from the filesystem.
It assumes that there is only one main package, that is the object of the development of the repository.
Such project package is assumed to be second-level subpackage. In particular a structure like <group>.<project_package> is assumed. This complies with PEP423 Use single name.
The first level package, namely <group> represents the author or the organization. It can a namespace package, in the form of either a native namespace package, that has no __init__.py file in it, or a pkgutil-style namespace package. Please recall that the strategy cannot be changed and all projects need to share the same method for the namespace package.
- Parameters
where – Path where to look for packages, the source directory.
group – The name of the author or organization.
rtfd – Whether the documentation is hosted in ReadTheDocs. Alternatively, it is considered to be hosted in the Gitlab Pages of the project.
- Returns
(project_package, project_urls, pkg_list)
project_package: The name of the main package. It can be used for the name parameter of setup.py.
project_urls: The Source Code and Documentation URLs. A project hosted in Gitlab.com is assumed.
pkg_list: The list of packages to be installed.