Skip to content

Commit

Permalink
Fix: issue-333 (#336)
Browse files Browse the repository at this point in the history
address issue-333
  • Loading branch information
Goldziher authored Aug 5, 2022
1 parent 4378e09 commit a422a5d
Show file tree
Hide file tree
Showing 12 changed files with 253 additions and 79 deletions.
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -122,5 +122,6 @@ repos:
starlette,
types-PyYAML,
types-freezegun,
types-redis,
types-requests,
]
150 changes: 140 additions & 10 deletions poetry.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ tox = "*"
requests = "*"
piccolo = "*"
pre-commit = "*"
redis = "^4.3.4"

[tool.poetry.extras]
testing = ["requests", "brotli"]
Expand Down
11 changes: 8 additions & 3 deletions starlite/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
)
from urllib.parse import urlencode

from pydantic import AnyUrl, BaseModel, DirectoryPath, constr, validator
from pydantic import AnyUrl, BaseConfig, BaseModel, DirectoryPath, constr, validator
from pydantic_openapi_schema.utils import construct_open_api_with_schema_class
from pydantic_openapi_schema.v3_1_0.contact import Contact
from pydantic_openapi_schema.v3_1_0.external_documentation import ExternalDocumentation
Expand Down Expand Up @@ -144,6 +144,9 @@ def dict(self, *args, **kwargs) -> Dict[str, Any]: # type: ignore[no-untyped-de
class OpenAPIConfig(BaseModel):
"""Class containing Settings and Schema Properties"""

class Config(BaseConfig):
copy_on_model_validation = False

create_examples: bool = False
openapi_controller: Type[OpenAPIController] = OpenAPIController

Expand Down Expand Up @@ -214,8 +217,9 @@ class StaticFilesConfig(BaseModel):


class TemplateConfig(BaseModel):
class Config:
class Config(BaseConfig):
arbitrary_types_allowed = True
copy_on_model_validation = False

directory: Union[DirectoryPath, List[DirectoryPath]]
engine: Type[TemplateEngineProtocol]
Expand All @@ -232,8 +236,9 @@ def default_cache_key_builder(request: "Request") -> str:


class CacheConfig(BaseModel):
class Config:
class Config(BaseConfig):
arbitrary_types_allowed = True
copy_on_model_validation = False

backend: CacheBackendProtocol = SimpleCacheBackend()
expiration: int = 60 # value in seconds
Expand Down
2 changes: 1 addition & 1 deletion starlite/exceptions/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from pydantic import BaseModel
from starlette.exceptions import HTTPException as StarletteHTTPException
from starlette.status import HTTP_500_INTERNAL_SERVER_ERROR # noqa: TC002
from starlette.status import HTTP_500_INTERNAL_SERVER_ERROR

from starlite.enums import MediaType
from starlite.response import Response
Expand Down
Binary file added test.sqlite
Binary file not shown.
20 changes: 20 additions & 0 deletions tests/caching/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import random
from typing import TYPE_CHECKING
from uuid import uuid4

if TYPE_CHECKING:
from starlite import Response


async def slow_handler() -> dict:
output = {}
count = 0
while count < 1000:
output[str(count)] = random.random()
count += 1
return output


def after_request_handler(response: "Response") -> "Response":
response.headers["unique-identifier"] = str(uuid4())
return response
30 changes: 30 additions & 0 deletions tests/caching/test_async_caching.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from typing import Any

from starlite import CacheConfig, get
from starlite.cache import SimpleCacheBackend
from starlite.testing import create_test_client

from . import after_request_handler, slow_handler


def test_async_handling() -> None:
class AsyncCacheBackend(SimpleCacheBackend):
async def set(self, key: str, value: Any, expiration: int) -> Any: # type: ignore
super().set(key=key, value=value, expiration=expiration)

async def get(self, key: str) -> Any:
return super().get(key=key)

cache_config = CacheConfig(backend=AsyncCacheBackend())

with create_test_client(
route_handlers=[get("/cached-async", cache=True)(slow_handler)],
after_request=after_request_handler,
cache_config=cache_config,
) as client:
first_response = client.get("/cached-async")
first_response_identifier = first_response.headers["unique-identifier"]
assert first_response_identifier
second_response = client.get("/cached-async")
assert second_response.headers["unique-identifier"] == first_response_identifier
assert first_response.json() == second_response.json()
38 changes: 38 additions & 0 deletions tests/caching/test_config_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from typing import Any

import pytest
import redis
from pydantic import ValidationError

from starlite import CacheConfig, Starlite
from starlite.cache import CacheBackendProtocol


def test_config_validation_scenario() -> None:
class ProtocolBaseBackend(CacheBackendProtocol):
def get(self, key: str) -> None:
...

def set(self, key: str, value: Any, expiration: int) -> None:
...

CacheConfig(backend=ProtocolBaseBackend) # type: ignore[arg-type]

class NoneProtocolBasedBackend:
def get(self, key: str) -> None:
...

def set(self, key: str, value: Any, expiration: int) -> None:
...

with pytest.raises(ValidationError):
CacheConfig(backend=NoneProtocolBasedBackend) # type: ignore[arg-type]


def test_config_validation_deep_copy() -> None:
"""
test fix for issue-333: https://github.com/starlite-api/starlite/issues/333
"""

cache_config = CacheConfig(backend=redis.from_url("redis://localhost:6379/1"))
Starlite(route_handlers=[], cache_config=cache_config)
68 changes: 3 additions & 65 deletions tests/test_caching.py → tests/caching/test_response_caching.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,11 @@
import random
from datetime import datetime, timedelta
from time import sleep
from typing import Any
from uuid import uuid4

import pytest
from freezegun import freeze_time
from pydantic import ValidationError

from starlite import CacheConfig, Request, Response, get
from starlite.cache import SimpleCacheBackend
from starlite import Request, get
from starlite.testing import create_test_client


async def slow_handler() -> dict:
output = {}
count = 0
while count < 1000:
output[str(count)] = random.random()
count += 1
return output


def after_request_handler(response: Response) -> Response:
response.headers["unique-identifier"] = str(uuid4())
return response
from . import after_request_handler, slow_handler


def test_default_cache_response() -> None:
Expand Down Expand Up @@ -67,7 +48,7 @@ def test_default_expiration() -> None:
assert first_response.headers["unique-identifier"] != third_response.headers["unique-identifier"]


def test_cache_key() -> None:
def test_custom_cache_key() -> None:
def custom_cache_key_builder(request: Request) -> str:
return request.url.path + ":::cached"

Expand All @@ -76,46 +57,3 @@ def custom_cache_key_builder(request: Request) -> str:
) as client:
client.get("/cached")
assert client.app.cache_config.backend.get("/cached:::cached")


def test_async_handling() -> None:
class AsyncCacheBackend(SimpleCacheBackend):
async def set(self, key: str, value: Any, expiration: int) -> Any: # type: ignore
super().set(key=key, value=value, expiration=expiration)

async def get(self, key: str) -> Any:
return super().get(key=key)

cache_config = CacheConfig(backend=AsyncCacheBackend())

with create_test_client(
route_handlers=[get("/cached-async", cache=True)(slow_handler)],
after_request=after_request_handler,
cache_config=cache_config,
) as client:
first_response = client.get("/cached-async")
first_response_identifier = first_response.headers["unique-identifier"]
assert first_response_identifier
second_response = client.get("/cached-async")
assert second_response.headers["unique-identifier"] == first_response_identifier
assert first_response.json() == second_response.json()


def test_config_validation() -> None:
class MyBackend:
def get(self) -> None:
...

def set(self) -> None:
...

with pytest.raises(ValidationError):
CacheConfig(backend=MyBackend) # type: ignore[arg-type]


def test_naive_cache_backend() -> None:
backend = SimpleCacheBackend()
backend.set("test", "1", 0.1) # type: ignore
assert backend.get("test")
sleep(0.2)
assert not backend.get("test")
11 changes: 11 additions & 0 deletions tests/caching/test_simple_cache_backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from time import sleep

from starlite.cache import SimpleCacheBackend


def test_simple_cache_backend() -> None:
backend = SimpleCacheBackend()
backend.set("test", "1", 0.1) # type: ignore
assert backend.get("test")
sleep(0.2)
assert not backend.get("test")
Binary file added tests/test.sqlite
Binary file not shown.

0 comments on commit a422a5d

Please sign in to comment.