HTTP traffic mocking and expectations made easy
Project description
pook |Build Status| |PyPI| |Coverage Status| |Documentation Status| |Stability| |Quality| |Versions|
====================================================================================================
Versatile, expressive and hackable utility library for HTTP traffic mocking
and expectations made easy in `Python`_. Heavily inspired by `gock`_.
To get started, see the `documentation`_, `how it works`_, `FAQ`_ or `examples`_.
.. raw:: html
<a target='_blank' rel='nofollow' href='https://app.codesponsor.io/link/1MpD3pzt63uUeG43NP4tDHPY/h2non/pook'>
<img alt='Sponsor' width='888' height='68' src='https://app.codesponsor.io/embed/1MpD3pzt63uUeG43NP4tDHPY/h2non/pook.svg' />
</a>
Features
--------
- Simple, expressive and fluent API.
- Provides both Pythonic and chainable DSL API styles.
- Full-featured HTTP response definitions and expectations.
- Matches any HTTP protocol primitive (URL, method, query params, headers, body...).
- Full regular expressions capable mock expectations matching.
- Supports most popular HTTP clients via interceptor adapters.
- Configurable volatile, persistent or TTL limited mocks.
- Works with any testing framework/engine (unittest, pytest, nosetests...).
- First-class JSON & XML support matching and responses.
- Supports JSON Schema body matching.
- Works in both runtime and testing environments.
- Can be used as decorator and/or via context managers.
- Supports real networking mode with optional traffic filtering.
- Map/filter mocks easily for generic or custom mock expectations.
- Custom user-defined mock matcher functions.
- Simulated raised error exceptions.
- Network delay simulation (only available for ``aiohttp``).
- Pluggable and hackable API.
- Customizable HTTP traffic mock interceptor engine.
- Supports third-party mocking engines, such as `mocket`_.
- Fits good for painless test doubles.
- Does not support WebSocket traffic mocking.
- Works with Python +2.7 and +3.0 (including PyPy).
- Dependency-less: just 2 small dependencies for JSONSchema and XML tree comparison.
Supported HTTP clients
----------------------
``pook`` can work with multiple mock engines, however it provides a
built-in one by default, which currently supports traffic mocking in
the following HTTP clients:
- ✔ `urllib3`_ v1+
- ✔ `requests`_ v2+
- ✔ `aiohttp`_ v1+ - v2+
- ✔ `urllib`_ / `http.client`_ v2/3
- ✘ `pycurl`_ (see `#16`_)
More HTTP clients can be supported progressively.
**Note**: only recent HTTP client package versions were tested.
Installation
------------
Using ``pip`` package manager (requires pip 1.8+):
.. code:: bash
pip install --upgrade pook
Or install the latest sources from Github:
.. code:: bash
pip install -e git+git://github.com/h2non/pook.git#egg=pook
Getting started
---------------
See ReadTheDocs documentation:
|Documentation Status|
API
---
See `annotated API reference`_ documention.
Examples
--------
See `examples`_ documentation for full featured code and use case examples.
Basic mocking:
.. code:: python
import pook
import requests
@pook.on
def test_my_api():
mock = pook.get('http://twitter.com/api/1/foobar', reply=404, response_json={'error': 'not found'})
resp = requests.get('http://twitter.com/api/1/foobar')
assert resp.status_code == 404
assert resp.json() == {"error": "not found"}
assert mock.calls == 1
Using the chainable API DSL:
.. code:: python
import pook
import requests
@pook.on
def test_my_api():
mock = (pook.get('http://twitter.com/api/1/foobar')
.reply(404)
.json({'error': 'not found'}))
resp = requests.get('http://twitter.com/api/1/foobar')
assert resp.json() == {"error": "not found"}
assert mock.calls == 1
Using the decorator:
.. code:: python
import pook
import requests
@pook.get('http://httpbin.org/status/500', reply=204)
@pook.get('http://httpbin.org/status/400', reply=200)
def fetch(url):
return requests.get(url)
res = fetch('http://httpbin.org/status/400')
print('#1 status:', res.status_code)
res = fetch('http://httpbin.org/status/500')
print('#2 status:', res.status_code)
Simple ``unittest`` integration:
.. code:: python
import pook
import unittest
import requests
class TestUnitTestEngine(unittest.TestCase):
@pook.on
def test_request(self):
pook.get('server.com/foo').reply(204)
res = requests.get('http://server.com/foo')
self.assertEqual(res.status_code, 204)
def test_request_with_context_manager(self):
with pook.use():
pook.get('server.com/bar', reply=204)
res = requests.get('http://server.com/bar')
self.assertEqual(res.status_code, 204)
Using the context manager for isolated HTTP traffic interception blocks:
.. code:: python
import pook
import requests
# Enable HTTP traffic interceptor
with pook.use():
pook.get('http://httpbin.org/status/500', reply=204)
res = requests.get('http://httpbin.org/status/500')
print('#1 status:', res.status_code)
# Interception-free HTTP traffic
res = requests.get('http://httpbin.org/status/200')
print('#2 status:', res.status_code)
Example using `mocket`_ Python library as underlying mock engine:
.. code:: python
import pook
import requests
from mocket.plugins.pook_mock_engine import MocketEngine
# Use mocket library as underlying mock engine
pook.set_mock_engine(MocketEngine)
# Explicitly enable pook HTTP mocking (optional)
pook.on()
# Target server URL to mock out
url = 'http://twitter.com/api/1/foobar'
# Define your mock
mock = pook.get(url,
reply=404, times=2,
headers={'content-type': 'application/json'},
response_json={'error': 'foo'})
# Run first HTTP request
requests.get(url)
assert mock.calls == 1
# Run second HTTP request
res = requests.get(url)
assert mock.calls == 2
# Assert response data
assert res.status_code == 404
assert res.json() == {'error': 'foo'}
# Explicitly disable pook (optional)
pook.off()
Example using Hy language (Lisp dialect for Python):
.. code:: hy
(import [pook])
(import [requests])
(defn request [url &optional [status 404]]
(doto (.mock pook url) (.reply status))
(let [res (.get requests url)]
(. res status_code)))
(defn run []
(with [(.use pook)]
(print "Status:" (request "http://server.com/foo" :status 204))))
;; Run test program
(defmain [&args] (run))
Development
-----------
Clone the repository:
.. code:: bash
git clone git@github.com:h2non/pook.git
Install dependencies:
.. code:: bash
pip install -r requirements.txt -r requirements-dev.txt
Install Python dependencies:
.. code:: bash
make install
Lint code:
.. code:: bash
make lint
Run tests:
.. code:: bash
make test
Generate documentation:
.. code:: bash
make htmldocs
License
-------
MIT - Tomas Aparicio
.. _Go: https://golang.org
.. _Python: http://python.org
.. _gock: https://github.com/h2non/gock
.. _annotated API reference: http://pook.readthedocs.io/en/latest/api.html
.. _#16: https://github.com/h2non/pook/issues/16
.. _examples: http://pook.readthedocs.io/en/latest/examples.html
.. _aiohttp: https://github.com/KeepSafe/aiohttp
.. _requests: http://docs.python-requests.org/en/master/
.. _urllib3: https://github.com/shazow/urllib3
.. _urllib: https://docs.python.org/3/library/urllib.html
.. _http.client: https://docs.python.org/3/library/http.client.html
.. _pycurl: http://pycurl.io
.. _documentation: http://pook.readthedocs.io/en/latest/
.. _FAQ: http://pook.readthedocs.io/en/latest/faq.html
.. _how it works: http://pook.readthedocs.io/en/latest/how_it_works.html
.. _mocket: https://github.com/mindflayer/python-mocket
.. |Build Status| image:: https://travis-ci.org/h2non/pook.svg?branch=master
:target: https://travis-ci.org/h2non/pook
.. |PyPI| image:: https://img.shields.io/pypi/v/pook.svg?maxAge=2592000?style=flat-square
:target: https://pypi.python.org/pypi/pook
.. |Coverage Status| image:: https://coveralls.io/repos/github/h2non/pook/badge.svg?branch=master
:target: https://coveralls.io/github/h2non/pook?branch=master
.. |Documentation Status| image:: https://img.shields.io/badge/docs-latest-green.svg?style=flat
:target: http://pook.readthedocs.io/en/latest/?badge=latest
.. |Quality| image:: https://codeclimate.com/github/h2non/pook/badges/gpa.svg
:target: https://codeclimate.com/github/h2non/pook
:alt: Code Climate
.. |Stability| image:: https://img.shields.io/pypi/status/pook.svg
:target: https://pypi.python.org/pypi/pook
:alt: Stability
.. |Versions| image:: https://img.shields.io/pypi/pyversions/pook.svg
:target: https://pypi.python.org/pypi/pook
:alt: Python Versions
History
=======
v0.2.5 / 2017-10-19
-------------------
* refactor(setup): remove extra install dependency
* Fix py27 compatibility (#49)
* Add activate_async decorator (#48)
* fix typo in pook.mock.Mock.ismatched.__doc__ (#47)
* fix README example (#46)
v0.2.4 / 2017-10-03
-------------------
* fix(#45): regex URL issue
* fix(travis): allow failures in pypy
* feat(docs): add sponsor banner
* refactor(History): normalize style
v0.2.3 / 2017-04-28
-------------------
* feat(docs): add supported version for aiohttp
* Merge branch 'master' of https://github.com/h2non/pook
* fix(api): export missing symbol "disable_network"
* Update README.rst (#43)
v0.2.2 / 2017-04-03
-------------------
* refactor(compare): disable maxDiff length limit while comparing values
v0.2.1 / 2017-03-25
-------------------
* fix(engine): enable new mock engine on register if needed
* fix(engine): remove activate argument before instantiating the Mock
v0.2.0 / 2017-03-18
-------------------
* refactor(engine): do not activate engine on mock declaration if not explicitly requested. This introduces a behavioral library change: you must explicitly use ``pook.on()`` to enable `pook` mock engine.
v0.1.14 / 2017-03-17
--------------------
* feat(docs): list supported HTTP client versions
* fix(#41): disable mocks after decorator call invokation
* feat(examples): add mock context manager example file
* feat(#40): support context manager definitions
* feat(#39): improve unmatched request output
* feat(docs): add mocket example file
* feat(#33): add mocket examples and documentation
v0.1.13 / 2017-01-29
--------------------
* fix(api): `mock.calls` property should be an `int`.
v0.1.12 / 2017-01-28
--------------------
* feat(#33): proxy mock definitions into mock.Request
* refactor(api): `pook.unmatched_requests()` now returns a `list` instead of a lazy `tuple`.
v0.1.11 / 2017-01-14
--------------------
* refactor(query)
* fix(#37): fix URL comparison
* fix(#38): proper mock engine interface validation.
v0.1.10 / 2017-01-13
--------------------
* fix(#37): decode byte bodies
* feat(setup.py): add author email
v0.1.9 / 2017-01-06
-------------------
* fix(Makefile): remove proper egg file
* feat(package): add wheel package distribution support
* feat(docs): add documentation links
v0.1.8 / 2016-12-24
-------------------
* fix(assertion): extract regex pattern only when required
* feat(examples): add regular expression example
v0.1.7 / 2016-12-18
-------------------
* feat(#33): add support for user defined custom mock engine
v0.1.6 / 2016-12-14
-------------------
* fix(setup.py): force utf-8 encoding
* feat(setup.py): add encoding header
* feat(api): add debug mode
* refactor(docs): minor enhancements
* refactor(tests): update URL matcher test cases
* refactor(docs): add note about HTTP clients and update features list
* fix(setup.py): remove encoding param
* fix(tests): use strict equality assertion
0.1.5 / 2016-12-12
------------------
* fix(matchers): fix matching issue in URL.
* refactor(assertion): regex expression based matching must be explicitly enabled.
* feat(tests): add initial matchers tests.
0.1.4 / 2016-12-08
------------------
* refactor(README): minor changes
* fix(setup.py): lint error
* fix(#32): use explicit encoding while reading files in setup.py
0.1.3 / 2016-12-08
------------------
* fix(core): several bug fixes.
* feat(core): add pending features and major refactors.
* feat(matchers): use ``unittest.TestCase`` matching engine by default.
0.1.2 / 2016-12-01
------------------
* fix(matchers): runtime missing variable.
0.1.1 / 2016-12-01
------------------
* fix: Python 2 dictionary iteration syntax.
* feat(docs): add more examples.
* fix(matchers): better regular expression comparison support.
0.1.0 / 2016-11-30
------------------
* First version (still beta)
0.1.0-rc.1 / 2016-11-27
-----------------------
* First release candidate version (still beta)
====================================================================================================
Versatile, expressive and hackable utility library for HTTP traffic mocking
and expectations made easy in `Python`_. Heavily inspired by `gock`_.
To get started, see the `documentation`_, `how it works`_, `FAQ`_ or `examples`_.
.. raw:: html
<a target='_blank' rel='nofollow' href='https://app.codesponsor.io/link/1MpD3pzt63uUeG43NP4tDHPY/h2non/pook'>
<img alt='Sponsor' width='888' height='68' src='https://app.codesponsor.io/embed/1MpD3pzt63uUeG43NP4tDHPY/h2non/pook.svg' />
</a>
Features
--------
- Simple, expressive and fluent API.
- Provides both Pythonic and chainable DSL API styles.
- Full-featured HTTP response definitions and expectations.
- Matches any HTTP protocol primitive (URL, method, query params, headers, body...).
- Full regular expressions capable mock expectations matching.
- Supports most popular HTTP clients via interceptor adapters.
- Configurable volatile, persistent or TTL limited mocks.
- Works with any testing framework/engine (unittest, pytest, nosetests...).
- First-class JSON & XML support matching and responses.
- Supports JSON Schema body matching.
- Works in both runtime and testing environments.
- Can be used as decorator and/or via context managers.
- Supports real networking mode with optional traffic filtering.
- Map/filter mocks easily for generic or custom mock expectations.
- Custom user-defined mock matcher functions.
- Simulated raised error exceptions.
- Network delay simulation (only available for ``aiohttp``).
- Pluggable and hackable API.
- Customizable HTTP traffic mock interceptor engine.
- Supports third-party mocking engines, such as `mocket`_.
- Fits good for painless test doubles.
- Does not support WebSocket traffic mocking.
- Works with Python +2.7 and +3.0 (including PyPy).
- Dependency-less: just 2 small dependencies for JSONSchema and XML tree comparison.
Supported HTTP clients
----------------------
``pook`` can work with multiple mock engines, however it provides a
built-in one by default, which currently supports traffic mocking in
the following HTTP clients:
- ✔ `urllib3`_ v1+
- ✔ `requests`_ v2+
- ✔ `aiohttp`_ v1+ - v2+
- ✔ `urllib`_ / `http.client`_ v2/3
- ✘ `pycurl`_ (see `#16`_)
More HTTP clients can be supported progressively.
**Note**: only recent HTTP client package versions were tested.
Installation
------------
Using ``pip`` package manager (requires pip 1.8+):
.. code:: bash
pip install --upgrade pook
Or install the latest sources from Github:
.. code:: bash
pip install -e git+git://github.com/h2non/pook.git#egg=pook
Getting started
---------------
See ReadTheDocs documentation:
|Documentation Status|
API
---
See `annotated API reference`_ documention.
Examples
--------
See `examples`_ documentation for full featured code and use case examples.
Basic mocking:
.. code:: python
import pook
import requests
@pook.on
def test_my_api():
mock = pook.get('http://twitter.com/api/1/foobar', reply=404, response_json={'error': 'not found'})
resp = requests.get('http://twitter.com/api/1/foobar')
assert resp.status_code == 404
assert resp.json() == {"error": "not found"}
assert mock.calls == 1
Using the chainable API DSL:
.. code:: python
import pook
import requests
@pook.on
def test_my_api():
mock = (pook.get('http://twitter.com/api/1/foobar')
.reply(404)
.json({'error': 'not found'}))
resp = requests.get('http://twitter.com/api/1/foobar')
assert resp.json() == {"error": "not found"}
assert mock.calls == 1
Using the decorator:
.. code:: python
import pook
import requests
@pook.get('http://httpbin.org/status/500', reply=204)
@pook.get('http://httpbin.org/status/400', reply=200)
def fetch(url):
return requests.get(url)
res = fetch('http://httpbin.org/status/400')
print('#1 status:', res.status_code)
res = fetch('http://httpbin.org/status/500')
print('#2 status:', res.status_code)
Simple ``unittest`` integration:
.. code:: python
import pook
import unittest
import requests
class TestUnitTestEngine(unittest.TestCase):
@pook.on
def test_request(self):
pook.get('server.com/foo').reply(204)
res = requests.get('http://server.com/foo')
self.assertEqual(res.status_code, 204)
def test_request_with_context_manager(self):
with pook.use():
pook.get('server.com/bar', reply=204)
res = requests.get('http://server.com/bar')
self.assertEqual(res.status_code, 204)
Using the context manager for isolated HTTP traffic interception blocks:
.. code:: python
import pook
import requests
# Enable HTTP traffic interceptor
with pook.use():
pook.get('http://httpbin.org/status/500', reply=204)
res = requests.get('http://httpbin.org/status/500')
print('#1 status:', res.status_code)
# Interception-free HTTP traffic
res = requests.get('http://httpbin.org/status/200')
print('#2 status:', res.status_code)
Example using `mocket`_ Python library as underlying mock engine:
.. code:: python
import pook
import requests
from mocket.plugins.pook_mock_engine import MocketEngine
# Use mocket library as underlying mock engine
pook.set_mock_engine(MocketEngine)
# Explicitly enable pook HTTP mocking (optional)
pook.on()
# Target server URL to mock out
url = 'http://twitter.com/api/1/foobar'
# Define your mock
mock = pook.get(url,
reply=404, times=2,
headers={'content-type': 'application/json'},
response_json={'error': 'foo'})
# Run first HTTP request
requests.get(url)
assert mock.calls == 1
# Run second HTTP request
res = requests.get(url)
assert mock.calls == 2
# Assert response data
assert res.status_code == 404
assert res.json() == {'error': 'foo'}
# Explicitly disable pook (optional)
pook.off()
Example using Hy language (Lisp dialect for Python):
.. code:: hy
(import [pook])
(import [requests])
(defn request [url &optional [status 404]]
(doto (.mock pook url) (.reply status))
(let [res (.get requests url)]
(. res status_code)))
(defn run []
(with [(.use pook)]
(print "Status:" (request "http://server.com/foo" :status 204))))
;; Run test program
(defmain [&args] (run))
Development
-----------
Clone the repository:
.. code:: bash
git clone git@github.com:h2non/pook.git
Install dependencies:
.. code:: bash
pip install -r requirements.txt -r requirements-dev.txt
Install Python dependencies:
.. code:: bash
make install
Lint code:
.. code:: bash
make lint
Run tests:
.. code:: bash
make test
Generate documentation:
.. code:: bash
make htmldocs
License
-------
MIT - Tomas Aparicio
.. _Go: https://golang.org
.. _Python: http://python.org
.. _gock: https://github.com/h2non/gock
.. _annotated API reference: http://pook.readthedocs.io/en/latest/api.html
.. _#16: https://github.com/h2non/pook/issues/16
.. _examples: http://pook.readthedocs.io/en/latest/examples.html
.. _aiohttp: https://github.com/KeepSafe/aiohttp
.. _requests: http://docs.python-requests.org/en/master/
.. _urllib3: https://github.com/shazow/urllib3
.. _urllib: https://docs.python.org/3/library/urllib.html
.. _http.client: https://docs.python.org/3/library/http.client.html
.. _pycurl: http://pycurl.io
.. _documentation: http://pook.readthedocs.io/en/latest/
.. _FAQ: http://pook.readthedocs.io/en/latest/faq.html
.. _how it works: http://pook.readthedocs.io/en/latest/how_it_works.html
.. _mocket: https://github.com/mindflayer/python-mocket
.. |Build Status| image:: https://travis-ci.org/h2non/pook.svg?branch=master
:target: https://travis-ci.org/h2non/pook
.. |PyPI| image:: https://img.shields.io/pypi/v/pook.svg?maxAge=2592000?style=flat-square
:target: https://pypi.python.org/pypi/pook
.. |Coverage Status| image:: https://coveralls.io/repos/github/h2non/pook/badge.svg?branch=master
:target: https://coveralls.io/github/h2non/pook?branch=master
.. |Documentation Status| image:: https://img.shields.io/badge/docs-latest-green.svg?style=flat
:target: http://pook.readthedocs.io/en/latest/?badge=latest
.. |Quality| image:: https://codeclimate.com/github/h2non/pook/badges/gpa.svg
:target: https://codeclimate.com/github/h2non/pook
:alt: Code Climate
.. |Stability| image:: https://img.shields.io/pypi/status/pook.svg
:target: https://pypi.python.org/pypi/pook
:alt: Stability
.. |Versions| image:: https://img.shields.io/pypi/pyversions/pook.svg
:target: https://pypi.python.org/pypi/pook
:alt: Python Versions
History
=======
v0.2.5 / 2017-10-19
-------------------
* refactor(setup): remove extra install dependency
* Fix py27 compatibility (#49)
* Add activate_async decorator (#48)
* fix typo in pook.mock.Mock.ismatched.__doc__ (#47)
* fix README example (#46)
v0.2.4 / 2017-10-03
-------------------
* fix(#45): regex URL issue
* fix(travis): allow failures in pypy
* feat(docs): add sponsor banner
* refactor(History): normalize style
v0.2.3 / 2017-04-28
-------------------
* feat(docs): add supported version for aiohttp
* Merge branch 'master' of https://github.com/h2non/pook
* fix(api): export missing symbol "disable_network"
* Update README.rst (#43)
v0.2.2 / 2017-04-03
-------------------
* refactor(compare): disable maxDiff length limit while comparing values
v0.2.1 / 2017-03-25
-------------------
* fix(engine): enable new mock engine on register if needed
* fix(engine): remove activate argument before instantiating the Mock
v0.2.0 / 2017-03-18
-------------------
* refactor(engine): do not activate engine on mock declaration if not explicitly requested. This introduces a behavioral library change: you must explicitly use ``pook.on()`` to enable `pook` mock engine.
v0.1.14 / 2017-03-17
--------------------
* feat(docs): list supported HTTP client versions
* fix(#41): disable mocks after decorator call invokation
* feat(examples): add mock context manager example file
* feat(#40): support context manager definitions
* feat(#39): improve unmatched request output
* feat(docs): add mocket example file
* feat(#33): add mocket examples and documentation
v0.1.13 / 2017-01-29
--------------------
* fix(api): `mock.calls` property should be an `int`.
v0.1.12 / 2017-01-28
--------------------
* feat(#33): proxy mock definitions into mock.Request
* refactor(api): `pook.unmatched_requests()` now returns a `list` instead of a lazy `tuple`.
v0.1.11 / 2017-01-14
--------------------
* refactor(query)
* fix(#37): fix URL comparison
* fix(#38): proper mock engine interface validation.
v0.1.10 / 2017-01-13
--------------------
* fix(#37): decode byte bodies
* feat(setup.py): add author email
v0.1.9 / 2017-01-06
-------------------
* fix(Makefile): remove proper egg file
* feat(package): add wheel package distribution support
* feat(docs): add documentation links
v0.1.8 / 2016-12-24
-------------------
* fix(assertion): extract regex pattern only when required
* feat(examples): add regular expression example
v0.1.7 / 2016-12-18
-------------------
* feat(#33): add support for user defined custom mock engine
v0.1.6 / 2016-12-14
-------------------
* fix(setup.py): force utf-8 encoding
* feat(setup.py): add encoding header
* feat(api): add debug mode
* refactor(docs): minor enhancements
* refactor(tests): update URL matcher test cases
* refactor(docs): add note about HTTP clients and update features list
* fix(setup.py): remove encoding param
* fix(tests): use strict equality assertion
0.1.5 / 2016-12-12
------------------
* fix(matchers): fix matching issue in URL.
* refactor(assertion): regex expression based matching must be explicitly enabled.
* feat(tests): add initial matchers tests.
0.1.4 / 2016-12-08
------------------
* refactor(README): minor changes
* fix(setup.py): lint error
* fix(#32): use explicit encoding while reading files in setup.py
0.1.3 / 2016-12-08
------------------
* fix(core): several bug fixes.
* feat(core): add pending features and major refactors.
* feat(matchers): use ``unittest.TestCase`` matching engine by default.
0.1.2 / 2016-12-01
------------------
* fix(matchers): runtime missing variable.
0.1.1 / 2016-12-01
------------------
* fix: Python 2 dictionary iteration syntax.
* feat(docs): add more examples.
* fix(matchers): better regular expression comparison support.
0.1.0 / 2016-11-30
------------------
* First version (still beta)
0.1.0-rc.1 / 2016-11-27
-----------------------
* First release candidate version (still beta)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
pook-0.2.5.tar.gz
(40.4 kB
view hashes)
Built Distribution
pook-0.2.5-py2.py3-none-any.whl
(57.4 kB
view hashes)