From 839cbd0dc0726eefeba725203d33e0402c1bf27e Mon Sep 17 00:00:00 2001 From: "Waylon S. Walker" Date: Tue, 6 Jun 2023 09:57:21 -0500 Subject: [PATCH] wip --- .pyflyby | 6 +++ Dockerfile | 3 -- Dockerfile.dev | 13 ++++- README.md | 13 +++++ alembic.ini | 2 +- docker-compose.yml | 2 + dockerfile-dev | 48 +++++++++++++++++++ learn_sql_model/api/hero.py | 10 +++- learn_sql_model/cli/api.py | 36 ++++++++++++++ learn_sql_model/cli/app.py | 6 +-- learn_sql_model/cli/hero.py | 37 ++++++++------ learn_sql_model/models/hero.py | 23 ++++++++- learn_sql_model/models/pet.py | 2 + migrations/env.py | 5 +- .../versions/a9bb6625c57b_add_birthday.py | 29 +++++++++++ ...21363b5f8_init.py => c8516c888495_init.py} | 6 +-- pyproject.toml | 7 ++- 17 files changed, 213 insertions(+), 35 deletions(-) create mode 100644 dockerfile-dev create mode 100644 migrations/versions/a9bb6625c57b_add_birthday.py rename migrations/versions/{2a221363b5f8_init.py => c8516c888495_init.py} (92%) diff --git a/.pyflyby b/.pyflyby index b5dfd0a..784d779 100644 --- a/.pyflyby +++ b/.pyflyby @@ -7,7 +7,13 @@ from learn_sql_model.console import console # models from learn_sql_model.models.fast_model import FastModel + from learn_sql_model.models.hero import Hero +from learn_sql_model.models.hero import HeroCreate +from learn_sql_model.models.hero import HeroRead +from learn_sql_model.models.hero import HeroUpdate +from learn_sql_model.models.hero import HeroDelete + from learn_sql_model.factories.hero import HeroFactory from learn_sql_model.factories.pet import PetFactory from learn_sql_model.models.pet import Pet diff --git a/Dockerfile b/Dockerfile index dcb1ecc..3f51154 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,5 @@ from python:3.10 -# install python and psycopg2 -# RUN apt-get update && apt-get install -y python3 python3-pip -# RUN pip3 install hatch WORKDIR /app Copy pyproject.toml /app COPY learn_sql_model/__about__.py /app/learn_sql_model/__about__.py diff --git a/Dockerfile.dev b/Dockerfile.dev index b8e3355..ae11120 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,4 +1,4 @@ -FROM learn-sql-model +FROM python:3.10 ENV DEBIAIN_FRONTEND=noninteractive ENV PATH="$PATH:/root/.local/bin:/root/.cargo/bin" @@ -43,11 +43,20 @@ RUN python3 -m pip install --upgrade pip && \ WORKDIR /app +## DUPLICATE from Dockerfile +## building FROM learn-sql-model will cause the cache to bust for every +## change, it needs to come after the dev installs. +COPY pyproject.toml /app +COPY learn_sql_model/__about__.py /app/learn_sql_model/__about__.py +COPY README.md /app +RUN pip3 install . COPY . . RUN python3 -m hatch env create && \ python3 -m hatch shell - RUN stow bin -t /root/ +COPY .env.dev.docker /app/.env.dev + + ENTRYPOINT /tmp/zellij/bootstrap/zellij diff --git a/README.md b/README.md index ffe4f6c..6792fe8 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,19 @@ pip install hatch hatch shell ``` +## Development with Docker + +```console +docker compose build +docker compose up -d +``` + +To attach to the cli. + +```console +docker attach learn-sql-model-cli-1 +``` + ## Start the Server ```console diff --git a/alembic.ini b/alembic.ini index 21a0cf7..bf183be 100644 --- a/alembic.ini +++ b/alembic.ini @@ -61,7 +61,7 @@ version_path_separator = os # Use os.pathsep. Default configuration used for ne # are written from script.py.mako # output_encoding = utf-8 -sqlalchemy.url = sqlite:///database.db +sqlalchemy.url = postgresql+psycopg2://postgres:postgres@localhost:5432 [post_write_hooks] diff --git a/docker-compose.yml b/docker-compose.yml index 8cfeb24..cf87453 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -35,6 +35,8 @@ services: restart: always tty: true stdin_open: true + volumes: + - ./:/app volumes: db: driver: local diff --git a/dockerfile-dev b/dockerfile-dev new file mode 100644 index 0000000..1f17d42 --- /dev/null +++ b/dockerfile-dev @@ -0,0 +1,48 @@ +FROM learn-sql-model + +ENV DEBIAIN_FRONTEND=noninteractive +ENV PATH="$PATH:/root/.local/bin:/root/.cargo/bin" +ENV SHELL=zsh +ENV USER=root + + +RUN apt update && \ + apt upgrade -y && \ + apt install -y \ + cmake \ + htop \ + stow \ + zsh + + +WORKDIR /root/downloads + +RUN wget https://github.com/neovim/neovim/releases/download/nightly/nvim.appimage && \ + mkdir -p /root/.local/bin && \ + chmod u+x /root/downloads/nvim.appimage && \ + /root/downloads/nvim.appimage --appimage-extract && \ + rm -rf nvim.appimage && \ + ln -s ~/downloads/squashfs-root/usr/bin/nvim ~/.local/bin/nvim && \ + cd ~ && \ + git clone https://github.com/LazyVim/starter ~/.config/nvim && \ + nvim --headless -c 'quitall' + +RUN curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly -y && \ + cargo install \ + bat \ + exa \ + gitui \ + ripgrep \ + starship \ + zellij + +RUN python3 -m pip install --upgrade pip && \ + python3 -m pip install ansible-core && \ + python3 -m pip install lolcat && \ + python3 -m pip install pyflyby && \ + python3 -m pip install rich-cli && \ + python3 -m pip install visidata + +WORKDIR /app + +ENTRYPOINT zellij diff --git a/learn_sql_model/api/hero.py b/learn_sql_model/api/hero.py index deca520..c177246 100644 --- a/learn_sql_model/api/hero.py +++ b/learn_sql_model/api/hero.py @@ -11,8 +11,8 @@ hero_router = APIRouter() @hero_router.on_event("startup") -def on_startup(config: Config = Depends(get_config)) -> None: - SQLModel.metadata.create_all(config.database.engine) +def on_startup() -> None: + SQLModel.metadata.create_all(get_config().database.engine) @hero_router.get("/items/") @@ -26,6 +26,12 @@ def get_hero(id: int, config: Config = Depends(get_config)) -> Hero: return Hero().get(id=id, config=config) +@hero_router.get("/h/{id}") +def get_h(id: int, config: Config = Depends(get_config)) -> Hero: + "get one hero" + return Hero().get(id=id, config=config) + + @hero_router.post("/hero/") def post_hero(hero: Hero, config: Config = Depends(get_config)) -> Hero: "read all the heros" diff --git a/learn_sql_model/cli/api.py b/learn_sql_model/cli/api.py index a06b50a..76657cc 100644 --- a/learn_sql_model/cli/api.py +++ b/learn_sql_model/cli/api.py @@ -1,3 +1,4 @@ +from rich.console import Console import typer import uvicorn @@ -27,3 +28,38 @@ def run( ), ): uvicorn.run(**get_config().api_server.dict()) + + +@api_app.command() +def status( + verbose: bool = typer.Option( + False, + callback=verbose_callback, + help="show the log messages", + ), +): + import httpx + + config = get_config() + host = config.api_server.host + port = config.api_server.port + url = f"http://{host}:{port}/docs" + + try: + r = httpx.get(url) + if r.status_code == 200: + Console().print(f"[green]API: ([gold1]{url}[green]) is running") + else: + Console().print(f"[red]API: ([gold1]{url}[red]) is not running") + except httpx.ConnectError: + Console().print(f"[red]API: ([gold1]{url}[red]) is not running") + + try: + with config.database.engine.connect(): + Console().print( + f"[green]database: ([gold1]{config.database.engine}[green]) is running" + ) + except Exception as e: + Console().print( + f"[red]database: ([gold1]{config.database.engine}[red]) is not running" + ) diff --git a/learn_sql_model/cli/app.py b/learn_sql_model/cli/app.py index 4224ba8..a6f8b94 100644 --- a/learn_sql_model/cli/app.py +++ b/learn_sql_model/cli/app.py @@ -5,16 +5,14 @@ from typer.main import get_group from learn_sql_model.cli.api import api_app from learn_sql_model.cli.config import config_app from learn_sql_model.cli.hero import hero_app -from learn_sql_model.cli.model import model_app -from learn_sql_model.cli.tui import tui_app app = typer.Typer( name="learn_sql_model", help="learn-sql-model cli for managing the project", ) app.add_typer(config_app, name="config") -app.add_typer(tui_app, name="tui") -app.add_typer(model_app, name="model") +# app.add_typer(tui_app, name="tui") +# app.add_typer(model_app, name="model") app.add_typer(api_app, name="api") app.add_typer(hero_app, name="hero") diff --git a/learn_sql_model/cli/hero.py b/learn_sql_model/cli/hero.py index f1fdb91..6c993f1 100644 --- a/learn_sql_model/cli/hero.py +++ b/learn_sql_model/cli/hero.py @@ -1,15 +1,14 @@ +import sys from typing import List, Optional, Union -from pydantic_typer import expand_pydantic_args +from engorgio import engorgio from rich.console import Console import typer -from learn_sql_model.config import Config +from learn_sql_model.config import Config, get_config from learn_sql_model.factories.hero import HeroFactory from learn_sql_model.factories.pet import PetFactory -from learn_sql_model.models.hero import Hero -from learn_sql_model.models.pet import Pet -import sys +from learn_sql_model.models.hero import Hero, HeroCreate hero_app = typer.Typer() @@ -20,9 +19,9 @@ def hero(): @hero_app.command() -@expand_pydantic_args(typer=True) +@engorgio(typer=True) def get( - id: Optional[int] = None, + id: Optional[int] = typer.Argument(default=None), config: Config = None, ) -> Union[Hero, List[Hero]]: "get one hero" @@ -33,28 +32,36 @@ def get( @hero_app.command() -@expand_pydantic_args(typer=True) +@engorgio(typer=True) +def list( + config: Config = None, +) -> Union[Hero, List[Hero]]: + "get one hero" + hero = Hero().get() + Console().print(hero) + return hero + + +@hero_app.command() +@engorgio(typer=True) def create( - hero: Hero, - pet: Pet = None, + hero: HeroCreate, config: Config = None, ) -> Hero: "read all the heros" config.init() - hero.pet = pet hero = hero.post(config=config) Console().print(hero) @hero_app.command() -@expand_pydantic_args(typer=True) +@engorgio(typer=True) def populate( + hero: Hero, n: int = 10, - config: Config = None, ) -> Hero: "read all the heros" - if config is None: - config = Config() + config = get_config() if config.env == "prod": Console().print("populate is not supported in production") sys.exit(1) diff --git a/learn_sql_model/models/hero.py b/learn_sql_model/models/hero.py index 734fdeb..4d80df3 100644 --- a/learn_sql_model/models/hero.py +++ b/learn_sql_model/models/hero.py @@ -6,8 +6,7 @@ from learn_sql_model.models.fast_model import FastModel from learn_sql_model.models.pet import Pet -class Hero(FastModel, table=True): - id: Optional[int] = Field(default=None, primary_key=True) +class HeroBase(FastModel, table=False): name: str secret_name: str age: Optional[int] = None @@ -15,3 +14,23 @@ class Hero(FastModel, table=True): pet_id: Optional[int] = Field(default=None, foreign_key="pet.id") pet: Optional[Pet] = Relationship(back_populates="hero") + + +class Hero(HeroBase, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + + +class HeroCreate(HeroBase): + ... + + +class HeroRead(HeroBase): + id: Optional[int] = Field(default=None, primary_key=True) + + +class HeroUpdate(HeroBase): + ... + + +class HeroDelete(HeroBase): + id: Optional[int] = Field(default=None, primary_key=True) diff --git a/learn_sql_model/models/pet.py b/learn_sql_model/models/pet.py index a9b4314..7485657 100644 --- a/learn_sql_model/models/pet.py +++ b/learn_sql_model/models/pet.py @@ -1,3 +1,4 @@ +from datetime import datetime from typing import Optional, TYPE_CHECKING from sqlmodel import Field, Relationship @@ -11,4 +12,5 @@ if TYPE_CHECKING: class Pet(FastModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) name: str = "Jim" + birthday: Optional[datetime] = None hero: "Hero" = Relationship(back_populates="pet") diff --git a/migrations/env.py b/migrations/env.py index 38bed18..4863934 100644 --- a/migrations/env.py +++ b/migrations/env.py @@ -4,6 +4,7 @@ from alembic import context from sqlalchemy import engine_from_config, pool from sqlmodel import SQLModel +from learn_sql_model.config import get_config from learn_sql_model.models.hero import Hero from learn_sql_model.models.pet import Pet @@ -48,7 +49,7 @@ def run_migrations_offline() -> None: script output. """ - url = config.get_main_option("sqlalchemy.url") + url = get_config().database_url context.configure( url=url, target_metadata=target_metadata, @@ -78,7 +79,7 @@ def run_migrations_online() -> None: context.configure( connection=connection, target_metadata=target_metadata, - render_as_batch=True, + render_as_batch=False, version_table=f'{config.get_main_option("project")}_alembic_version', ) diff --git a/migrations/versions/a9bb6625c57b_add_birthday.py b/migrations/versions/a9bb6625c57b_add_birthday.py new file mode 100644 index 0000000..9e8feb4 --- /dev/null +++ b/migrations/versions/a9bb6625c57b_add_birthday.py @@ -0,0 +1,29 @@ +"""add birthday + +Revision ID: a9bb6625c57b +Revises: c8516c888495 +Create Date: 2023-05-25 19:00:58.137464 + +""" +from alembic import op +import sqlalchemy as sa +import sqlmodel + + +# revision identifiers, used by Alembic. +revision = 'a9bb6625c57b' +down_revision = 'c8516c888495' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('pet', sa.Column('birthday', sa.DateTime(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('pet', 'birthday') + # ### end Alembic commands ### diff --git a/migrations/versions/2a221363b5f8_init.py b/migrations/versions/c8516c888495_init.py similarity index 92% rename from migrations/versions/2a221363b5f8_init.py rename to migrations/versions/c8516c888495_init.py index 278cb31..c351dd9 100644 --- a/migrations/versions/2a221363b5f8_init.py +++ b/migrations/versions/c8516c888495_init.py @@ -1,8 +1,8 @@ """init -Revision ID: 2a221363b5f8 +Revision ID: c8516c888495 Revises: -Create Date: 2023-05-21 20:52:12.693194 +Create Date: 2023-05-25 18:42:37.057225 """ from alembic import op @@ -11,7 +11,7 @@ import sqlmodel # revision identifiers, used by Alembic. -revision = '2a221363b5f8' +revision = 'c8516c888495' down_revision = None branch_labels = None depends_on = None diff --git a/pyproject.toml b/pyproject.toml index 26de1d9..3c9919a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,16 +25,19 @@ classifiers = [ ] dependencies = [ "anyconfig", - "trogon", + "engorgio", "fastapi", "httpx", "passlib[bcrypt]", "polyfactory", + "psycopg2", "python-jose[cryptography]", "python-multipart", "rich", "sqlmodel", "textual", + "toml", + "trogon", "typer", "uvicorn[standard]", ] @@ -49,6 +52,7 @@ Changelog = "https://github.com/waylonwalker/learn-sql-model" [project.scripts] learn-sql-model = "learn_sql_model.cli.app:app" +lsm = "learn_sql_model.cli.app:app" [tool.hatch.version] path = "learn_sql_model/__about__.py" @@ -82,6 +86,7 @@ lint-test = [ "cov", ] test-lint = "lint-test" +api = "learn-sql-model api run" [[tool.hatch.envs.test.matrix]] python = ["37", "38", "39", "310", "311"]