create infinite

This commit is contained in:
Waylon Walker 2024-04-05 20:13:47 -05:00
parent 54b3c4bc9b
commit 7bff037b78
No known key found for this signature in database
GPG key ID: 66E2BF2B4190EFE4
14 changed files with 419 additions and 4 deletions

67
htmx_patterns/app.py Normal file
View file

@ -0,0 +1,67 @@
from fastapi import Depends, FastAPI, Request
from fastapi.responses import FileResponse
from htmx_patterns.__about__ import __version__
from htmx_patterns.config import get_config
from htmx_patterns.infinite.router import infinite_router
def set_prefers(
request: Request,
):
hx_request_header = request.headers.get("hx-request")
user_agent = request.headers.get("user-agent", "").lower()
if hx_request_header:
request.state.prefers_html = True
request.state.prefers_partial = True
request.state.prefers_json = False
elif "mozilla" in user_agent or "webkit" in user_agent:
request.state.prefers_html = True
request.state.prefers_partial = False
request.state.prefers_json = False
else:
request.state.prefers_html = False
request.state.prefers_partial = False
request.state.prefers_json = True
app = FastAPI(
title="HTMX Patterns",
version=__version__,
docs_url=None,
redoc_url=None,
openapi_url=None,
dependencies=[Depends(set_prefers)],
)
config = get_config()
app.include_router(infinite_router)
@app.get("/")
async def read_main(request: Request):
return config.templates.TemplateResponse("index.html", {"request": request})
@app.get("/favicon.ico")
async def favicon(request: Request):
"use a proper static file server like nginx or apache in production"
return FileResponse("templates/favicon.ico")
@app.get("/robots.txt")
async def robots(request: Request):
"use a proper static file server like nginx or apache in production"
return config.templates.TemplateResponse("robots.txt", {"request": request})
@app.get("/css")
async def app_css(request: Request):
"use a proper static file server like nginx or apache in production"
return FileResponse("templates/app.css")
@app.get("/htmx")
async def htmx(request: Request):
"use a proper static file server like nginx or apache in production"
return config.templates.TemplateResponse("htmx.js", {"request": request})

44
htmx_patterns/cli/api.py Normal file
View file

@ -0,0 +1,44 @@
import typer
import uvicorn
from rich.console import Console
from htmx_patterns.config import get_config
api_app = typer.Typer()
@api_app.callback()
def api():
"model cli"
@api_app.command()
def config(
env: str = typer.Option(
"local",
help="the environment to use",
),
):
config = get_config(env)
Console().print(config)
@api_app.command()
def run(
env: str = typer.Option(
"local",
help="the environment to use",
),
alembic_revision: str = typer.Option(
"head",
help="the alembic revision to use",
),
):
config = get_config(env)
Console().print(config.api_server)
uvicorn.run(**config.api_server.dict())
if __name__ == "__main__":
api_app()

7
htmx_patterns/cli/cli.py Normal file
View file

@ -0,0 +1,7 @@
import typer
from htmx_patterns.cli.api import api_app
app = typer.Typer()
app.add_typer(api_app, name="api")

80
htmx_patterns/config.py Normal file
View file

@ -0,0 +1,80 @@
import os
from datetime import datetime, timezone
from functools import lru_cache
from typing import Any, Optional
from urllib.parse import quote_plus
import jinja2
from dotenv import load_dotenv
from fastapi.templating import Jinja2Templates
from pydantic import BaseModel, Field
from pydantic_settings import BaseSettings, SettingsConfigDict
from rich.console import Console
console = Console()
if hasattr(jinja2, "pass_context"):
pass_context = jinja2.pass_context
else:
pass_context = jinja2.contextfunction
class ApiServer(BaseModel):
app: str = "htmx_patterns.app:app"
port: int = 5000
reload: bool = True
log_level: str = "info"
host: str = "0.0.0.0"
workers: int = 1
forwarded_allow_ips: str = "*"
proxy_headers: bool = True
@pass_context
def https_url_for(context: dict, name: str, **path_params: Any) -> str:
request = context["request"]
http_url = request.url_for(name, **path_params)
return str(http_url).replace("http", "https", 1)
def get_templates(config: BaseSettings) -> Jinja2Templates:
templates = Jinja2Templates(directory="templates")
templates.env.filters["quote_plus"] = lambda u: quote_plus(str(u))
templates.env.filters["timestamp"] = lambda u: datetime.fromtimestamp(
u, tz=timezone.utc
).strftime("%B %d, %Y")
templates.env.globals["https_url_for"] = https_url_for
templates.env.globals["config"] = config
console.print(f'Using environment: {os.environ.get("ENV")}')
if os.environ.get("ENV") in ["dev", "qa", "prod"]:
templates.env.globals["url_for"] = https_url_for
console.print("Using HTTPS")
else:
console.print("Using HTTP")
return templates
class Config(BaseSettings):
env: str
the_templates: Optional[Jinja2Templates] = Field(None, exclude=True)
api_server: ApiServer = ApiServer()
@property
def templates(self) -> Jinja2Templates:
if self.the_templates is None:
self.the_templates = get_templates(self)
return self.the_templates
model_config = SettingsConfigDict(env_nested_delimiter="__")
@lru_cache
def get_config(env: Optional[str] = None):
if env is None:
env = os.environ.get("ENV", "local")
load_dotenv(dotenv_path=f".env.{env}")
config = Config()
return config

View file

@ -0,0 +1,24 @@
from datetime import date, datetime
from typing import List, Union
from faker import Faker
from polyfactory.factories.pydantic_factory import ModelFactory
from pydantic import UUID4, BaseModel
faker = Faker()
class Person(BaseModel):
id: UUID4
name: str
birthday: Union[datetime, date]
phone_number: str
class PersonFactory(ModelFactory):
name = faker.name
phone_number = faker.phone_number
__model__ = Person
# result = PersonFactory.build()

View file

@ -0,0 +1,42 @@
import time
from fastapi import APIRouter
from fastapi.requests import Request
infinite_router = APIRouter(prefix="/infinite", tags=["Shots Methods"])
from htmx_patterns.config import get_config
from htmx_patterns.infinite.models import PersonFactory
config = get_config()
@infinite_router.get("/persons")
async def get_persons(request: Request, page: int = 1, n: int = 10):
# simulated last page
if page == 5:
return config.templates.TemplateResponse(
"infinite/persons_partial.html", {"request": request, "persons": []}
)
persons = [PersonFactory.build() for _ in range(n)]
if request.state.prefers_partial:
time.sleep(1)
return config.templates.TemplateResponse(
"infinite/persons_partial.html",
{
"request": request,
"persons": persons,
"next_page": page + 1,
},
)
return config.templates.TemplateResponse(
"infinite/persons.html",
{
"request": request,
"persons": persons,
"next_page": page + 1,
},
)