Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/jacob414/kingston
Funcy Kingston
https://github.com/jacob414/kingston
python python-3 utility-library
Last synced: about 1 month ago
JSON representation
Funcy Kingston
- Host: GitHub
- URL: https://github.com/jacob414/kingston
- Owner: jacob414
- License: lgpl-3.0
- Created: 2020-03-18T17:14:17.000Z (over 4 years ago)
- Default Branch: master
- Last Pushed: 2021-05-25T11:57:06.000Z (over 3 years ago)
- Last Synced: 2024-09-29T17:03:24.810Z (about 2 months ago)
- Topics: python, python-3, utility-library
- Language: Python
- Homepage: https://kingston.readthedocs.io/
- Size: 261 KB
- Stars: 3
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.org
- Changelog: CHANGELOG.org
- License: LICENSE
Awesome Lists containing this project
README
* Kingston README
I use the excellent [[https://funcy.readthedocs.io/][Funcy]] library for Python a lot. This is my
collection of extras that I have designed to work closely together
with funcy. Funcy Kingston (Reference, see [[https://youtu.be/U79o7qwul48][here]]).[[https://repl.it/@jacob414/kingston][Run on Repl.it]]
Kingston is auto-formatted using [[https://github.com/google/yapf][yapf]].
** Pattern matching using extended =dict='s
=match.Match= objects are callable objects using a =dict= semantic
that also matches calls based on the type of the calling parameters:#+BEGIN_SRC python
>>> from kingston import match
>>> foo = match.TypeMatcher({
... int: lambda x: x*100,
... str: lambda x: f'Hello {x}'
... })
>>> foo(10)
1000
>>> foo('bar')
'Hello bar'
>>>
#+END_SRC#+BEGIN_SRC python
>>> from kingston import match
>>> foo = match.TypeMatcher({
... int: lambda x: x * 100,
... str: lambda x: f'Hello {x}',
... (int, int): lambda a, b: a + b
... })
>>> foo(10)
1000
>>> foo('bar')
'Hello bar'
>>>
>>> foo(1, 2)
3
>>>
#+END_SRCYou can use =typing.Any= as a wildcard:
#+BEGIN_SRC python
>>> from typing import Any
>>> from kingston import match
>>> foo = match.TypeMatcher({
... int: lambda x: x * 100,
... str: (lambda x: f"Hello {x}"),
... (int, Any): (lambda num, x: num * x)
... })
>>> foo(10)
1000
>>> foo('bar')
'Hello bar'
>>> foo(3, 'X')
'XXX'
>>> foo(10, 10)
100
>>>
#+END_SRCYou can also subclass type matchers and use a decorator to declare
cases as methods:#+BEGIN_SRC python
>>> from kingston.match import Matcher, TypeMatcher, case
>>> from numbers import Number
>>> class NumberDescriber(TypeMatcher):
... @case
... def describe_one_int(self, one:int) -> str:
... return "One integer"
...
... @case
... def describe_two_ints(self, one:int, two:int) -> str:
... return "Two integers"
...
... @case
... def describe_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'
>>>
#+END_SRC*** Typing pattern matchers
=match.Match= objects can be typed using Python's standard [[https://docs.python.org/3/library/typing.html][typing]]
mechanism. It is done using [[https://mypy.readthedocs.io/en/stable/generics.html][Generics]]:The two subtypes are /[argument type, return type]/.
#+BEGIN_SRC python
>>> from kingston import match
>>> foo:match.Matcher[int, int] = match.TypeMatcher({
... int: lambda x: x+1,
... str: lambda x: 'hello'})
>>> foo(10)
11
>>> foo('bar') # fails on mypy but would be ok at runtime
'hello'
>>>
#+END_SRC*** Match by value(s)
=match.ValueMatcher= will use the /values/ of the parameters to do the same
as as =match.Match=:#+BEGIN_SRC python
>>> from kingston import match
>>> foo = match.ValueMatcher({'x': (lambda: 'An x!'), ('x', 'y'): (lambda x,y: 3*(x+y))})
>>> foo('x')
'An x!'
>>> foo('x', 'y')
'xyxyxy'
>>>
#+END_SRCSame as with the type matcher above, =typing.Any= works as a wildcard
with the value matcher as well:#+BEGIN_SRC python
>>> from kingston import match
>>> from typing import Any
>>> foo = match.ValueMatcher({
... 'x': lambda x: 'An X!',
... ('y', Any): lambda x, y: 3 * (x + y)
... })
>>> foo('x')
'An X!'
>>> foo('y', 'x')
'yxyxyx'
>>>
#+END_SRCYou can also declare cases as methods in a custom =ValueMatcher=
subclass.Use the function =value_case()= to declare value cases. *Note:*
/imported as a shorthand/:#+BEGIN_SRC python
>>> from kingston.match import Matcher, ValueMatcher
>>> from kingston.match import value_case as case
>>> class SimplestEval(ValueMatcher):
... @case(Any, '+', Any)
... def _add(self, a, op, b) -> int:
... return a + b
...
... @case(Any, '-', Any)
... def _sub(self, a, op, b) -> int:
... return a - b
>>> simpl_eval = SimplestEval()
>>> simpl_eval(1, '+', 2)
3
>>> simpl_eval(10, '-', 5)
5
>>>
#+END_SRC** Aspect Oriented Programming with terse syntax
Kingston also implement a technique to do [[https://en.wikipedia.org/wiki/Aspect-oriented_programming][AOP]] with an opinionated
terse syntax that I like. It lives in the =kingston.aop= module.It's used in two main ways:
*** With decorators
Define an =Aspects= object as an empty object:
#+BEGIN_SRC python
>>> from kingston.aop import Aspects
>>> when = Aspects()
>>>
#+END_SRCThen declare your aspects using the object as a decorator:
#+BEGIN_SRC python
>>> @when(lambda x: x == 1, y=lambda y: y == 1)
... def labbo(x, y=1):
... return 11
>>> @when(lambda x: x == 1, z=lambda z: z == 2)
... def labbo(x, z=2):
... return 12
>>>
#+END_SRCAspect 1 above will be triggered if you call it with positional
parameter 0 as =1= and a keyword parameter =y=1=:#+BEGIN_SRC python
>>> labbo(1, y=1)
11
>>>
#+END_SRCAspect 2 is triggered by parameters =1, z=2=:
#+BEGIN_SRC python
>>> labbo(1, z=2)
12
>>>
#+END_SRCAny other combination of parameters will raise a
=AspectNotFound= exception:#+BEGIN_SRC python
>>> labbo(123) # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
AspectNotFound
>>>
>>>
#+END_SRC*** With a mapping of aspects
You might find this better if you want brievity and/or point free
style.#+BEGIN_SRC python
>>> given = Aspects({
... (lambda x: x == 1,): lambda x: 1,
... (lambda x: x > 1,): lambda x: x * x
... })
>>>
#+END_SRCCalls work the same as above:
#+BEGIN_SRC python
>>> given(1)
1
>>> given(2)
4
>>> given(0) # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
AspectNotFound
>>>
#+END_SRC** Nice things
*** dig()
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.#+BEGIN_SRC python
>>> from kingston import dig
>>> 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'
>>>
#+END_SRCThe difference between =dig.dig()= and =funcy.get_in()= is that you
can use shell-like blob patterns to get several values keyed by
similar names:#+BEGIN_SRC python
>>> from kingston import dig
>>> 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
>>>
#+END_SRC** Testing tools
Kingston has some testing tools as well. Also, due to Kingston's
opinionated nature, they are only targeted towards [[https://pytest.org][pytest]].*** Shortform for pytest.mark.parametrize
I tend to use pytest.mark.parametrize in the same form
everywhere. Thus I have implemented this short-form:#+BEGIN_SRC python
>>> from kingston.testing import fixture
>>> @fixture.params(
... "a, b",
... (1, 1),
... (2, 2),
... )
... def test_dummy_compare(a, b):
... assert a == b
>>>
#+END_SRC*** Doctests as fixtures
There is a test decorator that generates pytest fixtures from a
function or an object. Use it like this:#+BEGIN_SRC python
>>> def my_doctested_func():
... """
... >>> 1 + 1
... 2
... >>> mystring = 'abc'
... >>> mystring
... 'abc'
... """
... pass
>>> from kingston.testing import fixture
>>> @fixture.doctest(my_doctested_func)
... def test_doctest_my_doctested(doctest): # fixture name always 'doctest'
... res = doctest()
... assert res == '', res
>>>
#+END_SRC