PKnmNĶddpytest_lambda/__init__.py"""Define pytest fixtures using lambda functions""" __version__ = '0.1.0' from .fixtures import * PK wBNlpytest_lambda/exceptions.py__all__ = ['DisabledFixtureError', 'NotImplementedFixtureError'] class DisabledFixtureError(Exception): """Thrown when a disabled fixture has been requested by a test or fixture See pytest_lambda.fixtures.disabled_fixture """ class NotImplementedFixtureError(NotImplementedError): """Thrown when an abstract fixture has been requested See pytest_lambda.fixtures.not_implemented_fixture """ PK wBNpytest_lambda/fixtures.pyimport inspect from typing import Union, Callable, Any, Iterable from pytest_lambda.exceptions import DisabledFixtureError, NotImplementedFixtureError from pytest_lambda.impl import LambdaFixture __all__ = ['lambda_fixture', 'static_fixture', 'error_fixture', 'disabled_fixture', 'not_implemented_fixture'] def lambda_fixture(fixture_name_or_lambda: Union[str, Callable]=None, *other_fixture_names: Iterable[str], bind=False, scope="function", params=None, autouse=False, ids=None, name=None): """Use a fixture name or lambda function to compactly declare a fixture Usage: class DescribeMyTests: url = lambda_fixture('list_url') updated_name = lambda_fixture(lambda vendor: vendor.name + ' updated') :param fixture_name_or_lambda: Either the name of another fixture, or a lambda function, which can request other fixtures with its params. If None, this defaults to the name of the attribute containing the lambda_fixture. :param bind: Set this to true to pass self to your fixture. It must be the first parameter in your fixture. This cannot be true if using a fixture name. """ if other_fixture_names: fixture_names_or_lambda = (fixture_name_or_lambda,) + other_fixture_names else: fixture_names_or_lambda = fixture_name_or_lambda return LambdaFixture(fixture_names_or_lambda, bind=bind, scope=scope, params=params, autouse=autouse, ids=ids, name=name) def static_fixture(value: Any, **fixture_kwargs): """Compact method for defining a fixture that returns a static value """ return lambda_fixture(lambda: value, **fixture_kwargs) RAISE_EXCEPTION_FIXTURE_FUNCTION_FORMAT = ''' def raise_exception({args}): exc = error_fn({kwargs}) if exc is not None: raise exc ''' def error_fixture(error_fn: Callable, **fixture_kwargs): """Fixture whose usage results in the raising of an exception Usage: class DescribeMyTests: url = error_fixture(lambda request: Exception( f'Please override the {request.fixturename} fixture!')) :param error_fn: fixture method which returns an exception to raise. It may request pytest fixtures in its arguments """ proto = tuple(inspect.signature(error_fn).parameters) args = ', '.join(proto) kwargs = ', '.join(f'{arg}={arg}' for arg in proto) source = RAISE_EXCEPTION_FIXTURE_FUNCTION_FORMAT.format( args=args, kwargs=kwargs, ) ctx = {'error_fn': error_fn} exec(source, ctx) raise_exception = ctx['raise_exception'] return lambda_fixture(raise_exception, **fixture_kwargs) def disabled_fixture(**fixture_kwargs): """Mark a fixture as disabled – using the fixture will raise an error This is useful when you know any usage of a fixture would be in error. When using disabled_fixture, pytest will raise an error if the fixture is requested, so errors can be detected early, and faulty assumptions may be avoided. Usage: class DescribeMyListOnlyViewSet(ViewSetTest): list_route = lambda_fixture(lambda: reverse('...')) detail_route = disabled_fixture() class DescribeRetrieve(UsesDetailRoute): def test_that_should_throw_error(): print('I should never be executed!') """ def build_disabled_fixture_error(request): msg = (f'Usage of the {request.fixturename} fixture has been disabled ' f'in the current context.') return DisabledFixtureError(msg) return error_fixture(build_disabled_fixture_error, **fixture_kwargs) def not_implemented_fixture(**fixture_kwargs): """Mark a fixture as abstract – requiring definition/override by the user This is useful when defining abstract base classes requiring implementation to be used correctly. Usage: class MyBaseTest: list_route = not_implemented_fixture() class TestThings(MyBaseTest): list_route = lambda_fixture(lambda: reverse(...)) """ def build_not_implemented_fixture_error(request): msg = (f'Please define/override the {request.fixturename} fixture in ' f'the current context.') return NotImplementedFixtureError(msg) return error_fixture(build_not_implemented_fixture_error, **fixture_kwargs) PK~BN6pytest_lambda/impl.pyimport functools from types import ModuleType from typing import Callable, Union import pytest import wrapt _IDENTITY_LAMBDA_FORMAT = ''' {name} = lambda {argnames}: ({argnames}) ''' def create_identity_lambda(name, *argnames): source = _IDENTITY_LAMBDA_FORMAT.format(name=name, argnames=', '.join(argnames)) context = {} exec(source, context) fixture_func = context[name] return fixture_func class LambdaFixture(wrapt.ObjectProxy): # NOTE: pytest won't apply marks unless the markee has a __call__ and a # __name__ defined. __name__ = '' bind: bool fixture_kwargs: dict fixture_func: Callable has_fixture_func: bool parent: Union[type, ModuleType] def __init__(self, fixture_names_or_lambda, bind=False, **fixture_kwargs): self.bind = bind self.fixture_kwargs = fixture_kwargs self.fixture_func = self._not_implemented self.has_fixture_func = False self.parent = None #: pytest fixture info definition self._pytestfixturefunction = pytest.fixture(**fixture_kwargs) if fixture_names_or_lambda is not None: self.set_fixture_func(fixture_names_or_lambda) elif fixture_kwargs.get('params'): # Shortcut to allow `lambda_fixture(params=[1,2,3])` self.set_fixture_func(lambda request: request.param) def __call__(self, *args, **kwargs): if self.bind: args = (self.parent,) + args return self.fixture_func(*args, **kwargs) def _not_implemented(self): raise NotImplementedError( 'The fixture_func for this LambdaFixture has not been defined. ' 'This is a catastrophic error!') def set_fixture_func(self, fixture_names_or_lambda): self.fixture_func = self.build_fixture_func(fixture_names_or_lambda) self.has_fixture_func = True # NOTE: this initializes the ObjectProxy super().__init__(self.fixture_func) def build_fixture_func(self, fixture_names_or_lambda): if callable(fixture_names_or_lambda): real_fixture_func = fixture_names_or_lambda # We create a new method with the same signature as the passed # method, which simply calls the passed method – this is so we can # modify __name__ and other properties of the function without fear # of overwriting functions unrelated to the fixture. (A lambda need # not be used – a method imported from another module can be used.) @functools.wraps(real_fixture_func) def insulator(*args, **kwargs): return real_fixture_func(*args, **kwargs) return insulator else: if self.bind: raise ValueError( 'bind must be False if requesting a fixture by name') fixture_names = fixture_names_or_lambda if isinstance(fixture_names, str): fixture_names = (fixture_names,) # Create a new method with the requested parameter, so pytest can # determine its dependencies at parse time. If we instead use # request.getfixturevalue, pytest won't know to include the fixture # in its dependency graph, and will vomit with "The requested # fixture has no parameter defined for the current test." name = 'fixture__' + '__'.join(fixture_names) # XXX: will this conflict in certain circumstances? return create_identity_lambda(name, *fixture_names) def contribute_to_parent(self, parent: Union[type, ModuleType], name: str, **kwargs): """Setup the LambdaFixture for the given class/module This method is called during collection, when a LambdaFixture is encountered in a module or class. This method is responsible for saving any names and setting any attributes on parent as necessary. """ is_in_class = isinstance(parent, type) is_in_module = isinstance(parent, ModuleType) assert is_in_class or is_in_module if is_in_module and self.bind: raise ValueError(f'bind=True cannot be used at the module level. ' f'Please remove this arg in the {name} fixture in {parent.__file__}') if not self.has_fixture_func: # If no fixture definition was passed to lambda_fixture, it's our # responsibility to define it as the name of the attribute. This is # handy if ya just wanna force a fixture to be used, e.g.: # do_the_thing = lambda_fixture(autouse=True) self.set_fixture_func(name) self.__name__ = name self.__module__ = parent.__module__ if is_in_class else parent.__name__ self.parent = parent # These properties are required in order to expose attributes stored on the # LambdaFixture proxying instance without prefixing them with _self_ @property def bind(self): return self._self_bind @bind.setter def bind(self, value): self._self_bind = value @property def fixture_kwargs(self): return self._self_fixture_kwargs @fixture_kwargs.setter def fixture_kwargs(self, value): self._self_fixture_kwargs = value @property def fixture_func(self): return self._self_fixture_func @fixture_func.setter def fixture_func(self, value): self._self_fixture_func = value @property def has_fixture_func(self): return self._self_has_fixture_func @has_fixture_func.setter def has_fixture_func(self, value): self._self_has_fixture_func = value @property def parent(self): return self._self_parent @parent.setter def parent(self, value): self._self_parent = value @property def _pytestfixturefunction(self): return self._self__pytestfixturefunction @_pytestfixturefunction.setter def _pytestfixturefunction(self, value): self._self__pytestfixturefunction = value PK~BNTXnpytest_lambda/plugin.pyimport inspect from typing import List, Tuple import pytest from _pytest.python import Module from pytest_lambda.impl import LambdaFixture def get_attrs_to_expose_under_pytest() -> dict: """Attributes which should be accessible from the root pytest namespace""" import pytest_lambda return vars(pytest_lambda) def pytest_addhooks(pluginmanager): # No hooks added, but we do monkeypatch the pytest module namespace to # expose our goods, a la pytest_namespace — but with less deprecation warnings. for name, val in get_attrs_to_expose_under_pytest().items(): setattr(pytest, name, val) def pytest_collectstart(collector): if isinstance(collector, Module): process_lambda_fixtures(collector.module) def pytest_pycollect_makeitem(collector, name, obj): if inspect.isclass(obj): process_lambda_fixtures(obj) def process_lambda_fixtures(parent): """Turn all lambda_fixtures in a class/module into actual pytest fixtures """ lfix_attrs: List[Tuple[str, LambdaFixture]] = ( inspect.getmembers(parent, lambda o: isinstance(o, LambdaFixture))) for name, attr in lfix_attrs: attr.contribute_to_parent(parent, name) return parent PK!H u'!(.pytest_lambda-0.1.0.dist-info/entry_points.txt.,I-.14IMJICxz9y\\PKݕ"N?`--%pytest_lambda-0.1.0.dist-info/LICENSEMIT License Copyright (c) 2018 Zach Kanzler Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!HPO#pytest_lambda-0.1.0.dist-info/WHEEL HM K-*ϳR03rOK-J,/RH,szd&Y)r$[)T&UrPK!H?I &pytest_lambda-0.1.0.dist-info/METADATAYms6_4(M%N27sqi'2 "!5I2{|b[#gww[>]h#U>aO#gbŠ c)f1:(8.,㺚31h\R J#yGĸ O-`!mR΂He6>{`2ߜ;)mG {ϩЍXd\5~Z})%, {=?~;&Ҳ kǧ*"˪V:V;M1r.&nD r[6lN"kZhe[/J`Aҵk 'J_7z5;) nDL߽lmTHBkf;/qL[s&mC!o<\j^G% G YXM7WȂiq$XQC>sU:(*ϓYm6{\js(l$^&8H SLS {Q`DV!vjsFŭe_BO:ݱb9x D,=K=swc;mA֧$BQr֏ nyA{]0_b"L qnVF_R X յUt1[Km8DҚ"sE 65"O)e,geu3Vc6LTd"'!CQDTP=qgtgKrn>KEL_Y>G׈A\cC!̊k}zGm "bPsR[ ?h@I8f]̫)U{ڣu !PD-% aG=MUǤEctvڅb S_L|AG}":o78#l;DtD_84 0#ptTpxbu2)-T4+!8;m.MCVۮZ.N8| K^,Ѷ)mNas҄n1^!:$wQr-1?AY`ʺF*|yq5§zr&@=9`Q] *wJJ(^GV_ڙm{ԭTٻLG w${V5-הּ{UҚ w.mxMc)"C3 )m~eȨ=bւ*YL8_pVKF/|v ."T[l{x'$ &%SN O~Ree94ASmy!)FPgl .b/DI?;sr%gJ\GWn[r!OS ҆}_!&Hc8y\̊}nˌ-Ee]e0} i*.(^Exvt.ږ<͈aԁNf63sp>=hzq7c]N&L`o{ Aˮ$c,RaؤBTd)#|ג֣Q CM>T&4RcZԇN͸hu{PVuӀ]&XJ.aʈTE~Y̝ٻAR-6tdi`=>PK!H{7$pytest_lambda-0.1.0.dist-info/RECORDҽ0@~Ib "0(@"Ook |9E+b.,̏QQ%TI=:jΠP^:.}4Ek#͗K $WS\zci[뜝>5Fh&-D)f*6^ůVbm(ry~#7ѯ!WI SIE@yi^d/ڃT~.R6%ZKE<̳-wIfv٨\ C޴"'xXK8D7Qy 641h [9{ *'^D);߾c&6(n xfsү \)kG\з9ONID1H*Ĝ{vvxe/pw,qXX3{vOvbݥik*}p^V9'nZLK)idO|6QUC-Jm(TD@S:;\HW?+,V#IPKnmNĶddpytest_lambda/__init__.pyPK wBNlpytest_lambda/exceptions.pyPK wBNxpytest_lambda/fixtures.pyPK~BN6Epytest_lambda/impl.pyPK~BNTXn[,pytest_lambda/plugin.pyPK!H u'!(.\1pytest_lambda-0.1.0.dist-info/entry_points.txtPKݕ"N?`--%1pytest_lambda-0.1.0.dist-info/LICENSEPK!HPO#96pytest_lambda-0.1.0.dist-info/WHEELPK!H?I &6pytest_lambda-0.1.0.dist-info/METADATAPK!H{7$W@pytest_lambda-0.1.0.dist-info/RECORDPK B