Skip to content

Commit

Permalink
Adopt safir PydanticRedisStorage
Browse files Browse the repository at this point in the history
The base class, RedisPageInstanceStorage, now adds the functionality
of adding the page instance's key; it's a direct subclass of
PydanticRedisStorage.

NbHtmlCacheStorage and NoteburstJobStore both subclass
RedisPageInstanceStorage.

Add types-redis to support type checking of the redis package.
  • Loading branch information
jonathansick committed Apr 13, 2023
1 parent bff6b0f commit 0c8b294
Show file tree
Hide file tree
Showing 12 changed files with 86 additions and 105 deletions.
1 change: 1 addition & 0 deletions requirements/dev.in
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ sqlalchemy[mypy]
holdup
respx
types-PyYAML
types-redis
17 changes: 17 additions & 0 deletions requirements/dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ certifi==2022.12.7
# -c requirements/main.txt
# httpcore
# httpx
cffi==1.15.1
# via
# -c requirements/main.txt
# cryptography
cfgv==3.3.1
# via pre-commit
click==8.1.3
Expand All @@ -26,6 +30,11 @@ coverage[toml]==7.2.3
# via
# -r requirements/dev.in
# pytest-cov
cryptography==40.0.1
# via
# -c requirements/main.txt
# types-pyopenssl
# types-redis
distlib==0.3.6
# via virtualenv
filelock==3.11.0
Expand Down Expand Up @@ -83,6 +92,10 @@ pluggy==1.0.0
# via pytest
pre-commit==3.2.2
# via -r requirements/dev.in
pycparser==2.21
# via
# -c requirements/main.txt
# cffi
pytest==7.3.0
# via
# -r requirements/dev.in
Expand Down Expand Up @@ -114,8 +127,12 @@ sqlalchemy[asyncio,mypy]==2.0.9
# via
# -c requirements/main.txt
# -r requirements/dev.in
types-pyopenssl==23.1.0.2
# via types-redis
types-pyyaml==6.0.12.9
# via -r requirements/dev.in
types-redis==4.5.4.1
# via -r requirements/dev.in
typing-extensions==4.5.0
# via
# -c requirements/main.txt
Expand Down
1 change: 0 additions & 1 deletion requirements/main.in
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ nbformat
nbconvert
jsonschema
jinja2
aioredis
gidgethub
markdown-it-py[linkify,plugins]
mdformat
Expand Down
9 changes: 2 additions & 7 deletions requirements/main.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
#
# pip-compile --allow-unsafe --output-file=requirements/main.txt requirements/main.in
#
aioredis==2.0.1
# via -r requirements/main.in
anyio==3.6.2
# via
# httpcore
Expand All @@ -14,9 +12,7 @@ anyio==3.6.2
arq==0.25.0
# via safir
async-timeout==4.0.2
# via
# aioredis
# redis
# via redis
asyncpg==0.27.0
# via safir
attrs==22.2.0
Expand Down Expand Up @@ -46,7 +42,7 @@ dnspython==2.3.0
# via email-validator
email-validator==1.3.1
# via pydantic
fastapi==0.95.0
fastapi==0.95.1
# via
# -r requirements/main.in
# safir
Expand Down Expand Up @@ -201,7 +197,6 @@ traitlets==5.9.0
# nbformat
typing-extensions==4.5.0
# via
# aioredis
# arq
# pydantic
# sqlalchemy
Expand Down
11 changes: 5 additions & 6 deletions src/timessquare/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import click
import structlog
import uvicorn
from aioredis import Redis
from redis.asyncio import Redis
from safir.asyncio import run_with_asyncio
from safir.database import create_database_engine, initialize_database

Expand Down Expand Up @@ -78,11 +78,10 @@ async def reset_html() -> None:
redis = Redis.from_url(config.redis_url, password=None)
try:
html_store = NbHtmlCacheStore(redis)
record_count = await html_store.delete_all()
if record_count > 0:
click.echo(f"Deleted {record_count} HTML records")
else:
click.echo("No HTML records to delete")
html_store.scan("*")
n = len([r async for r in html_store.scan("*")])
await html_store.delete_all("*")
click.echo(f"Deleted {n} HTML records")
except Exception as e:
click.echo(str(e))
finally:
Expand Down
6 changes: 3 additions & 3 deletions src/timessquare/dependencies/redis.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

from typing import Optional

from aioredis import Redis
from redis.asyncio import Redis

__all__ = ["RedisDependency", "redis_dependency"]


class RedisDependency:
"""Provides an aioredis pool as a dependency.
"""Provides an asyncio-based Redis client as a dependency.
Notes
-----
Expand All @@ -17,7 +17,7 @@ class RedisDependency:
"""

def __init__(self) -> None:
self.redis: Optional[Redis] = None
self.redis: Redis | None = None

async def initialize(
self, redis_url: str, password: Optional[str] = None
Expand Down
6 changes: 3 additions & 3 deletions src/timessquare/dependencies/requestcontext.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
from dataclasses import dataclass
from typing import Optional

import aioredis
from fastapi import Depends, Request, Response
from httpx import AsyncClient
from redis.asyncio import Redis
from safir.dependencies.db_session import db_session_dependency
from safir.dependencies.http_client import http_client_dependency
from safir.dependencies.logger import logger_dependency
Expand Down Expand Up @@ -48,7 +48,7 @@ class RequestContext:
session: async_scoped_session
"""The database session."""

redis: aioredis.Redis
redis: Redis
"""Redis connection pool."""

http_client: AsyncClient
Expand Down Expand Up @@ -105,7 +105,7 @@ async def context_dependency(
response: Response,
logger: BoundLogger = Depends(logger_dependency),
session: async_scoped_session = Depends(db_session_dependency),
redis: aioredis.Redis = Depends(redis_dependency),
redis: Redis = Depends(redis_dependency),
http_client: AsyncClient = Depends(http_client_dependency),
) -> RequestContext:
"""Provides a RequestContext as a dependency."""
Expand Down
8 changes: 4 additions & 4 deletions src/timessquare/services/page.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ async def get_html(
values=resolved_values,
display_settings=display_settings,
)
nbhtml = await self._html_store.get(page_key)
nbhtml = await self._html_store.get_instance(page_key)
if nbhtml is not None:
self._logger.debug("Got HTML from cache")
return nbhtml
Expand Down Expand Up @@ -333,7 +333,7 @@ async def _get_html_from_noteburst_job(
not presently available.
"""
# Is there an existing job in the noteburst job store?
job = await self._job_store.get(page_instance)
job = await self._job_store.get_instance(page_instance)
if not job:
self._logger.debug("No existing noteburst job available")
# A record of a noteburst job is not available. Send a request
Expand Down Expand Up @@ -370,7 +370,7 @@ async def _get_html_from_noteburst_job(
self._logger.warning(
"Got a 404 from a noteburst job", job_url=job.job_url
)
await self._job_store.delete(page_instance)
await self._job_store.delete_instance(page_instance)
await self.request_noteburst_execution(page_instance)
else:
# server error from noteburst
Expand Down Expand Up @@ -456,7 +456,7 @@ async def _create_html_matrix(
"Stored new HTML", display_settings=asdict(matrix_key)
)

await self._job_store.delete(page_instance)
await self._job_store.delete_instance(page_instance)
self._logger.debug("Deleted old job record")

return html_matrix
Expand Down
18 changes: 10 additions & 8 deletions src/timessquare/storage/nbhtmlcache.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,24 @@

from typing import Optional

import aioredis
from redis.asyncio import Redis

from timessquare.domain.nbhtml import NbHtmlModel

from .redisbase import RedisStore
from .redisbase import RedisPageInstanceStore

__all__ = ["NbHtmlCacheStore"]

class NbHtmlCacheStore(RedisStore[NbHtmlModel]):

class NbHtmlCacheStore(RedisPageInstanceStore[NbHtmlModel]):
"""Manages the storage of HTML renderings of notebooks.
The domain is `timessquare.domain.nbhtml.NbHtmlModel`.
"""

def __init__(self, redis: aioredis.Redis) -> None:
def __init__(self, redis: Redis) -> None:
super().__init__(
redis=redis, key_prefix="nbhtml", datatype=NbHtmlModel
redis=redis, key_prefix="nbhtml/", datatype=NbHtmlModel
)

async def store_nbhtml(
Expand All @@ -29,11 +31,11 @@ async def store_nbhtml(
Parameters
----------
nbhtml : `timessquare.domain.nbhtml.NbHtmlModel`
nbhtml
The HTML page domain model.
lifetime : int, optional
lifetime
The lifetime for the record in seconds. `None` to cache the record
indefinitely.
"""
key = nbhtml.create_key()
await super().store(key, nbhtml, lifetime=lifetime)
await super().store_instance(key, nbhtml, lifetime=lifetime)
18 changes: 9 additions & 9 deletions src/timessquare/storage/noteburstjobstore.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,25 @@

from __future__ import annotations

import aioredis
from redis.asyncio import Redis

from timessquare.domain.noteburst import NoteburstJobModel
from timessquare.domain.page import PageInstanceIdModel

from .redisbase import RedisStore
from .redisbase import RedisPageInstanceStore


class NoteburstJobStore(RedisStore[NoteburstJobModel]):
class NoteburstJobStore(RedisPageInstanceStore[NoteburstJobModel]):
"""The noteburst job store keeps track of open notebook execution job
requests for a given page and set of parameters.
The associated domain model is
`timessquare.domain.noteburst.NoteburstJobModel`.
"""

def __init__(self, redis: aioredis.Redis) -> None:
def __init__(self, redis: Redis) -> None:
super().__init__(
redis=redis, key_prefix="noteburst", datatype=NoteburstJobModel
redis=redis, key_prefix="noteburst/", datatype=NoteburstJobModel
)

async def store_job(
Expand All @@ -34,14 +34,14 @@ async def store_job(
Parameters
----------
job : `timessquare.domain.noteburst.NoteburstJobModel`
job
The job record.
page_id : `timessquare.domain.page.PageInstanceIdModel`
page_id
Identifier of the page instance, composed of the page's name
and the values the page instance is rendered with.
lifetime : int
lifetime
The lifetime of the record, in seconds. The lifetime should be set
so that if it elapses, it can be assumed that noteburst has failed
to process the original job and that a new request can be sent.
"""
await super().store(page_id, job, lifetime=lifetime)
await super().store_instance(page_id, job, lifetime=lifetime)
Loading

0 comments on commit 0c8b294

Please sign in to comment.