Skip to content

Commit

Permalink
refactor: introducing dependency injector and switched from aiohttp t…
Browse files Browse the repository at this point in the history
…o httpx client
  • Loading branch information
Guibod committed Feb 25, 2023
1 parent f5ed7dc commit 1ade77a
Show file tree
Hide file tree
Showing 20 changed files with 543 additions and 220 deletions.
24 changes: 24 additions & 0 deletions examples/scryfall.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from dependency_injector.wiring import Provide, inject

from mightstone.ass import stream_as_list
from mightstone.containers import Container
from mightstone.services.scryfall import Scryfall


@inject
def main(
scry: Scryfall = Provide[Container.scryfall],
) -> None:
found = stream_as_list(scry.search("boseiju"))

print(f"Found {len(found)} instances of Boseiju")
for card in found:
print(f" - {card}")


if __name__ == "__main__":
container = Container()
container.init_resources()
container.wire(modules=[__name__])

main()
312 changes: 306 additions & 6 deletions poetry.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ appdirs = "^1.4.4"
requests = "^2.28.2"
pillow = "^9.4.0"
cairosvg = "^2.6.0"
dependency-injector = "^4.41.0"
logging = "^0.4.9.6"
httpx = "^0.23.3"
httpx-cache = "^0.7.0"

[tool.poetry.group.docs]
optional = true
Expand Down
2 changes: 1 addition & 1 deletion src/mightstone/ass/compressor/async_file_obj.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Optional, AsyncIterable
from typing import AsyncIterable, Optional

from mightstone.ass.compressor.codecs import error_import_usage

Expand Down
3 changes: 3 additions & 0 deletions src/mightstone/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import mightstone

from ..containers import Container
from ..services.cardconjurer.commands import cardconjurer
from ..services.edhrec.commands import edhrec
from ..services.mtgjson.commands import mtgjson
Expand All @@ -25,6 +26,8 @@ def cli(ctx, format, verbose, log_level):

ctx.ensure_object(dict)
ctx.obj["format"] = format
ctx.obj["container"] = Container()
ctx.obj["container"].init_resources()

logging.basicConfig(
level=log_level,
Expand Down
47 changes: 28 additions & 19 deletions src/mightstone/containers.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,42 @@
import httpx_cache
import logging.config
from appdirs import user_cache_dir
from dependency_injector import containers, providers

from .config import Settings
from .services.cardconjurer import CardConjurer
from .services.edhrec import EdhRecApi, EdhRecStatic
from .services.mtgjson import MtgJson
from .services.scryfall import Scryfall


class Container(containers.DeclarativeContainer):
config = providers.Configuration(pydantic_settings=[Settings()])
logging = providers.Resource(
logging.config.dictConfig,
fname="logging.ini",
)

# Gateways
httpx_client = providers.Singleton(
httpx_cache.AsyncClient,
config.database.dsn,
httpx_cache_transport = providers.Factory(
httpx_cache.AsyncCacheControlTransport,
cache=httpx_cache.FileCache(cache_dir=user_cache_dir("mightstone")),
)

# s3_client = providers.Singleton(
# boto3.client,
# service_name="s3",
# aws_access_key_id=config.aws.access_key_id,
# aws_secret_access_key=config.aws.secret_access_key,
# )

# Services
scryfall = providers.Factory(
Scryfall,
client=httpx_client,
transport=httpx_cache_transport,
)

edhrec_static = providers.Factory(
EdhRecStatic,
transport=httpx_cache_transport,
)

edhrec_api = providers.Factory(
EdhRecApi,
transport=httpx_cache_transport,
)

card_conjurer = providers.Factory(
CardConjurer,
transport=httpx_cache_transport,
)

mtg_json = providers.Factory(
MtgJson,
transport=httpx_cache_transport,
)
12 changes: 6 additions & 6 deletions src/mightstone/rule/cr.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from itertools import takewhile
from typing import Dict, List, Mapping, TextIO

import aiohttp
import httpx
import requests

from mightstone.core import MightstoneModel
Expand Down Expand Up @@ -381,12 +381,12 @@ async def explore(cls, f: date, t: date = None, concurrency=3):
async def test_url(session, url):
async with sem:
logger.debug("GET %s", url)
async with session.get(url) as response:
if response.status == 200:
logger.info("Found %s", url)
found.append(url)
resp = session.get(url)
if resp.status == 200:
logger.info("Found %s", url)
found.append(url)

async with aiohttp.ClientSession() as session:
async with httpx.Client() as session:
tasks = []
for url in urls:
task = asyncio.ensure_future(test_url(session, url))
Expand Down
50 changes: 8 additions & 42 deletions src/mightstone/services/__init__.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import asyncio

import ijson as ijson_module
from aiohttp_client_cache import CacheBackend, CachedSession, SQLiteBackend
from appdirs import user_cache_dir

from mightstone.ass import asyncio_run
from httpx import AsyncClient, BaseTransport

try:
ijson = ijson_module.get_backend("yajl2")
except ImportError:
ijson = ijson_module.get_backend("python")

cache_dir = user_cache_dir("mightstone")


class ServiceError(Exception):
def __init__(self, message, url=None, status=None, data=None, method=None):
Expand All @@ -36,44 +31,15 @@ class MightstoneHttpClient:
Induced delay in second between each API call
"""

def __init__(self, cache: int = 60 * 60):
self._session = None
def __init__(self, transport: BaseTransport = None):
options = {"transport": transport}
if self.base_url:
options["base_url"] = self.base_url
self.client = AsyncClient(**options)
self.ijson = ijson
self.cache = cache

@property
def session(self):
if not self._session:
self._session = self._build_session()
return self._session

def _build_session(self, *args, **kwargs) -> CachedSession:
if self.cache and self.cache > 0:
cache = SQLiteBackend(
cache_name=f"{cache_dir}/http-cache.sqlite", expire_after=self.cache
)
else:
cache = CacheBackend(expire_after=0, allowed_codes=())

return CachedSession(base_url=self.base_url, cache=cache, *args, **kwargs)

async def __aenter__(self):
return self

def __enter__(self):
return self

async def __aexit__(self, exc_type, exc_val, exc_tb):
if self._session:
await self._session.close()

def __exit__(self, exc_type, exc_val, exc_tb):
if self._session:
asyncio_run(self.session.close())

def __del__(self):
if self._session:
asyncio_run(self.session.close())
async def close(self):
await self.client.aclose()

async def _sleep(self):
await asyncio.sleep(self.delay)
33 changes: 15 additions & 18 deletions src/mightstone/services/cardconjurer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

import aiofiles
import PIL.Image
from aiohttp import ClientResponseError
from httpx import HTTPStatusError
from PIL import Image, ImageDraw, ImageFont, UnidentifiedImageError
from pydantic.error_wrappers import ValidationError

Expand Down Expand Up @@ -183,13 +183,11 @@ async def _file(self, model: Generic[T], path: str) -> T:

async def _url(self, model: Generic[T], url: str) -> T:
try:
async with self.session.get(url) as f:
f.raise_for_status()
x = model.parse_raw(await f.content.read())
x.asset_root_url = "{uri.scheme}://{uri.netloc}".format(
uri=urlparse(url)
)
return x
f = await self.client.get(url)
f.raise_for_status()
x = model.parse_raw(f.content)
x.asset_root_url = "{uri.scheme}://{uri.netloc}".format(uri=urlparse(url))
return x
except ValidationError as e:
raise ServiceError(
message=f"Failed to validate {Template} data, {e.errors()}",
Expand All @@ -198,12 +196,12 @@ async def _url(self, model: Generic[T], url: str) -> T:
status=None,
data=e,
)
except ClientResponseError as e:
except HTTPStatusError as e:
raise ServiceError(
message="Failed to fetch CardConjurer template",
url=e.request_info.real_url,
method=e.request_info.method,
status=e.status,
url=e.request.url,
method=e.request.method,
status=e.response.status_code,
data=None,
)

Expand All @@ -218,8 +216,8 @@ async def _fetch_font(self, font: TemplateFont, base_uri: str = None):
async with aiofiles.open(uri) as f:
buffer = BytesIO(await f.read())
elif parsed_uri.scheme in ("http", "https"):
async with self.session.get(uri) as f:
buffer = BytesIO(await f.read())
f = self.client.get(uri)
buffer = f.content
else:
raise RuntimeError(f"Unknown scheme {parsed_uri.scheme}")

Expand All @@ -246,10 +244,9 @@ async def _fetch_image(self, img: CCImage, base_uri: str = None):
return

if parsed_uri.scheme in ("http", "https"):
async with self.session.get(uri) as f:
fo = BytesIO(await f.read())
self.assets_images[id(img)] = self._image_potentially_from_svg(fo)
return
f = await self.client.get(uri)
self.assets_images[id(img)] = self._image_potentially_from_svg(f.content)
return

raise ValueError(f"URI: {uri} scheme is not supported")

Expand Down
16 changes: 9 additions & 7 deletions src/mightstone/services/cardconjurer/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,35 @@

from mightstone.ass import asyncio_run
from mightstone.cli.utils import pretty_print
from mightstone.containers import Container
from mightstone.services.cardconjurer import CardConjurer


class CardConjurerObj(TypedDict):
cc: CardConjurer
container: Container
client: CardConjurer
format: str


@click.group()
@click.pass_obj
@click.option("--cache", type=int, default=0)
def cardconjurer(obj, **kwargs):
obj["cc"] = CardConjurer(**kwargs)
def cardconjurer(obj: CardConjurerObj, **kwargs):
obj["client"] = obj["container"].card_conjurer(**kwargs)


@cardconjurer.command()
@click.pass_obj
@click.argument("url_or_path")
def card(obj: CardConjurerObj, **kwargs):
pretty_print(asyncio_run(obj["cc"].card(**kwargs)), obj.get("format"))
pretty_print(asyncio_run(obj["client"].card(**kwargs)), obj.get("format"))


@cardconjurer.command()
@click.pass_obj
@click.argument("url_or_path")
def template(obj: CardConjurerObj, **kwargs):
pretty_print(asyncio_run(obj["cc"].template(**kwargs)), obj.get("format"))
pretty_print(asyncio_run(obj["client"].template(**kwargs)), obj.get("format"))


@cardconjurer.command()
Expand All @@ -40,9 +42,9 @@ def template(obj: CardConjurerObj, **kwargs):
@click.option("--asset-root-url", type=str)
def render(obj: CardConjurerObj, url_or_path, output, asset_root_url):
async def run():
card = await obj["cc"].card(url_or_path)
card = await obj["client"].card(url_or_path)
if asset_root_url:
card.asset_root_url = asset_root_url
await obj["cc"].render(card, output)
await obj["client"].render(card, output)

asyncio_run(run())
2 changes: 1 addition & 1 deletion src/mightstone/services/cardconjurer/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ class TemplateContext(MightstoneModel):
"""

ui: Any
image_sets: Dict[str, TemplateContextImageSet] = Field(alias="imageSets")
image_sets: List[Dict[str, TemplateContextImageSet]] = Field(alias="imageSets")
fonts: List[TemplateFont] = []
symbolExtension: Optional[Dict[str, List[TemplateExtension]]]

Expand Down

0 comments on commit 1ade77a

Please sign in to comment.