Skip to content
Draft
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
4 changes: 2 additions & 2 deletions githubkit/auth/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def get_jwt(self) -> str:
return token

async def aget_jwt(self) -> str:
cache = self.github.config.cache_strategy.get_async_cache_storage()
cache = await self.github.config.cache_strategy.get_async_cache_storage()
cache_key = self._get_jwt_cache_key()
if not (token := await cache.aget(cache_key)):
token = self._create_jwt()
Expand Down Expand Up @@ -246,7 +246,7 @@ async def async_auth_flow(
yield request
return

cache = self.github.config.cache_strategy.get_async_cache_storage()
cache = await self.github.config.cache_strategy.get_async_cache_storage()
key = self._get_installation_cache_key()
if not (token := await cache.aget(key)):
token_request = self._build_installation_auth_request()
Expand Down
25 changes: 7 additions & 18 deletions githubkit/cache/base.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import abc
from datetime import timedelta

from hishel import AsyncBaseStorage, BaseStorage, Controller

from githubkit.typing import HishelControllerOptions
from hishel import AsyncBaseStorage, CachePolicy, SpecificationPolicy, SyncBaseStorage


class BaseCache(abc.ABC):
Expand Down Expand Up @@ -36,32 +34,23 @@ def get_cache_storage(self) -> BaseCache:
raise NotImplementedError

@abc.abstractmethod
def get_async_cache_storage(self) -> AsyncBaseCache:
async def get_async_cache_storage(self) -> AsyncBaseCache:
"""Get the cache storage instance used in async context

raise CacheUnsupportedError if the strategy does not support async usage
"""
raise NotImplementedError

def get_hishel_controller_options(self) -> HishelControllerOptions:
"""Get the hishel controller options"""
# set always revalidate by default
# See: https://hishel.com/examples/github/
return HishelControllerOptions(always_revalidate=True)

def get_hishel_controller(self) -> Controller:
"""Get the hishel controller instance

Get the controller options from `get_hishel_controller_options` method
"""
return Controller(**self.get_hishel_controller_options())
def get_hishel_policy(self) -> CachePolicy:
"""Get the hishel cache policy instance"""
return SpecificationPolicy()

@abc.abstractmethod
def get_hishel_storage(self) -> BaseStorage:
def get_hishel_storage(self) -> SyncBaseStorage:
"""Get the hishel storage instance used in sync context"""
raise NotImplementedError

@abc.abstractmethod
def get_async_hishel_storage(self) -> AsyncBaseStorage:
async def get_async_hishel_storage(self) -> AsyncBaseStorage:
"""Get the hishel storage instance used in async context"""
raise NotImplementedError
23 changes: 11 additions & 12 deletions githubkit/cache/mem_cache.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
import sqlite3
from typing_extensions import override

from hishel import AsyncInMemoryStorage, InMemoryStorage
import anysqlite
from hishel import AsyncSqliteStorage, SyncSqliteStorage

from .base import AsyncBaseCache, BaseCache, BaseCacheStrategy

Expand Down Expand Up @@ -47,8 +49,6 @@ async def aset(self, key: str, value: str, ex: timedelta) -> None:
class MemCacheStrategy(BaseCacheStrategy):
def __init__(self) -> None:
self._cache: MemCache | None = None
self._hishel_storage: InMemoryStorage | None = None
self._hishel_async_storage: AsyncInMemoryStorage | None = None

@override
def get_cache_storage(self) -> MemCache:
Expand All @@ -57,17 +57,16 @@ def get_cache_storage(self) -> MemCache:
return self._cache

@override
def get_async_cache_storage(self) -> MemCache:
async def get_async_cache_storage(self) -> MemCache:
return self.get_cache_storage()

@override
def get_hishel_storage(self) -> InMemoryStorage:
if self._hishel_storage is None:
self._hishel_storage = InMemoryStorage()
return self._hishel_storage
def get_hishel_storage(self) -> SyncSqliteStorage:
# NOTE: although it's possible to use in-memory sqlite for hishel,
# the connection can not be shared between clients.
# the storage is closed when the client transport is closed.
return SyncSqliteStorage(connection=sqlite3.connect(":memory:"))

@override
def get_async_hishel_storage(self) -> AsyncInMemoryStorage:
if self._hishel_async_storage is None:
self._hishel_async_storage = AsyncInMemoryStorage()
return self._hishel_async_storage
async def get_async_hishel_storage(self) -> AsyncSqliteStorage:
return AsyncSqliteStorage(connection=await anysqlite.connect(":memory:"))
34 changes: 10 additions & 24 deletions githubkit/cache/redis.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
from datetime import timedelta
from functools import partial
from typing import TYPE_CHECKING, Any, NoReturn
from typing_extensions import override

from hishel import AsyncBaseStorage, AsyncRedisStorage, RedisStorage

from githubkit.exception import CacheUnsupportedError
from githubkit.typing import HishelControllerOptions
from githubkit.utils import hishel_key_generator_with_prefix

from .base import AsyncBaseCache, BaseCache, BaseCacheStrategy

Expand All @@ -34,7 +31,7 @@ def __init__(self, client: "Redis", prefix: str | None = None) -> None:

def _get_key(self, key: str) -> str:
if self.prefix is not None:
return f"{self.prefix}{key}"
return f"{self.prefix}:{key}"
return key

@override
Expand All @@ -54,7 +51,7 @@ def __init__(self, client: "AsyncRedis", prefix: str | None = None) -> None:

def _get_key(self, key: str) -> str:
if self.prefix is not None:
return f"{self.prefix}{key}"
return f"{self.prefix}:{key}"
return key

@override
Expand All @@ -70,35 +67,24 @@ async def aset(self, key: str, value: str, ex: timedelta) -> None:
class RedisCacheStrategy(BaseCacheStrategy):
def __init__(self, client: "Redis", prefix: str | None = None) -> None:
self.client = client
self.prefix = prefix
self.prefix = prefix.removesuffix(":") if prefix else None

@override
def get_cache_storage(self) -> RedisCache:
return RedisCache(self.client, self.prefix)

@override
def get_async_cache_storage(self) -> NoReturn:
async def get_async_cache_storage(self) -> NoReturn:
raise CacheUnsupportedError(
"Sync redis cache strategy does not support async usage"
)

@override
def get_hishel_controller_options(self) -> HishelControllerOptions:
options = super().get_hishel_controller_options()

if self.prefix is not None:
options["key_generator"] = partial(
hishel_key_generator_with_prefix, prefix=self.prefix
)

return options

@override
def get_hishel_storage(self) -> RedisStorage:
return RedisStorage(client=self.client)
return RedisStorage(client=self.client, key_prefix=self.prefix or "hishel")

@override
def get_async_hishel_storage(self) -> NoReturn:
async def get_async_hishel_storage(self) -> NoReturn:
raise CacheUnsupportedError(
"Sync redis cache strategy does not support async usage"
)
Expand All @@ -107,7 +93,7 @@ def get_async_hishel_storage(self) -> NoReturn:
class AsyncRedisCacheStrategy(BaseCacheStrategy):
def __init__(self, client: "AsyncRedis", prefix: str | None = None) -> None:
self.client = client
self.prefix = prefix
self.prefix = prefix.removesuffix(":") if prefix else None

@override
def get_cache_storage(self) -> NoReturn:
Expand All @@ -116,7 +102,7 @@ def get_cache_storage(self) -> NoReturn:
)

@override
def get_async_cache_storage(self) -> AsyncRedisCache:
async def get_async_cache_storage(self) -> AsyncRedisCache:
return AsyncRedisCache(self.client, self.prefix)

@override
Expand All @@ -126,5 +112,5 @@ def get_hishel_storage(self) -> NoReturn:
)

@override
def get_async_hishel_storage(self) -> AsyncBaseStorage:
return AsyncRedisStorage(client=self.client)
async def get_async_hishel_storage(self) -> AsyncBaseStorage:
return AsyncRedisStorage(client=self.client, key_prefix=self.prefix or "hishel")
43 changes: 43 additions & 0 deletions githubkit/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from typing import Any

from hishel import AsyncBaseStorage, CachePolicy, SyncBaseStorage
from hishel.httpx import AsyncCacheTransport, SyncCacheTransport
import httpx


class SyncCacheClient(httpx.Client):
def __init__(self, *args: Any, **kwargs: Any):
self.storage: SyncBaseStorage | None = kwargs.pop("storage", None)
self.policy: CachePolicy | None = kwargs.pop("policy", None)
super().__init__(*args, **kwargs)

def _init_transport(self, *args, **kwargs) -> httpx.BaseTransport:
_transport = super()._init_transport(*args, **kwargs)
return SyncCacheTransport(
next_transport=_transport, storage=self.storage, policy=self.policy
)

def _init_proxy_transport(self, *args, **kwargs) -> httpx.BaseTransport:
_transport = super()._init_proxy_transport(*args, **kwargs)
return SyncCacheTransport(
next_transport=_transport, storage=self.storage, policy=self.policy
)


class AsyncCacheClient(httpx.AsyncClient):
def __init__(self, *args: Any, **kwargs: Any):
self.storage: AsyncBaseStorage | None = kwargs.pop("storage", None)
self.policy: CachePolicy | None = kwargs.pop("policy", None)
super().__init__(*args, **kwargs)

def _init_transport(self, *args, **kwargs) -> httpx.AsyncBaseTransport:
_transport = super()._init_transport(*args, **kwargs)
return AsyncCacheTransport(
next_transport=_transport, storage=self.storage, policy=self.policy
)

def _init_proxy_transport(self, *args, **kwargs) -> httpx.AsyncBaseTransport:
_transport = super()._init_proxy_transport(*args, **kwargs)
return AsyncCacheTransport(
next_transport=_transport, storage=self.storage, policy=self.policy
)
17 changes: 9 additions & 8 deletions githubkit/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
from typing import TYPE_CHECKING, Any, Generic, TypeVar, cast, overload

import anyio
import hishel
import httpx

from .auth import BaseAuthStrategy, TokenAuthStrategy, UnauthAuthStrategy
from .cache import BaseCacheStrategy
from .client import AsyncCacheClient, SyncCacheClient
from .compat import to_jsonable_python
from .config import Config, get_config
from .exception import (
Expand Down Expand Up @@ -250,6 +250,7 @@ def _get_client_defaults(self) -> dict[str, Any]:
"headers": {
"User-Agent": self.config.user_agent,
"Accept": self.config.accept,
"Cache-Control": "no-cache",
},
"timeout": self.config.timeout,
"follow_redirects": self.config.follow_redirects,
Expand All @@ -260,12 +261,12 @@ def _get_client_defaults(self) -> dict[str, Any]:

def _create_sync_client(self) -> httpx.Client:
if self.config.http_cache:
return hishel.CacheClient(
return SyncCacheClient(
**self._get_client_defaults(),
transport=self.config.transport,
event_hooks=self.config.event_hooks,
storage=self.config.cache_strategy.get_hishel_storage(),
controller=self.config.cache_strategy.get_hishel_controller(),
policy=self.config.cache_strategy.get_hishel_policy(),
)

return httpx.Client(
Expand All @@ -286,14 +287,14 @@ def get_sync_client(self) -> Generator[httpx.Client, None, None]:
finally:
client.close()

def _create_async_client(self) -> httpx.AsyncClient:
async def _create_async_client(self) -> httpx.AsyncClient:
if self.config.http_cache:
return hishel.AsyncCacheClient(
return AsyncCacheClient(
**self._get_client_defaults(),
transport=self.config.async_transport,
event_hooks=self.config.async_event_hooks,
storage=self.config.cache_strategy.get_async_hishel_storage(),
controller=self.config.cache_strategy.get_hishel_controller(),
storage=await self.config.cache_strategy.get_async_hishel_storage(),
policy=self.config.cache_strategy.get_hishel_policy(),
)

return httpx.AsyncClient(
Expand All @@ -308,7 +309,7 @@ async def get_async_client(self) -> AsyncGenerator[httpx.AsyncClient, None]:
if client := self.__async_client.get():
yield client
else:
client = self._create_async_client()
client = await self._create_async_client()
try:
yield client
finally:
Expand Down
19 changes: 1 addition & 18 deletions githubkit/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,10 @@
Any,
Literal,
NamedTuple,
Optional,
TypeAlias,
TypedDict,
TypeVar,
)

import httpcore
import httpx
from pydantic import Field

Expand All @@ -22,7 +19,7 @@
from .utils import Unset

if TYPE_CHECKING:
from hishel._utils import BaseClock
pass

T = TypeVar("T")
H = TypeVar("H", bound=Hashable)
Expand Down Expand Up @@ -94,17 +91,3 @@ class RetryOption(NamedTuple):


EventHookTypes: TypeAlias = Mapping[str, list[Callable[..., Any]]]


class HishelControllerOptions(TypedDict, total=False):
"""Options for the hishel controller."""

cacheable_methods: list[str] | None
cacheable_status_codes: list[int] | None
cache_private: bool
allow_heuristics: bool
clock: Optional["BaseClock"]
allow_stale: bool
always_revalidate: bool
force_cache: bool
key_generator: Callable[[httpcore.Request, bytes | None], str] | None
8 changes: 0 additions & 8 deletions githubkit/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
import inspect
from typing import Any, Generic, Literal, TypeVar, final, overload

from hishel._utils import generate_key
import httpcore
from pydantic import BaseModel

from .compat import PYDANTIC_V2, custom_validation, type_validate_python
Expand Down Expand Up @@ -130,9 +128,3 @@ def __get_pydantic_core_schema__(
),
),
)


def hishel_key_generator_with_prefix(
request: httpcore.Request, body: bytes | None, prefix: str = ""
) -> str:
return prefix + generate_key(request, b"" if body is None else body)
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ requires-python = ">=3.10, <4.0"
dependencies = [
"anyio >=3.6.1, <5.0.0",
"httpx >=0.23.0, <1.0.0",
"hishel >=0.0.21, <=0.2.0",
"hishel[async,httpx] >=1.2.0, <=2.0.0",
"typing-extensions >=4.11.0, <5.0.0",
"pydantic >=1.9.1, <3.0.0, !=2.5.0, !=2.5.1",
"githubkit-schemas >=26.5.7",
Expand All @@ -31,7 +31,7 @@ Documentation = "https://github.com/yanyongyu/githubkit"
"Bug Tracker" = "https://github.com/yanyongyu/githubkit/issues"

[dependency-groups]
thirdparty = ["redis >=5.2.0, <8.0.0"]
thirdparty = ["hishel[redis] >=1.2.0, <=2.0.0"]
dev = [
{ include-group = "thirdparty" },
{ include-group = "codegen" },
Expand Down
Loading
Loading