Skip to content

Commit

Permalink
Merge pull request #30 from Lyncs-API/develop
Browse files Browse the repository at this point in the history
Version 0.5.1
  • Loading branch information
sbacchio committed Mar 23, 2024
2 parents 4f1678f + 1bdd12a commit 2c72edb
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 124 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[![license](https://img.shields.io/github/license/Lyncs-API/lyncs.utils?logo=github&logoColor=white)](https://github.com/Lyncs-API/lyncs.utils/blob/master/LICENSE)
[![build & test](https://img.shields.io/github/actions/workflow/status/Lyncs-API/lyncs.utils/ci_cd.yml?logo=github&logoColor=white&branch=master)](https://github.com/Lyncs-API/lyncs.utils/actions)
[![codecov](https://img.shields.io/codecov/c/github/Lyncs-API/lyncs.utils?logo=codecov&logoColor=white)](https://codecov.io/gh/Lyncs-API/lyncs.utils)
[![pylint](https://img.shields.io/badge/pylint%20score-9.6%2F10-green?logo=python&logoColor=white)](http://pylint.pycqa.org/)
[![pylint](https://img.shields.io/badge/pylint%20score-9.4%2F10-green?logo=python&logoColor=white)](http://pylint.pycqa.org/)
[![black](https://img.shields.io/badge/code%20style-black-000000.svg?logo=codefactor&logoColor=white)](https://github.com/ambv/black)

This package provides a collection of generic-purpose and stand-alone functions that are of common use.
Expand Down
2 changes: 1 addition & 1 deletion lyncs_utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"Collection of generic-purpose and stand-alone functions"

__version__ = "0.5.0"
__version__ = "0.5.1"

from .math import *
from .logical import *
Expand Down
232 changes: 110 additions & 122 deletions lyncs_utils/pytest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@
import pytest


def is_dyn_param(val):
"Checks if is DynParam"
return isinstance(val, DynParam)


@dataclass
class DynParam:
"""
An object that allows to generate test parameters dynamically.
"""
"""An object that allows to generate test parameters dynamically."""

arg: None
ids: callable = lambda val: str(val)
Expand All @@ -25,9 +28,7 @@ def __call__(self, test):

@dataclass
class GetMark(DynParam):
"""
Takes a dictionary as input and returns the value corresponding to the first matching mark.
"""
"""Takes a dictionary as input and returns the value corresponding to the first matching mark."""

default: str = None

Expand All @@ -43,45 +44,19 @@ def __call__(self, test):
return tuple(out)


@pytest.hookimpl(hookwrapper=True)
def pytest_generate_tests(metafunc):
"Runs normalize_call for all calls"
yield
def normalize_dyn_param(callspec, metafunc, used_keys, idx, key, val):
"""Replaces DynParam with its output"""
ids = val.ids
test = metafunc.function
vals = val(test)
newcalls = []
for callspec in metafunc._calls:
calls = normalize_call(callspec, metafunc.function)
for val in vals:
newcallspec = copy_callspec(callspec)
newcallspec.params[key] = val
newcallspec._idlist[idx] = ids(val)
calls = normalize_call(newcallspec, metafunc)
newcalls.extend(calls)
metafunc._calls = newcalls


def normalize_call(callspec, test):
"Replaces DynParam with its output"
for idx, (key, val) in enumerate(callspec.params.items()):
if is_dyn_param(val):
ids = val.ids
vals = val(test)
newcalls = []
for val in vals:
newcallspec = copy_callspec(callspec)
newcallspec.params[key] = val
newcallspec._idlist[idx] = ids(val)
calls = normalize_call(newcallspec, test)
newcalls.extend(calls)
return newcalls
return [callspec]


def copy_callspec(callspec):
"Creating a copy of callspec"
new = copy(callspec)
object.__setattr__(new, "params", copy(callspec.params))
object.__setattr__(new, "_idlist", copy(callspec._idlist))
return new


def is_dyn_param(val):
"Checks if is DynParam"
return isinstance(val, DynParam)
return newcalls


@dataclass
Expand All @@ -92,95 +67,108 @@ class LazyFixture:


def lazy_fixture(name: str) -> LazyFixture:
"""Mark a fixture as lazy.
Credit:
- https://github.com/TvoroG/pytest-lazy-fixture/issues/65#issuecomment-1914581161
"""
"""Mark a fixture as lazy."""
return LazyFixture(name)


def is_lazy_fixture(value: object) -> bool:
"""Check whether a value is a lazy fixture.
Credit:
- https://github.com/TvoroG/pytest-lazy-fixture/issues/65#issuecomment-1914581161
"""
"""Check whether a value is a lazy fixture."""
return isinstance(value, LazyFixture)


def pytest_make_parametrize_id(
config: pytest.Config,
val: object,
argname: str,
) -> (str, None):
"""Inject lazy fixture parametrized id.
def normalize_lazy_fixture(callspec, metafunc, used_keys, idx, key, val):
"Replaces LazyFixture with its output"
fm = metafunc.config.pluginmanager.get_plugin("funcmanage")
try:
if pytest.version_tuple >= (8, 0, 0):
fixturenames_closure, arg2fixturedefs = fm.getfixtureclosure(
metafunc.definition.parent, [val.name], {}
)
else:
_, fixturenames_closure, arg2fixturedefs = fm.getfixtureclosure(
[val.name], metafunc.definition.parent
)
except ValueError:
# 3.6.0 <= pytest < 3.7.0; `FixtureManager.getfixtureclosure` returns 2 values
fixturenames_closure, arg2fixturedefs = fm.getfixtureclosure(
[val.name], metafunc.definition.parent
)
except AttributeError:
# pytest < 3.6.0; `Metafunc` has no `definition` attribute
fixturenames_closure, arg2fixturedefs = fm.getfixtureclosure(
[val.name], current_node
)

extra_fixturenames = [
fname for fname in fixturenames_closure if fname not in callspec.params
] # and fname not in callspec.funcargs]

newmetafunc = copy_metafunc(metafunc)
newmetafunc.fixturenames = extra_fixturenames
newmetafunc._arg2fixturedefs.update(arg2fixturedefs)
newmetafunc._calls = [callspec]
fm.pytest_generate_tests(newmetafunc)
normalize_metafunc_calls(newmetafunc, used_keys)
return newmetafunc._calls

Reference:
- https://bit.ly/48Off6r

Args:
config (pytest.Config): pytest configuration.
value (object): fixture value.
argname (str): automatic parameter name.
@pytest.hookimpl(tryfirst=True)
def pytest_fixture_setup(fixturedef, request):
"""Replaces LazyFixture with its name"""
val = getattr(request, "param", None)
if is_lazy_fixture(val):
request.param = request.getfixturevalue(val.name)

Returns:
str: new parameter id.

Credit:
- https://github.com/TvoroG/pytest-lazy-fixture/issues/65#issuecomment-1914581161
"""
if is_lazy_fixture(val):
return typing.cast(LazyFixture, val).name
return None
@pytest.hookimpl(hookwrapper=True)
def pytest_generate_tests(metafunc):
"""Runs normalize_call for all calls"""
yield
normalize_metafunc_calls(metafunc)


@pytest.hookimpl(tryfirst=True)
def pytest_fixture_setup(
fixturedef: pytest.FixtureDef,
request: pytest.FixtureRequest,
) -> (object, None):
"""Lazy fixture setup hook.
This hook will never take over a fixture setup but just simply will
try to resolve recursively any lazy fixture found in request.param.
Reference:
- https://bit.ly/3SyvsXJ
Args:
fixturedef (pytest.FixtureDef): fixture definition object.
request (pytest.FixtureRequest): fixture request object.
Returns:
object | None: fixture value or None otherwise.
Credit:
- https://github.com/TvoroG/pytest-lazy-fixture/issues/65#issuecomment-1914581161
"""
if hasattr(request, "param") and request.param:
request.param = _resolve_lazy_fixture(request.param, request)
return None


def _resolve_lazy_fixture(__val: object, request: pytest.FixtureRequest) -> object:
"""Lazy fixture resolver.
Args:
__val (object): fixture value object.
request (pytest.FixtureRequest): pytest fixture request object.
Returns:
object: resolved fixture value.
Credit:
- https://github.com/TvoroG/pytest-lazy-fixture/issues/65#issuecomment-1914581161
"""
if isinstance(__val, (list, tuple)):
return tuple(_resolve_lazy_fixture(v, request) for v in __val)
if isinstance(__val, typing.Mapping):
return {k: _resolve_lazy_fixture(v, request) for k, v in __val.items()}
if not is_lazy_fixture(__val):
return __val
lazy_obj = typing.cast(LazyFixture, __val)
return request.getfixturevalue(lazy_obj.name)
def normalize_metafunc_calls(metafunc, used_keys=None):
"""Runs normalize_call for all calls"""
newcalls = []
for callspec in metafunc._calls:
calls = normalize_call(callspec, metafunc, used_keys)
newcalls.extend(calls)
metafunc._calls = newcalls


def normalize_call(callspec, metafunc, used_keys=None):
"Replaces special fixtures with their output"
used_keys = used_keys or set()
for idx, (key, val) in enumerate(callspec.params.items()):
if key in used_keys:
continue
used_keys.add(key)
if is_dyn_param(val):
return normalize_dyn_param(callspec, metafunc, used_keys, idx, key, val)
if is_lazy_fixture(val):
return normalize_lazy_fixture(callspec, metafunc, used_keys, idx, key, val)
return [callspec]


def copy_callspec(callspec):
"""Creates a copy of callspec"""
new = copy(callspec)
object.__setattr__(new, "params", copy(callspec.params))
object.__setattr__(new, "_idlist", copy(callspec._idlist))
return new


def copy_metafunc(metafunc):
"""Creates a copy of metafunc"""
copied = copy(metafunc)
copied.fixturenames = copy(metafunc.fixturenames)
copied._calls = []

try:
copied._ids = copy(metafunc._ids)
except AttributeError:
# pytest>=5.3.0
pass

copied._arg2fixturedefs = copy(metafunc._arg2fixturedefs)
return copied

0 comments on commit 2c72edb

Please sign in to comment.