diff --git a/CHANGELOG.md b/CHANGELOG.md index 612602f..01f0ad8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,25 +1,5 @@ # HTMX-PATTERNS CHANGELOG -## 0.1.3 - -* fix mobile style - -## 0.1.2 - -* fix seo image size - -## 0.1.1 - -* feat seo - -## 0.1.0 - -* feat boosted links - -## 0.0.4 - -* fix simulated load need asyncio.sleep - ## 0.0.3 * fix docker `ENV=prod` diff --git a/alembic.ini b/alembic.ini deleted file mode 100644 index d99f1a3..0000000 --- a/alembic.ini +++ /dev/null @@ -1,118 +0,0 @@ -# A generic, single database configuration. - -[alembic] -# path to migration scripts -script_location = migrations -revision_id = alembic.util.rev_id() -project = htmx-patterns - -# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s -# Uncomment the line below if you want the files to be prepended with date and time -# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file -# for all available tokens -# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s - -# sys.path path, will be prepended to sys.path if present. -# defaults to the current working directory. -prepend_sys_path = . - -# timezone to use when rendering the date within the migration file -# as well as the filename. -# If specified, requires the python>=3.9 or backports.zoneinfo library. -# Any required deps can installed by adding `alembic[tz]` to the pip requirements -# string value is passed to ZoneInfo() -# leave blank for localtime -# timezone = - -# max length of characters to apply to the -# "slug" field -# truncate_slug_length = 40 - -# set to 'true' to run the environment during -# the 'revision' command, regardless of autogenerate -# revision_environment = false - -# set to 'true' to allow .pyc and .pyo files without -# a source .py file to be detected as revisions in the -# versions/ directory -# sourceless = false - -# version location specification; This defaults -# to migrations/versions. When using multiple version -# directories, initial revisions must be specified with --version-path. -# The path separator used here should be the separator specified by "version_path_separator" below. -# version_locations = %(here)s/bar:%(here)s/bat:migrations/versions - -# version path separator; As mentioned above, this is the character used to split -# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. -# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. -# Valid values for version_path_separator are: -# -# version_path_separator = : -# version_path_separator = ; -# version_path_separator = space -version_path_separator = os # Use os.pathsep. Default configuration used for new projects. - -# set to 'true' to search source files recursively -# in each "version_locations" directory -# new in Alembic version 1.10 -# recursive_version_locations = false - -# the output encoding used when revision files -# are written from script.py.mako -# output_encoding = utf-8 - -; sqlalchemy.url = driver://user:pass@localhost/dbname - - -[post_write_hooks] -# post_write_hooks defines scripts or Python functions that are run -# on newly generated revision scripts. See the documentation for further -# detail and examples - -# format using "black" - use the console_scripts runner, against the "black" entrypoint -# hooks = black -# black.type = console_scripts -# black.entrypoint = black -# black.options = -l 79 REVISION_SCRIPT_FILENAME - -# lint with attempts to fix using "ruff" - use the exec runner, execute a binary -# hooks = ruff -# ruff.type = exec -# ruff.executable = %(here)s/.venv/bin/ruff -# ruff.options = --fix REVISION_SCRIPT_FILENAME - -# Logging configuration -[loggers] -keys = root,sqlalchemy,alembic - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = WARN -handlers = console -qualname = - -[logger_sqlalchemy] -level = WARN -handlers = -qualname = sqlalchemy.engine - -[logger_alembic] -level = INFO -handlers = -qualname = alembic - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(levelname)-5.5s [%(name)s] %(message)s -datefmt = %H:%M:%S diff --git a/htmx_patterns/__about__.py b/htmx_patterns/__about__.py index 5a7ed2b..4f7f2ea 100644 --- a/htmx_patterns/__about__.py +++ b/htmx_patterns/__about__.py @@ -1,4 +1,4 @@ # SPDX-FileCopyrightText: 2024-present Waylon S. Walker # # SPDX-License-Identifier: MIT -__version__ = "0.1.4" +__version__ = "0.0.3" diff --git a/htmx_patterns/app.py b/htmx_patterns/app.py index dea60f4..de0884a 100644 --- a/htmx_patterns/app.py +++ b/htmx_patterns/app.py @@ -2,19 +2,13 @@ from fastapi import Depends, FastAPI, Request from fastapi.responses import FileResponse 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 -from htmx_patterns.zpages.router import zpages_router def set_prefers( - request: Request = None, + request: Request, ): - 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: @@ -42,14 +36,10 @@ app = FastAPI( config = get_config() app.include_router(infinite_router) -app.include_router(boosted_router) -app.include_router(toast_router) -app.include_router(websocket_router) -app.include_router(zpages_router) @app.get("/") -async def index(request: Request): +async def read_main(request: Request): return config.templates.TemplateResponse("index.html", {"request": request}) @@ -71,19 +61,7 @@ async def app_css(request: Request): return FileResponse("templates/app.css") -@app.get("/htmx.js") -async def htmx_js(request: Request): +@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}) - - -@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}) diff --git a/htmx_patterns/boosted/router.py b/htmx_patterns/boosted/router.py deleted file mode 100644 index b48aa13..0000000 --- a/htmx_patterns/boosted/router.py +++ /dev/null @@ -1,55 +0,0 @@ -from typing import Annotated - -from fastapi import APIRouter, Depends, Header -from fastapi.requests import Request -from sqlmodel import Session - - -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 = 1, - session: Session = Depends(get_session), - user_agent: Annotated[str | None, Header()] = None, -): - # person = PersonFactory.build() - person = Person.get_by_id(session, id) - - 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: - prev_id = None - next_id = id + 1 - - return config.templates.TemplateResponse( - "boosted/person.html", - { - "request": request, - "person": person, - "person_id": id, - "prev_id": prev_id, - "next_id": next_id, - }, - ) diff --git a/htmx_patterns/cli/api.py b/htmx_patterns/cli/api.py index 20711f8..1a97e0b 100644 --- a/htmx_patterns/cli/api.py +++ b/htmx_patterns/cli/api.py @@ -1,7 +1,5 @@ -import alembic import typer import uvicorn -from alembic.config import Config from rich.console import Console from htmx_patterns.config import get_config @@ -9,6 +7,7 @@ from htmx_patterns.config import get_config api_app = typer.Typer() + @api_app.callback() def api(): "model cli" @@ -38,10 +37,6 @@ 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()) diff --git a/htmx_patterns/config.py b/htmx_patterns/config.py index bac720f..aa2ffe7 100644 --- a/htmx_patterns/config.py +++ b/htmx_patterns/config.py @@ -1,26 +1,17 @@ -from datetime import datetime, timezone -from functools import lru_cache import os -from typing import Optional +import urllib.parse +from datetime import datetime, timezone +from functools import lru_cache, partial +from typing import Any, Optional from urllib.parse import quote_plus -from dotenv import load_dotenv -from fastapi.templating import Jinja2Templates import jinja2 +from dotenv import load_dotenv +from fastapi import Request +from fastapi.templating import Jinja2Templates 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 -from htmx_patterns.__about__ import __version__ - -if TYPE_CHECKING: - from sqlalchemy.engine import Engine - -__all__ = ["models"] console = Console() @@ -41,6 +32,12 @@ class ApiServer(BaseModel): proxy_headers: bool = True +# @pass_context +# def https_url_for(context: dict, name: str, **params: Any) -> str: +# http_url = url_for_query(context, name, **params) +# return str(http_url).replace("http", "https", 1) + + @pass_context def url_for_query(context: dict, name: str, **params: dict) -> str: request = context["request"] @@ -74,6 +71,7 @@ def url_for_query(context: dict, name: str, **params: dict) -> str: if os.environ.get("ENV") in ["dev", "qa", "prod"]: updated_url = updated_url.replace("http", "https", 1) + return updated_url @@ -85,7 +83,7 @@ def get_templates(config: BaseSettings) -> Jinja2Templates: ).strftime("%B %d, %Y") templates.env.globals["url_for"] = url_for_query templates.env.globals["config"] = config - console.print(f"Using environment: {os.environ.get('ENV')}") + 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 @@ -97,11 +95,9 @@ def get_templates(config: BaseSettings) -> Jinja2Templates: class Config(BaseSettings): - env: str = "local" + env: str the_templates: Optional[Jinja2Templates] = Field(None, exclude=True) api_server: ApiServer = ApiServer() - database_url: str = "sqlite:///database.db" - app_version: str = __version__ @property def templates(self) -> Jinja2Templates: @@ -112,36 +108,6 @@ 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: @@ -150,12 +116,3 @@ 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 diff --git a/htmx_patterns/infinite/models.py b/htmx_patterns/infinite/models.py new file mode 100644 index 0000000..a5ad575 --- /dev/null +++ b/htmx_patterns/infinite/models.py @@ -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() diff --git a/htmx_patterns/infinite/router.py b/htmx_patterns/infinite/router.py index 8ce2bed..c9ee302 100644 --- a/htmx_patterns/infinite/router.py +++ b/htmx_patterns/infinite/router.py @@ -1,12 +1,12 @@ -import asyncio +import time from fastapi import APIRouter from fastapi.requests import Request -from htmx_patterns.config import get_config -from htmx_patterns.models import PersonFactory +infinite_router = APIRouter(prefix="/infinite", tags=["Shots Methods"]) -infinite_router = APIRouter(prefix="/infinite", tags=["Infinite"]) +from htmx_patterns.config import get_config +from htmx_patterns.infinite.models import PersonFactory config = get_config() @@ -22,7 +22,7 @@ async def infinite(request: Request, page: int = 1, n: int = 10): persons = [PersonFactory.build() for _ in range(n)] if request.state.prefers_partial: - await asyncio.sleep(1) + time.sleep(1) return config.templates.TemplateResponse( "infinite/persons_partial.html", { diff --git a/htmx_patterns/models.py b/htmx_patterns/models.py deleted file mode 100644 index a3ab75e..0000000 --- a/htmx_patterns/models.py +++ /dev/null @@ -1,60 +0,0 @@ -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 diff --git a/htmx_patterns/toast/router.py b/htmx_patterns/toast/router.py deleted file mode 100644 index bcc6db6..0000000 --- a/htmx_patterns/toast/router.py +++ /dev/null @@ -1,49 +0,0 @@ -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("") diff --git a/htmx_patterns/websocket/dependencies.py b/htmx_patterns/websocket/dependencies.py deleted file mode 100644 index d9f556e..0000000 --- a/htmx_patterns/websocket/dependencies.py +++ /dev/null @@ -1,22 +0,0 @@ -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) diff --git a/htmx_patterns/websocket/router.py b/htmx_patterns/websocket/router.py deleted file mode 100644 index b2cb219..0000000 --- a/htmx_patterns/websocket/router.py +++ /dev/null @@ -1,39 +0,0 @@ -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) diff --git a/htmx_patterns/zpages/router.py b/htmx_patterns/zpages/router.py deleted file mode 100644 index 65589b5..0000000 --- a/htmx_patterns/zpages/router.py +++ /dev/null @@ -1,92 +0,0 @@ -from datetime import datetime, timezone -from kubernetes import client, config as kubernetes_config -from kubernetes.config.config_exception import ConfigException -import os -from pydantic import BaseModel - -from fastapi import APIRouter - -from htmx_patterns.config import get_config -from typing import Union - -zpages_router = APIRouter(tags=["zpages"]) - -config = get_config() - - -def format_uptime(seconds: int) -> str: - minutes, sec = divmod(seconds, 60) - hours, min = divmod(minutes, 60) - days, hr = divmod(hours, 24) - - parts = [] - if days: - parts.append(f"{days}d") - if hr: - parts.append(f"{hr}h") - if min: - parts.append(f"{min}m") - parts.append(f"{sec}s") - - return " ".join(parts) - - -class PodInfo(BaseModel): - pod_name: str - namespace: str - node_name: str - container_image: str - start_time: Union[str, datetime] - pod_uptime: Union[str, int] - - -class Ready(BaseModel): - ready: bool - timestamp: str - app_version: str - - pod_info: PodInfo - - -def get_pod_info(): - pod_name = os.getenv("KUBERNETES_POD_NAME") - namespace = os.getenv("KUBERNETES_POD_NAMESPACE") - - try: - kubernetes_config.load_incluster_config() - except ConfigException: - return PodInfo( - pod_name="unknown", - namespace="unknown", - node_name="unknown", - pod_ip="unknown", - container_image="unknown", - start_time="unknown", - pod_uptime="unknown", - ) - v1 = client.CoreV1Api() - pod = v1.read_namespaced_pod(name=pod_name, namespace=namespace) - start_time = pod.status.start_time - now = datetime.now(timezone.utc) - uptime_seconds = int((now - start_time).total_seconds()) - - return PodInfo( - pod_name=pod.metadata.name, - namespace=pod.metadata.namespace, - node_name=pod.spec.node_name, - container_image=pod.spec.containers[0].image, - start_time=pod.status.start_time, - pod_uptime=format_uptime(uptime_seconds), - ) - - -@zpages_router.get("/readyz") -async def root() -> Ready: - pod_info = get_pod_info() - ready = Ready( - ready=True, - timestamp=datetime.now(timezone.utc).isoformat(), - app_version=config.app_version, - pod_info=pod_info, - ) - return ready diff --git a/justfile b/justfile index 5689950..5a88655 100644 --- a/justfile +++ b/justfile @@ -2,26 +2,8 @@ default: @just --list build-image: - podman build -t docker.io/waylonwalker/htmx-patterns-waylonwalker-com:$(hatch version) . - -tag-wayl-one: - podman tag docker.io/waylonwalker/htmx-patterns-waylonwalker-com:$(hatch version) registry.wayl.one/htmx-patterns-waylonwalker-com:latest - podman tag docker.io/waylonwalker/htmx-patterns-waylonwalker-com:$(hatch version) registry.wayl.one/htmx-patterns-waylonwalker-com:$(hatch version) -push-wayl-one: - podman push registry.wayl.one/htmx-patterns-waylonwalker-com:latest - podman push registry.wayl.one/htmx-patterns-waylonwalker-com:$(hatch version) + podman build -t docker.io/waylonwalker/htmx-patterns-waylonwalker-com:0.0.3 . push-image: - podman push docker.io/waylonwalker/htmx-patterns-waylonwalker-com:$(hatch version) - -shell: - hatch shell - -run: - uv run htmx-patterns api run - -lint: - ruff format htmx_patterns - ruff check --fix htmx_patterns - + podman push docker.io/waylonwalker/htmx-patterns-waylonwalker-com:0.0.3 diff --git a/migrations/env.py b/migrations/env.py deleted file mode 100644 index dfee355..0000000 --- a/migrations/env.py +++ /dev/null @@ -1,90 +0,0 @@ -from logging.config import fileConfig - -from alembic import context -from sqlmodel import SQLModel - -# this is the Alembic Config object, which provides -# access to the values within the .ini file in use. -config = context.config - -# Interpret the config file for Python logging. -# This line sets up loggers basically. -if config.config_file_name is not None: - fileConfig(config.config_file_name) - -# add your model's MetaData object here -# for 'autogenerate' support -# from myapp import mymodel -# target_metadata = mymodel.Base.metadata -target_metadata = SQLModel.metadata - -# other values from the config, defined by the needs of env.py, -# can be acquired: -# my_important_option = config.get_main_option("my_important_option") -# ... etc. - - -def run_migrations_offline() -> None: - """Run migrations in 'offline' mode. - - This configures the context with just a URL - and not an Engine, though an Engine is acceptable - here as well. By skipping the Engine creation - we don't even need a DBAPI to be available. - - Calls to context.execute() here emit the given string to the - script output. - - """ - url = config.get_main_option("sqlalchemy.url") - context.configure( - url=url, - target_metadata=target_metadata, - literal_binds=True, - dialect_opts={"paramstyle": "named"}, - ) - - with context.begin_transaction(): - context.run_migrations() - - -def run_migrations_online() -> None: - """Run migrations in 'online' mode. - - In this scenario we need to create an Engine - and associate a connection with the context. - - """ - # connectable = engine_from_config( - # config.get_section(config.config_ini_section, {}), - # prefix="sqlalchemy.", - # poolclass=pool.NullPool, - # ) - from htmx_patterns.config import Database, get_config - - project_config = get_config() - database = Database(project_config) - - config.set_main_option("sqlalchemy.url", project_config.database_url) - # connectable = engine_from_config( - # config.get_section(config.config_ini_section, {}), - # prefix="sqlalchemy.", - # poolclass=pool.NullPool, - # ) - - with database.engine.connect() as connection: - context.configure( - connection=connection, - target_metadata=target_metadata, - render_as_batch=True, - version_table=f'{config.get_main_option("project")}_alembic_version', - ) - - with context.begin_transaction(): - context.run_migrations() - - -if context.is_offline_mode(): - run_migrations_offline() -else: - run_migrations_online() diff --git a/migrations/script.py.mako b/migrations/script.py.mako deleted file mode 100644 index 6ce3351..0000000 --- a/migrations/script.py.mako +++ /dev/null @@ -1,27 +0,0 @@ -"""${message} - -Revision ID: ${up_revision} -Revises: ${down_revision | comma,n} -Create Date: ${create_date} - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa -import sqlmodel -${imports if imports else ""} - -# revision identifiers, used by Alembic. -revision: str = ${repr(up_revision)} -down_revision: Union[str, None] = ${repr(down_revision)} -branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} -depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} - - -def upgrade() -> None: - ${upgrades if upgrades else "pass"} - - -def downgrade() -> None: - ${downgrades if downgrades else "pass"} diff --git a/migrations/versions/bf7df4c30cea_init.py b/migrations/versions/bf7df4c30cea_init.py deleted file mode 100644 index 4770b42..0000000 --- a/migrations/versions/bf7df4c30cea_init.py +++ /dev/null @@ -1,39 +0,0 @@ -"""init - -Revision ID: bf7df4c30cea -Revises: -Create Date: 2024-04-07 09:47:07.245218 - -""" - -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa -import sqlmodel - - -# revision identifiers, used by Alembic. -revision: str = "bf7df4c30cea" -down_revision: Union[str, None] = None -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table( - "person", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column("birthday", sa.DateTime(), nullable=False), - sa.Column("phone_number", sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.PrimaryKeyConstraint("id"), - ) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table("person") - # ### end Alembic commands ### diff --git a/pyproject.toml b/pyproject.toml index 2c7028f..9dc6a6c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,6 @@ classifiers = [ "Programming Language :: Python :: Implementation :: PyPy", ] dependencies = [ -"kubernetes", "fastapi", "sqlmodel", "pydantic", @@ -37,7 +36,6 @@ dependencies = [ "jinja2", "uvicorn[standard]", "typer", -"alembic", ] [project.scripts] diff --git a/templates/app.css b/templates/app.css index 1cfe8ff..ef9badd 100644 --- a/templates/app.css +++ b/templates/app.css @@ -8,46 +8,47 @@ } ::-webkit-scrollbar { - height: .5rem; - width: .5rem; + height: 1rem; + width: 1rem; } ::-webkit-scrollbar-track { border-radius: 0.25rem; border-radius: 9999px; --tw-bg-opacity: 1; - background-color: #002600; + background-color: #450a0a; } body::-webkit-scrollbar-track { border-radius: 0.25rem; border-radius: 9999px; --tw-bg-opacity: 1; - background-color: #002600; + background-color: #450a0a; } ::-webkit-scrollbar-thumb { border-radius: 0.25rem; border-radius: 9999px; --tw-bg-opacity: 1; -background-color: #1aff1a; - background-color: #00b300; +background-color: rgba(239,68,68,var(--tw-bg-opacity)); } ::-webkit-scrollbar-thumb:hover { --tw-bg-opacity: 1; - background-color: #1aff1a; +background-color: #f87171; +background-color: #dc2626 } body::-webkit-scrollbar-thumb { border-radius: 0.25rem; border-radius: 9999px; --tw-bg-opacity: 1; - background-color: #00b300; +background-color: rgba(239,68,68,var(--tw-bg-opacity)); } body::-webkit-scrollbar-thumb:hover { --tw-bg-opacity: 1; - background-color: #1aff1a; +background-color: #f87171; +background-color: #dc2626 } diff --git a/templates/base.html b/templates/base.html index aeae2d6..833867e 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,155 +1,33 @@ - - {% block head %} - - {% block title %}HTMX Patterns{% endblock %} - - - - - - - - - - - - - - + + {% block head %} + + {% block title %}HTMX Patterns{% endblock %} + + + + + + + + {% endblock %} + - - - - - - - {% if DEBUG %} - {{ hot_reload.script(url_for('hot-reload') ) | safe }} - {% endif %} + +
+
+ {% if DEBUG %} + {{ hot_reload.script(url_for('hot-reload') ) | safe }} + {% endif %} + {% block content %} + {{ body | safe }} {% endblock %} - - - {% set links = { - "HTMX-PATTERNS": url_for("index"), - "Boosted Links": url_for('boosted'), - "Infinite Scroll": url_for('infinite'), - "Toast": url_for('get_toast'), - "WebSocket": url_for('websocket_index'), - } %} - - - -
-
-
- - {% block content %} - {{ body | safe }} - {% endblock %} -
-
-
- +
+ diff --git a/templates/boosted/person.html b/templates/boosted/person.html deleted file mode 100644 index 7d47ae0..0000000 --- a/templates/boosted/person.html +++ /dev/null @@ -1,63 +0,0 @@ -{% extends "base.html" %} -{% block title %}Contact - {{ person_id }} - {{ person.name }}{% endblock %} -{% block content %} -

- HTMX PATTERNS - BOOSTED -

- -

- Contact - {{ person_id }} -

- -{% if person is not none %} -

- {{ person.name.upper() }} - - {{ person.phone_number }} -

-{% else %} -

- Person not found -

-{% endif %} - -{% macro link(id, text, boosted=false) -%} - - {{ text }} - -{%- endmacro %} - -

- Boosted Links -

- -
- {{ link(prev_id, 'Previous', boosted=True) }} - {{ link(next_id, 'Next', boosted=True) }} -
- -

- Normal Links -

- -
- {{ link(prev_id, 'Previous', boosted=False) }} - {{ link(next_id, 'Next', boosted=False) }} -
-{% endblock %} diff --git a/templates/index.html b/templates/index.html index 050723a..b683019 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,53 +1,21 @@ {% extends "base.html" %} {% block content %}

+ class="inline-block pb-0 mx-auto mb-0 text-8xl font-black leading-tight leading-loose text-transparent bg-clip-text bg-gradient-to-r from-red-600 via-pink-500 to-yellow-400 ring-red-500 text-shadow-xl text-shadow-zinc-950 ring-5"> HTMX PATTERNS

-

+

A collection of HTMX patterns

-

- These are patterns that I have written based on content from the hypermedia.systems - book. There is lots of code duplication as each pattern is meant to be standalone. -

- -

- I currently make use of htmx with fastapi, sqlmodel, sqlite, and tailwindcss - for many of my projects. These patterns are here to serve for reference to - myself implemented using this stack in the most pure way possible to remain - simple and understandable. Sometimes real projects get complicated and are - hard to bring in new features correctly. This is a playground with completely - separate routers, models, and templates for each project. -

- -