This commit is contained in:
Waylon S. Walker 2024-12-11 09:17:38 -06:00
parent a70c24398a
commit e181f57a91
30 changed files with 2458 additions and 197 deletions

View file

@ -5,11 +5,15 @@ from htmx_patterns.__about__ import __version__
from htmx_patterns.boosted.router import boosted_router
from htmx_patterns.config import get_config
from htmx_patterns.infinite.router import infinite_router
from htmx_patterns.toast.router import toast_router
from htmx_patterns.websocket.router import websocket_router
def set_prefers(
request: Request,
request: Request = None,
):
if request is None:
return
hx_request_header = request.headers.get("hx-request")
user_agent = request.headers.get("user-agent", "").lower()
if hx_request_header:
@ -38,6 +42,8 @@ config = get_config()
app.include_router(infinite_router)
app.include_router(boosted_router)
app.include_router(toast_router)
app.include_router(websocket_router)
@app.get("/")
@ -63,7 +69,19 @@ async def app_css(request: Request):
return FileResponse("templates/app.css")
@app.get("/htmx")
async def htmx(request: Request):
@app.get("/htmx.js")
async def htmx_js(request: Request):
"use a proper static file server like nginx or apache in production"
return config.templates.TemplateResponse("htmx.js", {"request": request})
@app.get("/ws.js")
async def ws_js(request: Request):
"use a proper static file server like nginx or apache in production"
return config.templates.TemplateResponse("ws.js", {"request": request})
@app.get("/tailwind.js")
async def tailwind_js(request: Request):
"use a proper static file server like nginx or apache in production"
return config.templates.TemplateResponse("tailwind.js", {"request": request})

View file

@ -1,24 +0,0 @@
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

@ -1,24 +1,42 @@
import asyncio
import time
from typing import Annotated
from fastapi import APIRouter
from fastapi import APIRouter, Depends, Header
from fastapi.requests import Request
from sqlmodel import Session
boosted_router = APIRouter(prefix="/boosted", tags=["Shots Methods"])
from htmx_patterns.boosted.models import PersonFactory
from htmx_patterns.config import get_config
from htmx_patterns.config import get_config, get_session
from htmx_patterns.models import Person
boosted_router = APIRouter(prefix="/boosted", tags=["Boosted"])
config = get_config()
@boosted_router.get("/")
@boosted_router.get("")
async def boosted(request: Request, id: int = 0):
# simulate getting a person by id
person = PersonFactory.build()
async def boosted(
request: Request,
id: int = 1,
session: Session = Depends(get_session),
user_agent: Annotated[str | None, Header()] = None,
):
# person = PersonFactory.build()
person = Person.get_by_id(session, id)
if id > 0:
if person is None:
return config.templates.TemplateResponse(
"boosted/person.html",
{
"request": request,
"person": None,
"person_id": id,
"prev_id": None,
"next_id": 1,
},
)
if id > 1:
prev_id = id - 1
next_id = id + 1
else:

View file

@ -1,5 +1,7 @@
import alembic
import typer
import uvicorn
from alembic.config import Config
from rich.console import Console
from htmx_patterns.config import get_config
@ -7,7 +9,6 @@ from htmx_patterns.config import get_config
api_app = typer.Typer()
@api_app.callback()
def api():
"model cli"
@ -37,6 +38,10 @@ def run(
):
config = get_config(env)
Console().print(config.api_server)
Console().print(config.database_url)
alembic_cfg = Config("alembic.ini")
alembic_cfg.set_main_option("sqlalchemy.url", config.database_url)
alembic.command.upgrade(config=alembic_cfg, revision=alembic_revision)
uvicorn.run(**config.api_server.dict())

View file

@ -1,17 +1,25 @@
import os
import urllib.parse
from datetime import datetime, timezone
from functools import lru_cache, partial
from typing import Any, Optional
from functools import lru_cache
import os
from typing import Optional
from urllib.parse import quote_plus
import jinja2
from dotenv import load_dotenv
from fastapi import Request
from fastapi.templating import Jinja2Templates
import jinja2
from pydantic import BaseModel, Field
from pydantic_settings import BaseSettings, SettingsConfigDict
from rich.console import Console
from sqlalchemy import create_engine
from sqlmodel import Session
from typing import TYPE_CHECKING
from htmx_patterns import models
if TYPE_CHECKING:
from sqlalchemy.engine import Engine
__all__ = ["models"]
console = Console()
@ -32,7 +40,6 @@ class ApiServer(BaseModel):
proxy_headers: bool = True
@pass_context
def url_for_query(context: dict, name: str, **params: dict) -> str:
request = context["request"]
@ -89,9 +96,10 @@ def get_templates(config: BaseSettings) -> Jinja2Templates:
class Config(BaseSettings):
env: str
env: str = "local"
the_templates: Optional[Jinja2Templates] = Field(None, exclude=True)
api_server: ApiServer = ApiServer()
database_url: str = "sqlite:///database.db"
@property
def templates(self) -> Jinja2Templates:
@ -102,6 +110,36 @@ class Config(BaseSettings):
model_config = SettingsConfigDict(env_nested_delimiter="__")
class Database:
def __init__(self, config: Config) -> None:
self.config = config
self.db_conf = {}
if "sqlite" in self.config.database_url:
self.db_conf = {
"connect_args": {"check_same_thread": False},
"pool_recycle": 3600,
"pool_pre_ping": True,
}
# if os.environ.get("ENV") == "test":
# self._engine = create_engine(
# "sqlite://",
# connect_args={"check_same_thread": False},
# poolclass=StaticPool,
# )
# else:
self._engine = create_engine(self.config.database_url, **self.db_conf)
@property
def engine(self) -> "Engine":
return self._engine
@property
def session(self) -> "Session":
return Session(self.engine)
@lru_cache
def get_config(env: Optional[str] = None):
if env is None:
@ -110,3 +148,12 @@ def get_config(env: Optional[str] = None):
config = Config()
return config
config = get_config()
database = Database(config)
def get_session() -> "Session":
with Session(database.engine) as session:
yield session

View file

@ -1,24 +0,0 @@
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

@ -1,13 +1,12 @@
import asyncio
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
from htmx_patterns.models import PersonFactory
infinite_router = APIRouter(prefix="/infinite", tags=["Infinite"])
config = get_config()

60
htmx_patterns/models.py Normal file
View file

@ -0,0 +1,60 @@
from datetime import datetime
from typing import Optional
from faker import Faker
from polyfactory.factories.pydantic_factory import ModelFactory
from pydantic import UUID4
from sqlmodel import Field, SQLModel
faker = Faker()
class PersonBase(SQLModel, table=False):
id: UUID4
name: str
birthday: datetime
phone_number: str
def get_by_id(session, id):
return session.get(Person, id)
def save(self, session):
session.add(self)
session.commit()
session.refresh(self)
return self
def delete(self, session):
session.delete(self)
session.commit()
return self
def update(self, session):
session.merge(self)
session.commit()
session.refresh(self)
return self
def all(session):
return session.query(Person).all()
def paginate(session, page=1, page_size=10):
return session.query(Person).offset((page - 1) * page_size).limit(page_size)
class Person(PersonBase, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
class PersonCreate(PersonBase):
pass
class PersonRead(PersonBase):
id: int = Field(default=None, primary_key=True)
class PersonFactory(ModelFactory):
name = faker.name
phone_number = faker.phone_number
__model__ = Person

View file

@ -0,0 +1,49 @@
from fastapi import APIRouter
from fastapi.requests import Request
from fastapi.responses import PlainTextResponse
from htmx_patterns.config import get_config
toast_router = APIRouter(prefix="/toast", tags=["toast"])
config = get_config()
@toast_router.get("/")
@toast_router.get("")
async def get_toast(
request: Request,
):
return config.templates.TemplateResponse(
"toast/toast.html",
{
"request": request,
},
)
@toast_router.post("/")
@toast_router.post("")
async def post_toast(
request: Request,
):
return config.templates.TemplateResponse(
"toast/toast-message.html",
{
"request": request,
"message": "Submitted",
},
)
@toast_router.get("/null/")
@toast_router.get("/null")
@toast_router.delete("/null/")
@toast_router.delete("/null")
@toast_router.post("/null/")
@toast_router.post("/null")
async def null(
request: Request,
):
return PlainTextResponse("")

View file

@ -0,0 +1,22 @@
from fastapi import WebSocket
class ConnectionManager:
"""Class defining socket events"""
def __init__(self):
"""init method, keeping track of connections"""
self.active_connections = []
async def connect(self, websocket: WebSocket):
"""connect event"""
await websocket.accept()
self.active_connections.append(websocket)
async def send_personal_message(self, message: str, websocket: WebSocket):
"""Direct Message"""
await websocket.send_text(message)
def disconnect(self, websocket: WebSocket):
"""disconnect event"""
self.active_connections.remove(websocket)

View file

@ -0,0 +1,39 @@
from fastapi import APIRouter, Request, WebSocket, WebSocketDisconnect
from htmx_patterns.websocket.dependencies import ConnectionManager
from htmx_patterns.config import get_config
websocket_router = APIRouter(prefix="/websocket", tags=["Websocket"])
manager = ConnectionManager()
config = get_config()
@websocket_router.get("/")
def websocket_index(request: Request):
return config.templates.TemplateResponse(
"websocket/index.html", {"request": request}
)
@websocket_router.websocket("/")
async def websocket_endpoint(
websocket: WebSocket,
):
await manager.connect(websocket)
await manager.send_personal_message("Hello", websocket)
try:
while True:
data = await websocket.receive_text()
await manager.send_personal_message(f"Received:{data}", websocket)
# import time
#
# time.sleep(1)
# data = "hello"
# await manager.send_personal_message(f"Received:{data}", websocket)
#
except WebSocketDisconnect:
manager.disconnect(websocket)
await manager.send_personal_message("Bye!!!", websocket)