wip
This commit is contained in:
parent
a70c24398a
commit
e181f57a91
30 changed files with 2458 additions and 197 deletions
|
|
@ -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})
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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
60
htmx_patterns/models.py
Normal 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
|
||||
49
htmx_patterns/toast/router.py
Normal file
49
htmx_patterns/toast/router.py
Normal 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("")
|
||||
22
htmx_patterns/websocket/dependencies.py
Normal file
22
htmx_patterns/websocket/dependencies.py
Normal 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)
|
||||
39
htmx_patterns/websocket/router.py
Normal file
39
htmx_patterns/websocket/router.py
Normal 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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue