From a65aa3b56bc40b13f0a7a7b5d81c08834fd0004d Mon Sep 17 00:00:00 2001 From: huangsong Date: Wed, 9 Oct 2024 15:39:19 +0800 Subject: [PATCH] use enum to define the client type --- README.md | 9 +-- example/protocol.py | 2 +- example/use_config_to_init.py | 26 +++++--- pydantic_client/__init__.py | 10 +-- pydantic_client/factory.py | 37 ----------- pydantic_client/main.py | 81 +++++++++---------------- pydantic_client/schema/client_config.py | 15 +++-- pyproject.toml | 2 +- tests/conftest.py | 18 +++--- 9 files changed, 78 insertions(+), 122 deletions(-) delete mode 100644 pydantic_client/factory.py diff --git a/README.md b/README.md index a83a4dd..69a616a 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Only support the json response. ```python from pydantic import BaseModel -from pydantic_client import delete, get, post, put, pydantic_client_factory +from pydantic_client import delete, get, post, put, pydantic_client_manager from pydantic_client import ClientConfig @@ -33,9 +33,8 @@ class Book(BaseModel): age: int -@pydantic_client_factory.register( +@pydantic_client_manager.register( ClientConfig( - client_type="requests", base_url="https://example.com", headers={"Authorization": "Bearer abcdefg"}, timeout=10 @@ -62,7 +61,7 @@ class WebClient: ... -client = pydantic_client_factory.get_client() +client = pydantic_client_manager.get() book: Book = client.get_book(1) ``` @@ -74,3 +73,5 @@ And see the examples to get more examples. ### v1.0.0: refactor all the code, to be simple. remove the group client. ### v1.0.1: simple to use. + +### v1.0.2: use enum to define the client type. diff --git a/example/protocol.py b/example/protocol.py index 07ce6ba..f9dc2db 100644 --- a/example/protocol.py +++ b/example/protocol.py @@ -36,5 +36,5 @@ def change_book(self, book_id: int) -> Book: class AuthorProtocol: @get("/authors") - def get_author(self) -> Author: + async def get_author(self) -> Author: ... diff --git a/example/use_config_to_init.py b/example/use_config_to_init.py index 30e7a6b..7915cde 100644 --- a/example/use_config_to_init.py +++ b/example/use_config_to_init.py @@ -1,17 +1,25 @@ from example.protocol import BookProtocol, Book, AuthorProtocol, Author -from pydantic_client import pydantic_client_factory, ClientConfig +from pydantic_client import pydantic_client_manager, ClientConfig, ClientType -if __name__ == "__main__": + +def get_book(): cfg = ClientConfig( - client_type="requests", base_url="https://example.com/api" ) + pydantic_client_manager.register(cfg)(BookProtocol) + client = pydantic_client_manager.get(BookProtocol) + book: Book = client.get_book(1, "name") + print(book) - pydantic_client_factory.register(cfg)(BookProtocol) - pydantic_client_factory.register(cfg)(AuthorProtocol) - client = pydantic_client_factory.get_client(BookProtocol) - book: Book = client.get_book(1, "name") +async def get_author(): + cfg_2 = ClientConfig( + client_type=ClientType.httpx, + base_url="https://example.com/api" + ) + + pydantic_client_manager.register(cfg_2)(AuthorProtocol) - author_client = pydantic_client_factory.get_client(AuthorProtocol) - author: Author = author_client.get_author() + author_client = pydantic_client_manager.get(AuthorProtocol) + author: Author = await author_client.get_author() + print(author) diff --git a/pydantic_client/__init__.py b/pydantic_client/__init__.py index f993987..0ff5528 100644 --- a/pydantic_client/__init__.py +++ b/pydantic_client/__init__.py @@ -1,13 +1,13 @@ from pydantic_client.decorators import delete, get, patch, post, put -from pydantic_client.factory import PydanticClientFactory -from pydantic_client.main import PydanticClient -from pydantic_client.schema.client_config import ClientConfig +from pydantic_client.main import PydanticClientManager +from pydantic_client.schema.client_config import ClientConfig, ClientType -pydantic_client_factory = PydanticClientFactory() +pydantic_client_manager = PydanticClientManager() __all__ = [ - "pydantic_client_factory", + "pydantic_client_manager", "ClientConfig", + "ClientType", "patch", "get", "post", diff --git a/pydantic_client/factory.py b/pydantic_client/factory.py deleted file mode 100644 index ca15e42..0000000 --- a/pydantic_client/factory.py +++ /dev/null @@ -1,37 +0,0 @@ -from pydantic_client.schema.client_config import ClientConfig - - -class PydanticClientFactory: - def __init__(self): - # name: client_class - self.pydantic_clients = {} - # self.config = config - - def register( - self, - client_config: ClientConfig - ): - def wrapper(protocol_class): - from pydantic_client.main import PydanticClient - - client = PydanticClient(client_config) - - self.pydantic_clients[protocol_class] = client \ - .bind_client(client_config.get_client()) \ - .bind_protocol(protocol_class) \ - .build() - - return protocol_class - - return wrapper - - def get_client(self, protocol_class=None): - if len(self.pydantic_clients) == 1: - return self.pydantic_clients[list(self.pydantic_clients.keys())[0]] - if not protocol_class: - raise ValueError( - "Multiple clients exists, protocol_class is required") - client = self.pydantic_clients.get(protocol_class) - if not client: - raise ValueError(f"client for {protocol_class} not found") - return client diff --git a/pydantic_client/main.py b/pydantic_client/main.py index 05185f8..4567555 100644 --- a/pydantic_client/main.py +++ b/pydantic_client/main.py @@ -1,56 +1,33 @@ -from typing import Type - -from pydantic_client.clients.abstract_client import AbstractClient from pydantic_client.container import container from pydantic_client.schema.client_config import ClientConfig -class PydanticClient: - """ - - class User(BaseModel): - id: int - name: str - - class WebClient: - @get("/users/{user_id}") - def get_user(self, user_id: int) -> User: - ... - - web_client = PydanticClient.from_toml(...) - .bind_client(RequestsClient) - .bind_protocol(WebClient) - .build() - - # or - - client: WebClient = PydanticClient( - ClientConfig( - base_url="https://example.com", - headers={"Authorization": "Bearer abcdefg"}, - timeout=10 - ) - ).bind_client(RequestsClient) - .bind_protocol(WebClient) - .build() - - user: User = web_client.get_user(123) - - """ - - def __init__(self, config: ClientConfig): - self.config = config - self.client = None - self.protocol = None - - def bind_client(self, client_class: Type[AbstractClient]): - self.client = client_class(self.config) - return self - - def bind_protocol(self, protocol_class, *args, **kwargs): - self.protocol = protocol_class(*args, **kwargs) - return self - - def build(self): - container.bind_protocol(self.protocol, self.client) - return self.protocol +class PydanticClientManager: + def __init__(self): + # proto_class + self.pydantic_clients = {} + # self.config = config + + def register( + self, + client_config: ClientConfig + ): + def wrapper(protocol_class): + web_client = client_config.get_client()(client_config) + protocol = protocol_class() + container.bind_protocol(protocol, web_client) + self.pydantic_clients[protocol_class] = protocol + return protocol_class + + return wrapper + + def get(self, protocol_class=None): + if len(self.pydantic_clients) == 1: + return self.pydantic_clients[list(self.pydantic_clients.keys())[0]] + if not protocol_class: + raise ValueError( + "Multiple clients exists, protocol_class is required") + client = self.pydantic_clients.get(protocol_class) + if not client: + raise ValueError(f"client for {protocol_class} not found") + return client diff --git a/pydantic_client/schema/client_config.py b/pydantic_client/schema/client_config.py index dee9edc..928ed33 100644 --- a/pydantic_client/schema/client_config.py +++ b/pydantic_client/schema/client_config.py @@ -1,16 +1,23 @@ import logging -from typing import Dict, Any, Optional, Literal +from enum import Enum +from typing import Dict, Any, Optional from pydantic import BaseModel logger = logging.getLogger(__name__) +class ClientType(str, Enum): + requests = "requests" + httpx = "httpx" + aiohttp = "aiohttp" + + class ClientConfig(BaseModel): # unique name for the client name: Optional[str] = None # requests, httpx, aiohttp - client_type: Literal["requests", "httpx", "aiohttp"] = "requests" + client_type: ClientType = ClientType.requests base_url: str headers: Dict[str, Any] = {} # only httpx support http2 @@ -18,10 +25,10 @@ class ClientConfig(BaseModel): timeout: Optional[int] = None def get_client(self): - if self.client_type == "requests": + if self.client_type.value == "requests": from pydantic_client.clients import RequestsClient return RequestsClient - elif self.client_type == "httpx": + elif self.client_type.value == "httpx": from pydantic_client.clients import HttpxClient return HttpxClient else: diff --git a/pyproject.toml b/pyproject.toml index affa25d..5cb9d70 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pydantic-client" -version = "1.0.1" +version = "1.0.2" description = "Http client base pydantic, with requests or aiohttp" authors = ["ponytailer "] readme = "README.md" diff --git a/tests/conftest.py b/tests/conftest.py index c046b08..d113b9e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,7 @@ import pytest from pydantic_client import delete, get, patch, post, put, \ - ClientConfig, pydantic_client_factory + ClientConfig, pydantic_client_manager, ClientType from tests.book import Book server_url = "http://localhost:12098" @@ -12,19 +12,19 @@ ) config_2 = ClientConfig( - client_type="httpx", + client_type=ClientType.httpx, base_url=server_url, timeout=10 ) config_3 = ClientConfig( - client_type="aiohttp", + client_type=ClientType.aiohttp, base_url=server_url, timeout=10 ) -@pydantic_client_factory.register(config_1) +@pydantic_client_manager.register(config_1) class R: @get("/books/{book_id}?query={query}") @@ -58,7 +58,7 @@ def patch_book(self, book_id: int, book: Book) -> Book: ... -@pydantic_client_factory.register(config_3) +@pydantic_client_manager.register(config_3) class AsyncR: @get("/books/{book_id}?query={query}") async def get_book(self, book_id: int, query: str) -> Book: @@ -91,7 +91,7 @@ async def patch_book(self, book_id: int, book: Book) -> Book: ... -@pydantic_client_factory.register(config_2) +@pydantic_client_manager.register(config_2) class HttpxR(AsyncR): ... @@ -127,8 +127,8 @@ def start_server(): @pytest.fixture def clients(fastapi_server_url): - client_1 = pydantic_client_factory.get_client(R) - client_2 = pydantic_client_factory.get_client(AsyncR) - client_3 = pydantic_client_factory.get_client(HttpxR) + client_1 = pydantic_client_manager.get(R) + client_2 = pydantic_client_manager.get(AsyncR) + client_3 = pydantic_client_manager.get(HttpxR) yield client_1, client_2, client_3