aioqiwi 1.1.5
Convenient and asynchronous qiwi.com api-wrapper library
License: MIT
Requires Python: >=3.7,<4.0
Keywords: asyncio,aiohttp,qiwi,api-client,aioqiwi

===========
�� aioqiwi
===========

.. image:: https://img.shields.io/badge/Python%203.7-blue.svg
    :target: https://www.python.org/
    :alt: Python-version

**Qiwi payments for humans(for healthy humans)**

Supports most of `qiwi <https://qiwi.com>`_ apis: `qiwi-maps <https://github.com/QIWI-API/qiwi-map>`_, `bills <https://developer.qiwi.com/en/bill-payments/>`_, `wallet <https://developer.qiwi.com/en/qiwi-wallet-personal/>`_

------------
Installation
------------

::

    pip install aioqiwi

---------------
�� Dependencies
---------------

+------------+----------------------------+
| Library    | Description                |
+============+============================+
|  aiohttp   | default http server        |
+------------+----------------------------+
|  pydantic  | schema validation          |
+------------+----------------------------+


**However aioqiwi is highly customizable. Example of switching json modules:**

::

    pip install orjson

.. code-block:: python

    from aioqiwi import Wallet
    from aioqiwi.core.tooling import json

    wallet = Wallet()
    wallet.tools.json_module = json.JSONModule("orjson")

--------------------
�� Dive-in Examples
--------------------

.. code:: python

    import asyncio

    from aioqiwi import Wallet

    async def qiwi():
        async with Wallet("TOKEN from https://qiwi.com/api") as w:
            w.phone_number = '+7878787878'  # phone number is not required by default, but some methods need it
            balance = await w.balance()
            print("ACCOUNTS:")
            for acc in balance.accounts:
                print(acc.alias, acc.balance)

    asyncio.run(qiwi())


--------------------
�� Handling updates
--------------------

**aioqiwi** provides user-friendly web-hooks handler


.. code:: python

    import asyncio
    from aioqiwi.wallet import WebHook, Wallet

    wallet = Wallet("...")

    @wallet.hm(lambda event: ...)
    async def payments_handler(hook: WebHook):
        print(f"{hook.payment.account} sent you {event.payment}")

    @wallet.hm()
    async def secret_payments_handler(event: WebHook):
        await something(event.payment.commission.amount)

    wallet.idle(port=8090)

When you do `Wallet::idle`, aioqiwi adds connector closing to `aiohttp.web.Application::on_shutdown` to make sure connector closes, however if you want to avoid this behaviour pass `close_connector_ate=False` to `Wallet::idle`

****************
Handler manager
****************

Handler manager `QiwiClient.handler_manager` or `qiwi_client.hm` is responsible for event-handlers registering and filtering/delivering updates to them.
There're currently two event processing strategies:
1. `core.handler.EventProcessStrategy.ORDERED` - sequential filter-check. has O(n) amplitude
2. `core.handler.EventProcessStrategy.MILKSHAKE` - as receives update, will shuffle existing handlers list. has O(n) amplitude

.. note::
    Filters results are not currently cached.

.. note::
    Some users don't want mess with web-hooks, for those fellas aioqiwi has `history_polling` [wip] in `aioqiwi.contrib`. Different approach for dealing with payment events.
    Find usage example in `examples/` directory.

---------------------------------------------------
�� Qiwi API p2p transactions(bills)
---------------------------------------------------

.. code:: python

    import asyncio
    from aioqiwi import QiwiKassa

    async def test_kassa():
        async with QiwiKassa("SECRET KEY from p2p.qiwi.com or kassa.qiwi.com") as kassa:
            sent_invoice = await kassa.new_bill(14.88, lifetime=44)
            # setting lifetime to 44 ahead today [default is 10] 45 - is max
            print("Url to pay:", sent_invoice.pay_url)
            await kassa.close()

    asyncio.run(test_kassa())


``sent_invoice.pay_url`` will redirect us to something like:

.. image:: https://imbt.ga/gO8EzaFItB


---------------------------
�� Handling bill payments
---------------------------


.. code:: python


    from aioqiwi.kassa import QiwiKassa, Notification

    kassa = QiwiKassa('PRIVATE_KEY')

    @kassa.hm(lambda bill: bill.bill.amount.currency == 'RUB')
    async def my_shiny_rubles_handler(bill_update: Notification):
        # do something
        pass

    kassa.idle()


--------------------
�� QIWI terminals
--------------------

**aioqiwi** covers qiwi's `MAPS
<https://developer.qiwi.com/ru/qiwi-map>`_ api in aioqiwi.terminals module

---------------
Connectors
---------------

QiwiClient.connector is responsible for making http requests. Current available request managers are located in `aioqiwi.core.connectors`

Default connector is `aioqiwi.core.connectors.asyncio`, but if it's no suit for you, you can easily switch to another

Example:

.. code:: python3

    from aioqiwi import Wallet
    from aioqiwi.core.connectors.aiohttp import AiohttpConnector

    wallet = Wallet("auth")
    # switch with read-to-use connector-like instance implementing
    wallet.connector = AiohttpConnector(timeout, {"user-agent": "opeka/02"})
    # or switch with aioqiwi.core.connectors.abstract.Connector compatible class
    wallet.connector = AiohttpConnector

*******************
Hacking connector
*******************

You can easily implement your own http client(connector), subclassing from `aioqiwi.core.connectors.abstract.AbstractConnector`. Take a look at "out of the box" `aiohttp` or `asyncio` sessions for the start.

-----------------------
�� Handling errors
-----------------------

******************
API request error
******************

Consider we have a `aioqiwi.wallet.Wallet` instance with a named reference `wallet` to it.
Known error when we cannot ask server for more than 50 rows in `wallet.history`. To handle that error, we simply:

.. code:: python

    from aioqiwi.exceptions import AioqiwiError
    from aioqiwi.errors import ErrorInfo

    try:
        await wallet.history(2 ** 6)  # pass rows=64, whilst constraint is 0<rows<51
    except AioqiwiError as exc:
        if exc.err:  # this feature is experimental
            exc.err: ErrorInfo = exc.err  # cast to aioqiwi.Wallet's error info
            print(exc.err.error_message)

***************
TimeoutError
***************

This is slight different error and aioqiwi should not be really responsible for it. It's usually server-side error
which makes exception that should be raised connector-specific. `asyncio.TimeoutError` is exception that is produced
by `asyncio` connector. In `aiohttp` or other connectors it may differ.

-----------------------------
⛏ return policies (types)
-----------------------------

aioqiwi's server.BaseWebHookView and requests.Requests support "return policy", it means you can get response/update in the form that suits your needs.
There're currently 5 return policies.

- NOTHING - returns nothing(note: None is python's implicit return), :note: returning nothing does not mean doing nothing, validation is done anyway
- READ_DATA - raw return once stream is read
- JSON - raw return once read data was deserialized
- MODEL - complex return once json deserialized and new model instantiated
- LIST_OF_MODELS - complex return once json deserialized as an iterable list with new instantiated models of json objects

-------------------
❓ HOW-TOs
-------------------

You can find examples in ``examples/`` directory in github repository. For start examples above should be enough.


---------------------------
�� TODOs
---------------------------

- **Tests/CI/CD**
- **Implement all qiwi wallet API methods**

-----------------
Work in progress
-----------------

- history_polling needs to be tested
- implement wallet web-hook payment verification

------------------------------------------
�� Community
------------------------------------------

**My group**
`✈️ Telegram
<https://t.me/joinchat/B2cC_hSIAiYXxqKghdguCA>`_