Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.0.2 #15

Merged
merged 2 commits into from
Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 10 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ pip install "loamy[uvloop]"
The package can be imported as shown:

```python
from loamy.session import Clump, RequestMap, RequestResults
from loamy.session import Clump, RequestMap, RequestResponse
```

| Class | Description|
| ----- | -----------|
| `Clump` | Container object that stores collection of requests (type RequestMap) to send |
| `RequestMap` | Container object that stores all info about an individual request to send |
| `RequestResults` | Container object that stores the request responses and any exceptions raised |
| `RequestResponse` | Container object that stores the request response and any exception raised for each individual request |


### Example
Expand All @@ -59,19 +59,19 @@ req3 = RequestMap(

# Create Clump and call sendRequests()
session = Clump(requests=[req1, req2, req3])
reqResps: RequestResults = session.sendRequests(return_exceptions=True)
responses: list[RequestResponse] = session.sendRequests(return_exceptions=True)

# Handle exceptions raised for individual requests
if len(reqResps.taskExceptions) > 0:
print("Handling exceptions")

# Handle responses for individual requests
for resp in requestResponses:
for resp in responses:
httpVerb = resp.requestMap.httpOperation
print(f"Evaluating response for {httpVerb} request to {resp.requestMap.url}")
print(f"Status Code: {resp.statusCode}")
if resp.body is not None:
print(resp.body)
if resp.error is not None:
print("Exception raised for request")
else:
print(f"Status Code: {resp.statusCode}")
if resp.body is not None:
print(resp.body)
```

#### RequestMap Class
Expand All @@ -94,12 +94,3 @@ class RequestResponse(msgspec.Struct):
statusCode: int
body: dict | None = None
```

#### RequestResults Class

```python
@dataclass
class RequestResults:
requestResponses: list[RequestResponse]
taskExceptions: list[BaseException]
```
928 changes: 652 additions & 276 deletions poetry.lock

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[tool.poetry]
name = "loamy"
version = "0.0.2.alpha"
version = "0.0.2"
description = ""
authors = ["Zach Fuller <[email protected]>"]
readme = "README.md"
license = "MIT"
repository = "https://github.com/fullerzz/zConcurrent"
repository = "https://github.com/fullerzz/loamy"
packages = [{include = "loamy", from = "src"}]

[tool.poetry.dependencies]
Expand All @@ -20,6 +20,9 @@ ruff = "~0.1.4"
pre-commit = "3.5.0"
pytest = "^7.4.3"
pytest-asyncio = "^0.21.1"
blacksheep = "^2.0.1"
uvicorn = "^0.24.0.post1"
mypy = "^1.7.1"

[tool.poetry.extras]
uvloop = ["uvloop"]
Expand Down
489 changes: 245 additions & 244 deletions requirements.txt

Large diffs are not rendered by default.

65 changes: 23 additions & 42 deletions src/loamy/session.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import asyncio
from dataclasses import dataclass
from typing import Literal

import aiohttp
Expand All @@ -25,57 +24,52 @@ class RequestResponse(msgspec.Struct):
requestMap: RequestMap
statusCode: int
body: dict | None = None


@dataclass
class RequestResults:
requestResponses: list[RequestResponse]
taskExceptions: list[BaseException]
error: BaseException | None = None


class Clump:
def __init__(self, requests: list[RequestMap]) -> None:
self._requestMaps: list[RequestMap] = requests

def sendRequests(self, return_exceptions: bool = False) -> RequestResults:
def sendRequests(self, return_exceptions: bool = False) -> list[RequestResponse]:
return asyncRun(self._sendRequests(rtn_exc=return_exceptions))

async def _sendRequests(self, rtn_exc: bool) -> RequestResults:
async def _sendRequests(self, rtn_exc: bool) -> list[RequestResponse]:
async with aiohttp.ClientSession() as session:
httpTasks: list[asyncio.Task] = []
for req in self._requestMaps:
httpTasks.append(
asyncio.ensure_future(self._routeIndividualRequest(req, session))
)
responses: list[RequestResponse | BaseException] = await asyncio.gather(
responses: list[RequestResponse] = await asyncio.gather(
*httpTasks, return_exceptions=rtn_exc
)
requestResults: RequestResults = await _processResults(
taskResults=responses
)
return requestResults
return responses

async def _routeIndividualRequest(
self, reqMap: RequestMap, session: aiohttp.ClientSession
) -> RequestResponse:
requestResponse: RequestResponse = RequestResponse(
requestMap=reqMap, statusCode=0
)
match reqMap.httpOperation:
case "GET":
requestResponse = await self._sendGetRequest(reqMap, session)
case "POST":
requestResponse = await self._sendGetRequest(reqMap, session)
case "PUT":
requestResponse = await self._sendPutRequest(reqMap, session)
case "PATCH":
requestResponse = await self._sendPatchRequest(reqMap, session)
case "OPTIONS":
requestResponse = await self._sendOptionsRequest(reqMap, session)
case "DELETE":
requestResponse = await self._sendDeleteRequest(reqMap, session)
case _:
pass
try:
match reqMap.httpOperation:
case "GET":
requestResponse = await self._sendGetRequest(reqMap, session)
case "POST":
requestResponse = await self._sendPostRequest(reqMap, session)
case "PUT":
requestResponse = await self._sendPutRequest(reqMap, session)
case "PATCH":
requestResponse = await self._sendPatchRequest(reqMap, session)
case "OPTIONS":
requestResponse = await self._sendOptionsRequest(reqMap, session)
case "DELETE":
requestResponse = await self._sendDeleteRequest(reqMap, session)
case _:
pass
except Exception as e:
requestResponse.error = e

return requestResponse

Expand Down Expand Up @@ -171,16 +165,3 @@ async def _sendDeleteRequest(
requestMap=reqMap, statusCode=statusCode, body=body
)
return reqResponse


async def _processResults(
taskResults: list[RequestResponse | BaseException]
) -> RequestResults:
responses: list[RequestResponse] = []
taskExceptions: list[BaseException] = []
for result in taskResults:
if isinstance(result, BaseException):
taskExceptions.append(result)
else:
responses.append(result)
return RequestResults(requestResponses=responses, taskExceptions=taskExceptions)
19 changes: 19 additions & 0 deletions tests/bin/test_server/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from blacksheep import Application, Request, Response, get, json, post

app = Application()


@get("/")
async def index() -> Response:
return json({"message": "Hello, world!"})


@post("/foo")
async def post_foo(request: Request) -> Response:
data = await request.json()
return json(data)


@get("/exception")
async def mock_exception():
raise Exception("Mock exception")
60 changes: 60 additions & 0 deletions tests/test_requests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# import pytest
# from src.loamy.session import Clump, RequestMap, RequestResponse


# @pytest.fixture(scope="session")
# def request_map_collection() -> list[RequestMap]:
# requests: list[RequestMap] = []
# for i in range(0, 100):
# print(i)
# if i % 2 == 0:
# requests.append(
# RequestMap(
# url="http://localhost:44777/",
# httpOperation="GET",
# )
# )
# else:
# requests.append(
# RequestMap(
# url="http://localhost:44777/foo",
# httpOperation="POST",
# body={"foo": "bar"},
# )
# )
# return requests


# @pytest.fixture(scope="session")
# def request_map_to_trigger_exception() -> RequestMap:
# return RequestMap(
# url="http://localhost:44777/exception",
# httpOperation="GET",
# )


# def test_send_requests(request_map_collection: list[RequestMap]) -> None:
# session = Clump(requests=request_map_collection)
# responses: list[RequestResponse] = session.sendRequests()
# assert len(responses) == 100
# for response in responses:
# assert response.statusCode == 200
# assert response.error is None


# def test_send_requests_with_exceptions(
# request_map_collection: list[RequestMap],
# request_map_to_trigger_exception: RequestMap,
# ) -> None:
# requests: list[RequestMap] = request_map_collection.copy()
# requests.append(request_map_to_trigger_exception)
# session = Clump(requests=requests)
# responses: list[RequestResponse] = session.sendRequests(return_exceptions=True)
# assert len(responses) == 101
# for response in responses:
# if response.requestMap.url == "http://localhost:44777/exception":
# assert response.statusCode == 0
# assert response.error is not None
# else:
# assert response.statusCode == 200
# assert response.error is None
Loading