diff --git a/pydantic_client/clients/aiohttp_client.py b/pydantic_client/clients/aiohttp_client.py index 74f9542..684dd25 100644 --- a/pydantic_client/clients/aiohttp_client.py +++ b/pydantic_client/clients/aiohttp_client.py @@ -10,13 +10,12 @@ class AIOHttpClient(AbstractClient): runner_class: Type[Proxy] = AsyncClientProxy - def __init__(self, base_url: str, session: ClientSession = ClientSession()): - self.session = session + def __init__(self, base_url: str): self.base_url = base_url async def do_request(self, request: HttpRequest) -> Any: data, json = self.parse_request(request) - async with self.session as session: + async with ClientSession() as session: try: req = session.request( url=self.base_url + request.url, diff --git a/pydantic_client/proxy.py b/pydantic_client/proxy.py index 6581c1b..b30b056 100644 --- a/pydantic_client/proxy.py +++ b/pydantic_client/proxy.py @@ -2,7 +2,7 @@ import logging import re from typing import Any, Dict -from urllib.parse import urlparse +from urllib.parse import urlparse, parse_qsl from pydantic import BaseModel @@ -32,8 +32,12 @@ def _apply_args(self, *args, **kwargs) -> Dict[str, Any]: def _get_url(self, args) -> str: keys = self.querystring_pattern.findall(self.method_info.url) - query_args = {arg: args.pop(arg) for arg, val in args.items() if + query_args = {arg: val for arg, val in args.items() if arg in keys and val} + + for key in keys: + args.pop(key, None) + url = self.method_info.url.format(**query_args) url_result = urlparse(url) @@ -41,7 +45,7 @@ def _get_url(self, args) -> str: logger.warning(f"Not formatted args in url path: {url}") querystring = "&".join( - f"{k}={v}" for k, v in url_result.query if "{" not in v + f"{k}={v}" for k, v in parse_qsl(url_result.query) if "{" not in v ) return url_result.path + "?" + querystring if querystring else url_result.path diff --git a/pyproject.toml b/pyproject.toml index 262c5d6..83ffd9b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pydantic-client" -version = "0.1.7" +version = "0.1.8" description = "Http client base pydantic, with requests or aiohttp" authors = ["ponytailer "] readme = "README.md" diff --git a/tests/book.py b/tests/book.py new file mode 100644 index 0000000..3393488 --- /dev/null +++ b/tests/book.py @@ -0,0 +1,6 @@ +from pydantic import BaseModel + + +class Book(BaseModel): + name: str + age: int diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..dee1b4e --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,36 @@ +import pytest + +from pydantic_client import delete, get, post, put, RequestsClient +from tests.book import Book + + +class R(RequestsClient): + def __init__(self): + super().__init__("http://localhost") + + @get("/books/{book_id}?query={query}") + def get_book(self, book_id: int, query: str) -> Book: + ... + + @get("/books/{book_id}") + def get_raw_book(self, book_id: int): + ... + + @post("/books", form_body=True) + def create_book_form(self, book: Book) -> Book: + """ will post the form with book""" + ... + + @put("/books/{book_id}") + def change_book(self, book_id: int, book: Book) -> Book: + """will put the json body""" + ... + + @delete("/books/{book_id}") + def delete_book(self, book_id: int) -> Book: + ... + + +@pytest.fixture +def client(): + yield R() diff --git a/tests/helpers.py b/tests/helpers.py new file mode 100644 index 0000000..3ed7094 --- /dev/null +++ b/tests/helpers.py @@ -0,0 +1,42 @@ +import requests + + +class MockResponse: + + def __init__(self, code, response): + self.code = code + self.response = response + + @property + def ok(self): + return int(self.code) == 200 + + def json(self): + return self.response + + def raise_for_status(self): + if not self.ok: + raise + + @property + def status_code(self): + return self.code + + +def mock_requests( + monkeypatch, + response=None, + code=200 +): + + def mock_call(mock_return_value): + def mock(*args, **kwargs): + return MockResponse(**mock_return_value) + + return mock + + return_value = {"code": code, "response": response or {}} + monkeypatch.setattr( + requests.Session, "request", + mock_call(return_value) + ) diff --git a/tests/test_client.py b/tests/test_client.py index 99a5c07..8dc02b1 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,45 +1,40 @@ -import unittest +import pytest -from pydantic import BaseModel +from tests.book import Book +from tests.helpers import mock_requests -from pydantic_client import delete, get, post, put, RequestsClient +@pytest.fixture +def mock_get_book(monkeypatch): + mock_resp = {"name": "name", "age": 1} + yield mock_requests(monkeypatch, response=mock_resp) -class Book(BaseModel): - name: str - age: int +def test_get(client, mock_get_book): + book: Book = client.get_book(1, "world") + assert book.name == "name" + assert book.age == 1 -class R(RequestsClient): - def __init__(self): - super().__init__("http://localhost") - @get("/books/{book_id}?query={query}") - def get_book(self, book_id: int, query: str) -> Book: - ... +def test_get_raw(client, mock_get_book): + book: dict = client.get_raw_book(1) + assert book["name"] == "name" + assert book["age"] == 1 - @post("/books", form_body=True) - def create_book_form(self, book: Book) -> Book: - """ will post the form with book""" - ... - @put("/books/{book_id}") - def change_book(self, book_id: int, book: Book) -> Book: - """will put the json body""" - ... +def test_post_form(client, mock_get_book): + book: Book = client.create_book_form(Book(name="name", age=2)) + assert book.name == "name" + assert book.age == 1 - @delete("/books/{book_id}") - def change_book(self, book_id: int) -> Book: - ... +def test_put(client, mock_get_book): + book: Book = client.change_book(1, Book(name="name", age=2)) + assert book.name == "name" + assert book.age == 1 -class TestClient(unittest.TestCase): - def setUp(self): - self.test_client = R() - - def tearDown(self): - self.test_client = None - - def test_get(self): - ... +def test_delete(client, mock_get_book): + book: Book = client.delete_book(1) + assert book.name == "name" + assert book.age == 1