Skip to content

Commit

Permalink
Merge pull request #36 from unmade/update-docs
Browse files Browse the repository at this point in the history
Update docs
  • Loading branch information
unmade authored Feb 19, 2020
2 parents 59f850d + 7db965f commit 2480cda
Show file tree
Hide file tree
Showing 11 changed files with 142 additions and 24 deletions.
79 changes: 79 additions & 0 deletions docs/auth.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
.. toctree::
:name: auth


==============
Authentication
==============

This page describes how you can use various kinds of authentication with
*apiwrappers*.

Basic Authentication
====================

Making request with HTTP Basic Auth is rather straightforward:

.. code-block:: python
from apiwrappers import Method, Request
Request(..., auth=("user", "pass"))
Token Authentication
====================

To make a request with a Token Based Authentication:

.. code-block:: python
from apiwrappers import Method, Request
from apiwrappers.auth import TokenAuth
Request(..., auth=TokenAuth("your_token", kind="JWT"))
Custom Authentication
=====================

You can add your own authentication mechanism relatively easy.

If you don't need to make any external calls, then you can define a callable
that returns a dictionary with authorization headers.

For example, this is simple authentication class:

.. code-block:: python
from typing import Dict
class ProxyAuth:
def __call__(self) -> Dict[str, str]:
return {"Proxy-Authorization": "<type> <credentials>"}
Authentication Flows
--------------------

Sometimes we need to make additional calls to get credentials.

*apiwrappers* allows you to do just that:

.. code-block:: python
from typing import Generator, Dict
from apiwrappers import Request, Response
class CustomAuthFlow:
def __call__(self) -> Generator[Request, Response, Dict[str, str]]:
# you can issue as many request as you needed
# this is how you issue a request
response = yield Request(...)
# response is available immediately for processing
return {"Authorization": response.json()["token"]}
*Note, that a function now is generator function and you can yield as many
request as you needed, but you should always return a dictionary with
authentication headers.*
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ Table of Contents

building-an-api-client
drivers
auth
middleware
experimental-features
api
44 changes: 38 additions & 6 deletions docs/middleware.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ The most simple way is to write a middleware as function:
return middleware
Since middleware is used by drivers, and the one we've written can be used
only by regular driver, we also need to supply an async implementation:
only by regular driver, we also need to provide an async implementation:

.. code-block:: python
Expand All @@ -57,10 +57,10 @@ only by regular driver, we also need to supply an async implementation:
return middleware
As you can see, the only difference is that in async middleware we have to await
the handler call
As you can see, the only difference is that in async middleware we have to
await the handler call.

To help us reduce this code duplication *apiwrappers* supplies a
To help us reduce this code duplication *apiwrappers* provides a
``BaseMiddleware`` class. Subclassing one you can then
override it hook methods like that:

Expand Down Expand Up @@ -92,11 +92,43 @@ Using middleware

Middleware are used by drivers and each driver accepts a list of middleware.

Although, middleware we defined earlier literally does nothing, it still can be used like that:
Although, middleware we defined earlier literally does nothing,
it still can be used like that:

.. code-block:: python
from apiwrappers import make_driver
make_driver("requests", SimpleMiddleware)
driver = make_driver("requests", SimpleMiddleware)
# RequestsDriver(Authorization, SimpleMiddleware, ...)
*Note, that even we provide only ``SimpleMiddleware`` the driver also has
``Authorization`` middleware. That's because some drivers have middleware
that should always be present.*

You can also change driver middleware after creation by simply reassigning
``driver.middleware`` attribute:

.. code-block:: python
driver.middleware = []
# RequestsDriver(Authorization, ...)
The order of the default middleware can be overridden by explicitly
specifying it:

.. code-block:: python
driver.middleware = [SimpleMiddleware, Authorization]
# RequestsDriver(SimpleMiddleware, Authorization, ...)
Middleware order
================

The order of middleware matters because a middleware can depend on other
middleware.

Before making actual request, middleware are executed in the order
they are defined.
After getting the response middleware are executed in the reverse order.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "apiwrappers"
version = "0.1.0-beta"
version = "0.1.0-beta.1"
description = "apiwrappers is a library for building API clients that work both with regular and async code"
keywords = ["api", "wrapper", "http", "client"]
readme = "README.rst"
Expand Down
4 changes: 2 additions & 2 deletions src/apiwrappers/drivers/aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from apiwrappers import exceptions, utils
from apiwrappers.entities import Request, Response
from apiwrappers.middleware import MiddlewareChain
from apiwrappers.middleware.auth import Authorization
from apiwrappers.middleware.auth import Authentication
from apiwrappers.protocols import AsyncMiddleware
from apiwrappers.structures import CaseInsensitiveDict, NoValue
from apiwrappers.typedefs import QueryParams, Timeout
Expand All @@ -16,7 +16,7 @@


class AioHttpDriver:
middleware = MiddlewareChain(Authorization)
middleware = MiddlewareChain(Authentication)

def __init__(
self,
Expand Down
4 changes: 2 additions & 2 deletions src/apiwrappers/drivers/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from apiwrappers import exceptions, utils
from apiwrappers.entities import Request, Response
from apiwrappers.middleware import MiddlewareChain
from apiwrappers.middleware.auth import Authorization
from apiwrappers.middleware.auth import Authentication
from apiwrappers.protocols import Middleware
from apiwrappers.structures import CaseInsensitiveDict, NoValue
from apiwrappers.typedefs import Timeout
Expand All @@ -15,7 +15,7 @@


class RequestsDriver:
middleware = MiddlewareChain(Authorization)
middleware = MiddlewareChain(Authentication)

def __init__(
self,
Expand Down
16 changes: 13 additions & 3 deletions src/apiwrappers/entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,17 @@
import json
from dataclasses import dataclass, field
from http.cookies import SimpleCookie
from typing import Any, Callable, Dict, Generator, Mapping, Optional, Tuple, Union, cast
from typing import (
Any,
Callable,
Dict,
Generator,
MutableMapping,
Optional,
Tuple,
Union,
cast,
)

from apiwrappers.structures import CaseInsensitiveDict
from apiwrappers.typedefs import JSON, Data, QueryParams
Expand Down Expand Up @@ -89,8 +99,8 @@ class Request:
host: str
path: str
query_params: QueryParams = field(default_factory=dict)
headers: Mapping[str, str] = field(default_factory=dict)
cookies: Mapping[str, str] = field(default_factory=dict)
headers: MutableMapping[str, str] = field(default_factory=dict)
cookies: MutableMapping[str, str] = field(default_factory=dict)
auth: _Auth = None
data: Data = None
json: JSON = None
Expand Down
8 changes: 2 additions & 6 deletions src/apiwrappers/middleware/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from apiwrappers.protocols import AsyncHandler, Handler


class Authorization(BaseMiddleware):
class Authentication(BaseMiddleware):
def call_next(
self, handler: Handler, request: Request, *args, **kwargs,
) -> Response:
Expand Down Expand Up @@ -50,8 +50,4 @@ def set_auth_headers(request) -> Generator[Request, Response, None]:
value.send(auth_response)
except StopIteration as exc:
value = exc.value
# Since header is Mapping it is possible it doesn't have
# any method to set an item
headers = dict(request.headers)
headers.update(value)
request.headers = headers
request.headers.update(value)
2 changes: 1 addition & 1 deletion tests/test_drivers/test_aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ async def test_representation_with_middleware():
driver = AioHttpDriver(RequestMiddleware, ResponseMiddleware)
assert repr(driver) == (
"AioHttpDriver("
"Authorization, RequestMiddleware, ResponseMiddleware, "
"Authentication, RequestMiddleware, ResponseMiddleware, "
"timeout=300, verify_ssl=True"
")"
)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_drivers/test_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def test_representation_with_middleware():
driver = RequestsDriver(RequestMiddleware, ResponseMiddleware)
assert repr(driver) == (
"RequestsDriver("
"Authorization, RequestMiddleware, ResponseMiddleware, "
"Authentication, RequestMiddleware, ResponseMiddleware, "
"timeout=300, verify_ssl=True"
")"
)
Expand Down
4 changes: 2 additions & 2 deletions tests/test_middleware/test_chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

class First(BaseMiddleware):
def process_request(self, request: Request) -> Request:
request.headers = {"x-request-id": "1"}
request.headers["x-request-id"] = "1"
return super().process_request(request)

def process_response(self, response: Response) -> Response:
Expand All @@ -18,7 +18,7 @@ def process_response(self, response: Response) -> Response:

class Second(BaseMiddleware):
def process_request(self, request: Request) -> Request:
request.headers["x-request-id"] += "2" # type: ignore
request.headers["x-request-id"] += "2"
return super().process_request(request)

def process_response(self, response: Response) -> Response:
Expand Down

0 comments on commit 2480cda

Please sign in to comment.