Package: kingston
I use the excellentFuncylibrary
for Python a lot. This is my collection of extras that I have designed
to work closely together with funcy. Funcy Kingston (Reference, seehere).Run on Repl.itKingston is auto-formatted usingyapf.Pattern matching using extendeddict’smatch.Matchobjects are callable objects using adictsemantic
that also matches calls based on the type of the calling parameters:>>>fromkingstonimportmatch>>>foo=match.TypeMatcher({...int:lambdax:x*100,...str:lambdax:f'Hello{x}'...})>>>foo(10)1000>>>foo('bar')'Hello bar'>>>>>>fromkingstonimportmatch>>>foo=match.TypeMatcher({...int:lambdax:x*100,...str:lambdax:f'Hello{x}',...(int,int):lambdaa,b:a+b...})>>>foo(10)1000>>>foo('bar')'Hello bar'>>>>>>foo(1,2)3>>>You can usetyping.Anyas a wildcard:>>>fromtypingimportAny>>>fromkingstonimportmatch>>>foo=match.TypeMatcher({...int:lambdax:x*100,...str:(lambdax:f"Hello{x}"),...(int,Any):(lambdanum,x:num*x)...})>>>foo(10)1000>>>foo('bar')'Hello bar'>>>foo(3,'X')'XXX'>>>foo(10,10)100>>>You can also subclass type matchers and use a decorator to declare cases
as methods:>>>fromkingston.matchimportMatcher,TypeMatcher,case>>>fromnumbersimportNumber>>>classNumberDescriber(TypeMatcher):...@case...defdescribe_one_int(self,one:int)->str:...return"One integer"......@case...defdescribe_two_ints(self,one:int,two:int)->str:...return"Two integers"......@case...defdescribe_one_float(self,one:float)->str:...return"One float">>>my_num_matcher:Matcher[Number,str]=NumberDescriber()>>>my_num_matcher(1)'One integer'>>>my_num_matcher(1,2)'Two integers'>>>my_num_matcher(1.0)'One float'>>>Typing pattern matchersmatch.Matchobjects can be typed using Python’s standardtypingmechanism. It
is done usingGenerics:The two subtypes are[argument type, return type].>>>fromkingstonimportmatch>>>foo:match.Matcher[int,int]=match.TypeMatcher({...int:lambdax:x+1,...str:lambdax:'hello'})>>>foo(10)11>>>foo('bar')# fails on mypy but would be ok at runtime'hello'>>>Match by value(s)match.ValueMatcherwill use thevaluesof the parameters to do the
same as asmatch.Match:>>>fromkingstonimportmatch>>>foo=match.ValueMatcher({'x':(lambda:'An x!'),('x','y'):(lambdax,y:3*(x+y))})>>>foo('x')'An x!'>>>foo('x','y')'xyxyxy'>>>Same as with the type matcher above,typing.Anyworks as a wildcard
with the value matcher as well:>>>fromkingstonimportmatch>>>fromtypingimportAny>>>foo=match.ValueMatcher({...'x':lambdax:'An X!',...('y',Any):lambdax,y:3*(x+y)...})>>>foo('x')'An X!'>>>foo('y','x')'yxyxyx'>>>You can also declare cases as methods in a customValueMatchersubclass.Use the functionvalue_case()to declare value cases.Note:imported as a shorthand:>>>fromkingston.matchimportMatcher,ValueMatcher>>>fromkingston.matchimportvalue_caseascase>>>classSimplestEval(ValueMatcher):...@case(Any,'+',Any)...def_add(self,a,op,b)->int:...returna+b......@case(Any,'-',Any)...def_sub(self,a,op,b)->int:...returna-b>>>simpl_eval=SimplestEval()>>>simpl_eval(1,'+',2)3>>>simpl_eval(10,'-',5)5>>>Aspect Oriented Programming with terse syntaxKingston also implement a technique to doAOPwith
an opinionated terse syntax that I like. It lives in thekingston.aopmodule.It’s used in two main ways:With decoratorsDefine an=Aspects=object as an empty object:>>>fromkingston.aopimportAspects>>>when=Aspects()>>>Then declare your aspects using the object as a decorator:>>>@when(lambdax:x==1,y=lambday:y==1)...deflabbo(x,y=1):...return11>>>@when(lambdax:x==1,z=lambdaz:z==2)...deflabbo(x,z=2):...return12>>>Aspect 1 above will be triggered if you call it with positional
parameter 0 as1and a keyword parametery=1:>>>labbo(1,y=1)11>>>Aspect 2 is triggered by parameters1, z=2:>>>labbo(1,z=2)12>>>Any other combination of parameters will raise aAspectNotFoundexception:>>>labbo(123)# doctest: +IGNORE_EXCEPTION_DETAILTraceback(mostrecentcalllast):AspectNotFound>>>>>>With a mapping of aspectsYou might find this better if you want brievity and/or point free style.>>>given=Aspects({...(lambdax:x==1,):lambdax:1,...(lambdax:x>1,):lambdax:x*x...})>>>Calls work the same as above:>>>given(1)1>>>given(2)4>>>given(0)# doctest: +IGNORE_EXCEPTION_DETAILTraceback(mostrecentcalllast):AspectNotFound>>>Nice thingsdig()Deep value grabbing from almost any object. Somewhat inspired by CSS
selectors, but not very complete. This part of the API is unstable — it
will (hopefully) be developed further in the future.>>>fromkingstonimportdig>>>dig.xget((1,2,3),1)2>>>dig.xget({'foo':'bar'},'foo')'bar'>>>dig.dig({'foo':1,'bar':[1,2,3]},'bar.1')2>>>dig.dig({'foo':1,'bar':[1,{'baz':'jox'},3]},'bar.1.baz')'jox'>>>The difference betweendig.dig()andfuncy.get_in()is that you
can use shell-like blob patterns to get several values keyed by similar
names:>>>fromkingstonimportdig>>>res=dig.dig({'foo':1,'foop':2},'f*')>>>res[foo=1:int,foop=2:int]>>># (textual representation of an indexable object)>>>res[0]foo=1:int>>>res[1]foop=2:int>>>Testing toolsKingston has some testing tools as well. Also, due to Kingston’s
opinionated nature, they are only targeted towardspytest.Shortform for pytest.mark.parametrizeI tend to use pytest.mark.parametrize in the same form everywhere. Thus
I have implemented this short-form:>>>fromkingston.testingimportfixture>>>@fixture.params(..."a, b",...(1,1),...(2,2),...)...deftest_dummy_compare(a,b):...asserta==b>>>Doctests as fixturesThere is a test decorator that generates pytest fixtures from a function
or an object. Use it like this:>>>defmy_doctested_func():..."""
...   >>> 1 + 1
...   2
...   >>> mystring = 'abc'
...   >>> mystring
...   'abc'
...   """...pass>>>fromkingston.testingimportfixture>>>@fixture.doctest(my_doctested_func)...deftest_doctest_my_doctested(doctest):# fixture name always 'doctest'...res=doctest()...assertres=='',res>>>