Skip to content

Commit

Permalink
Merge pull request #107 from quantmind/ls-mypy
Browse files Browse the repository at this point in the history
Full typing
  • Loading branch information
lsbardel authored Oct 26, 2024
2 parents 9a3f56c + 650abb5 commit d5d171b
Show file tree
Hide file tree
Showing 18 changed files with 843 additions and 637 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:

strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
python-version: ["3.10", "3.11", "3.12", "3.13"]

steps:
- uses: actions/checkout@v4
Expand Down
8 changes: 3 additions & 5 deletions dev/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
version: "3"

services:

kong-database:
Expand All @@ -16,7 +14,7 @@ services:
command: ["postgres", "-c", "fsync=off", "-c", "full_page_writes=off"]

kong-migration-bootstrap:
image: kong:3.7
image: kong:3.8
restart: on-failure
depends_on:
- kong-database
Expand All @@ -31,7 +29,7 @@ services:
KONG_ADMIN_LISTEN: 0.0.0.0:8001

kong-migration-up:
image: kong:3.3
image: kong:3.8
restart: on-failure
depends_on:
- kong-database
Expand All @@ -47,7 +45,7 @@ services:
KONG_ADMIN_LISTEN: 0.0.0.0:8001

kong:
image: kong:3.3
image: kong:3.8
restart: always
depends_on:
- kong-database
Expand Down
2 changes: 1 addition & 1 deletion dev/lint-code
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ black kong tests ${BLACK_ARG}
echo "run ruff"
ruff check kong tests ${RUFF_ARG}
echo "run mypy"
mypy kong
mypy kong tests
2 changes: 1 addition & 1 deletion kong/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Asynchronous Kong client"""

__version__ = "3.4.0"
__version__ = "3.5.0"
9 changes: 9 additions & 0 deletions kong/acls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from .components import CrudComponent, KongEntity


class Acl(KongEntity):
pass


class Acls(CrudComponent[Acl]):
pass
16 changes: 10 additions & 6 deletions kong/auths.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@
def auth_factory(consumer: Consumer, auth_type: str) -> ConsumerAuth:
known_types = {"basic-auth": BasicAuth, "key-auth": KeyAuth}
constructor = known_types.get(auth_type, ConsumerAuth)
return constructor(consumer, auth_type)
return constructor(consumer, Auth, auth_type)


class ConsumerAuth(CrudComponent):
class Auth(KongEntity):
pass


class ConsumerAuth(CrudComponent[Auth]):
unique_field: str = ""

@property
Expand All @@ -37,13 +41,13 @@ async def get_existing_id(self, creds_config: dict) -> str | None:
except StopIteration:
return None

async def create_or_update_credentials(self, creds_config: dict) -> KongEntity:
async def create_or_update_credentials(self, creds_config: dict) -> Auth:
if existing_id := await self.get_existing_id(creds_config):
return await self.update_credentials(existing_id, data=creds_config)
else:
return await self.create_credentials(data=creds_config)

async def update_credentials(self, id_: str, **kw: Any) -> KongEntity:
async def update_credentials(self, id_: str, **kw: Any) -> Auth:
url = f"{self.url}/{id_}"

return await self.cli.execute(
Expand All @@ -54,7 +58,7 @@ async def update_credentials(self, id_: str, **kw: Any) -> KongEntity:
**kw,
)

async def create_credentials(self, **kw: Any) -> KongEntity:
async def create_credentials(self, **kw: Any) -> Auth:
return await self.cli.execute(
self.url,
"post",
Expand All @@ -63,7 +67,7 @@ async def create_credentials(self, **kw: Any) -> KongEntity:
**kw,
)

async def get_or_create(self) -> KongEntity:
async def get_or_create(self) -> Auth:
secrets = await self.get_list(limit=1)
return secrets[0] if secrets else await self.create()

Expand Down
8 changes: 3 additions & 5 deletions kong/certificates.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
from .components import CrudComponent, KongEntity
from .snis import Snis
from .snis import Sni, Snis


class Certificate(KongEntity):
@property
def snis(self) -> Snis:
return Snis(self)
return Snis(self, Sni)


class Certificates(CrudComponent):
class Certificates(CrudComponent[Certificate]):
"""Kong TLS certificate component"""

Entity = Certificate
31 changes: 16 additions & 15 deletions kong/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,19 @@
from aiohttp import ClientResponse, ClientSession

from . import __version__
from .certificates import Certificates
from .components import CrudComponent, KongError, KongResponseError
from .consumers import Consumers
from .plugins import Plugins
from .routes import Routes
from .services import Services
from .snis import Snis
from .acls import Acl, Acls
from .certificates import Certificate, Certificates
from .components import KongError, KongResponseError
from .consumers import Consumer, Consumers
from .plugins import Plugin, Plugins
from .routes import Route, Routes
from .services import Service, Services
from .snis import Sni, Snis

__all__ = ["Kong", "KongError", "KongResponseError"]

DEFAULT_USER_AGENT = (
f"Python/{'.'.join(map(str, sys.version_info[:2]))} aio-kong/{__version__}"
f"python/{'.'.join(map(str, sys.version_info[:2]))} aio-kong/{__version__}"
)


Expand All @@ -41,13 +42,13 @@ def __init__(
self.session = session
self.user_agent = user_agent
self.request_kwargs = request_kwargs or {}
self.services = Services(self)
self.routes = Routes(self)
self.plugins = Plugins(self)
self.consumers = Consumers(self)
self.certificates = Certificates(self)
self.acls = CrudComponent(self, "acls")
self.snis = Snis(self)
self.services = Services(self, Service)
self.routes = Routes(self, Route)
self.plugins = Plugins(self, Plugin)
self.consumers = Consumers(self, Consumer)
self.certificates = Certificates(self, Certificate)
self.acls = Acls(self, Acl)
self.snis = Snis(self, Sni)

def __repr__(self) -> str:
return self.url
Expand Down
39 changes: 26 additions & 13 deletions kong/components.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
from __future__ import annotations

import json
from typing import TYPE_CHECKING, Any, AsyncIterator, Iterator, Mapping
from typing import (
TYPE_CHECKING,
Any,
AsyncIterator,
Generic,
Iterator,
Mapping,
TypeVar,
)

from aiohttp import ClientResponse

Expand Down Expand Up @@ -90,11 +98,16 @@ async def execute(self, url: str, method: str = "", **params: Any) -> Any:
return await self.root.execute(url, method, **params)


class CrudComponent:
Entity = KongEntity
Entity = TypeVar("Entity", bound=KongEntity)

def __init__(self, root: Kong | KongEntity, name: str = "") -> None:

class CrudComponent(Generic[Entity]):

def __init__(
self, root: Kong | KongEntity, factory: type[Entity], name: str = ""
) -> None:
self.root = root
self.factory = factory
self.name = name or self.__class__.__name__.lower()

def __repr__(self) -> str:
Expand All @@ -121,7 +134,7 @@ async def execute(self, url: str, method: str = "", **kwargs: Any) -> Any:
async def apply_json(self, data: JsonType, clear: bool = True) -> list:
raise NotImplementedError

async def paginate(self, **params: Any) -> AsyncIterator[KongEntity]:
async def paginate(self, **params: Any) -> AsyncIterator[Entity]:
url = self.list_create_url()
next_ = url
exec_params = as_params(**params)
Expand All @@ -133,26 +146,26 @@ async def paginate(self, **params: Any) -> AsyncIterator[KongEntity]:
for d in data["data"]:
yield self.wrap(d)

async def get_list(self, **params: Any) -> list[KongEntity]:
async def get_list(self, **params: Any) -> list[Entity]:
url = self.list_create_url()
return await self.execute(url, params=as_params(**params), wrap=self.wrap_list)

async def get_full_list(self, **params: Any) -> list[KongEntity]:
async def get_full_list(self, **params: Any) -> list[Entity]:
return [d async for d in self.paginate(**params)]

async def get(self, id_: str | UUID) -> KongEntity:
async def get(self, id_: str | UUID) -> Entity:
url = f"{self.url}/{uid(id_)}"
return await self.execute(url, wrap=self.wrap)

async def has(self, id_: str | UUID) -> bool:
url = f"{self.url}/{uid(id_)}"
return await self.execute(url, "get", callback=self.head)

async def create(self, **params: Any) -> KongEntity:
async def create(self, **params: Any) -> Entity:
url = self.list_create_url()
return await self.execute(url, "post", json=params, wrap=self.wrap)

async def update(self, id_: str | UUID, **params: Any) -> KongEntity:
async def update(self, id_: str | UUID, **params: Any) -> Entity:
url = f"{self.url}/{uid(id_)}"
return await self.execute(url, "patch", json=params, wrap=self.wrap)

Expand All @@ -175,10 +188,10 @@ async def head(self, response: ClientResponse) -> bool:
else: # pragma: no cover
raise KongResponseError(response)

def wrap(self, data: dict) -> KongEntity:
return self.Entity(self, data)
def wrap(self, data: dict) -> Entity:
return self.factory(self, data)

def wrap_list(self, data: dict) -> list[KongEntity]:
def wrap_list(self, data: dict) -> list[Entity]:
return [self.wrap(d) for d in data["data"]]

def list_create_url(self) -> str:
Expand Down
8 changes: 4 additions & 4 deletions kong/consumers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from typing import cast

from .acls import Acl, Acls
from .auths import ConsumerAuth, auth_factory
from .components import CrudComponent, JsonType, KongError, KongResponseError
from .plugins import KongEntityWithPlugins
Expand All @@ -11,8 +12,8 @@ def username(self) -> str:
return self.data.get("username", "")

@property
def acls(self) -> CrudComponent:
return CrudComponent(self, "acls")
def acls(self) -> Acls:
return Acls(self, Acl)

@property
def jwts(self) -> ConsumerAuth:
Expand All @@ -27,8 +28,7 @@ def basicauths(self) -> ConsumerAuth:
return auth_factory(self, "basic-auth")


class Consumers(CrudComponent):
Entity = Consumer
class Consumers(CrudComponent[Consumer]):

async def apply_credentials(self, auths: list[dict], consumer: Consumer) -> None:
for auth_data in auths:
Expand Down
12 changes: 8 additions & 4 deletions kong/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@
from .client import Kong


class Plugins(CrudComponent):
async def create(self, **params: Any) -> KongEntity:
class Plugin(KongEntity):
pass


class Plugins(CrudComponent[Plugin]):
async def create(self, **params: Any) -> Plugin:
params = await self.preprocess_parameters(params)
return await super().create(**params)

Expand Down Expand Up @@ -50,15 +54,15 @@ async def preprocess_parameters(self, params: dict) -> dict:
params = await preprocessor(self.cli, params)
return params

async def update(self, id_: str | UUID, **params: Any) -> KongEntity:
async def update(self, id_: str | UUID, **params: Any) -> Plugin:
params = await self.preprocess_parameters(params)
return await super().update(id_, **params)


class KongEntityWithPlugins(KongEntity):
@property
def plugins(self) -> Plugins:
return Plugins(self)
return Plugins(self, Plugin)


async def consumer_id_from_username(cli: Kong, params: dict) -> dict:
Expand Down
6 changes: 4 additions & 2 deletions kong/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@
from .utils import as_list


class Route(KongEntityWithPlugins):
pass


class Routes(CrudComponent):
"""Kong Routes
Routes are always associated with a Service
"""

Entity = KongEntityWithPlugins

async def delete(self, id_: str | UUID) -> bool:
route = cast(KongEntityWithPlugins, self.wrap({"id": id_}))
await route.plugins.delete_all()
Expand Down
8 changes: 3 additions & 5 deletions kong/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from .components import UUID, CrudComponent, JsonType, KongError
from .plugins import KongEntityWithPlugins
from .routes import Routes
from .routes import Route, Routes
from .utils import local_ip

REMOVE = frozenset(("absent", "remove"))
Expand All @@ -14,18 +14,16 @@ class Service(KongEntityWithPlugins):

@property
def routes(self) -> Routes:
return Routes(self)
return Routes(self, Route)

@property
def host(self) -> str:
return self.data.get("host", "")


class Services(CrudComponent):
class Services(CrudComponent[Service]):
"""Kong Services"""

Entity = Service

async def delete(self, id_: str | UUID) -> bool:
srv = cast(Service, self.wrap({"id": id_}))
await srv.routes.delete_all()
Expand Down
8 changes: 6 additions & 2 deletions kong/snis.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from .components import CrudComponent, JsonType
from .components import CrudComponent, JsonType, KongEntity


class Snis(CrudComponent):
class Sni(KongEntity):
pass


class Snis(CrudComponent[Sni]):
"""Kong SNI API component"""

async def apply_json(self, data: JsonType, clear: bool = True) -> list:
Expand Down
Loading

0 comments on commit d5d171b

Please sign in to comment.