From aaa88b7442c92a79913bb4888d2df09a9a310405 Mon Sep 17 00:00:00 2001 From: sergey Date: Sun, 11 Aug 2024 14:22:42 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B2=D1=81=D1=91=20=D1=81=D0=BB=D0=BE=D0=BC?= =?UTF-8?q?=D0=B0=D0=BB=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=20=D1=80=D0=B5?= =?UTF-8?q?=D1=84=D0=B0=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=D0=BE?= =?UTF-8?q?=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env-template | 3 +- ...0914-2455bc245a0b_create_products_table.py | 47 +++++++++++++++ api/__init__.py | 11 ++++ api/ip.py | 17 ++++++ core/config.py | 15 ++--- core/crud/__init__.py | 0 core/models/__init__.py | 8 ++- core/models/db_helper.py | 43 ++++++++++++++ core/models/ip.py | 19 ++++++ core/schemas/__init__.py | 0 core/schemas/ip.py | 20 +++++++ main.py | 14 +++-- poetry.lock | 56 +++++++++++++++++- pyproject.toml | 1 + {static => web/static}/js/htmx.js | 0 {static => web/static}/js/htmx.min.js | 0 {static => web/static}/swagger/favicon.png | Bin .../static}/swagger/swagger-ui-bundle.js | 0 {static => web/static}/swagger/swagger-ui.css | 0 19 files changed, 238 insertions(+), 16 deletions(-) create mode 100644 alembic/versions/2024_08_11_0914-2455bc245a0b_create_products_table.py create mode 100644 api/ip.py create mode 100644 core/crud/__init__.py create mode 100644 core/models/db_helper.py create mode 100644 core/models/ip.py create mode 100644 core/schemas/__init__.py create mode 100644 core/schemas/ip.py rename {static => web/static}/js/htmx.js (100%) rename {static => web/static}/js/htmx.min.js (100%) rename {static => web/static}/swagger/favicon.png (100%) rename {static => web/static}/swagger/swagger-ui-bundle.js (100%) rename {static => web/static}/swagger/swagger-ui.css (100%) diff --git a/.env-template b/.env-template index 8b2a6f4..0e9dd6a 100644 --- a/.env-template +++ b/.env-template @@ -1,2 +1,3 @@ BLOCKING_IP__DB__URL=postgresql+asyncpg://username:password@localhost:5432/dbname -BLOCKING_IP__DB__ECHO=0 \ No newline at end of file +BLOCKING_IP__DB__ECHO=0 +ALEMBIC_CONFIG=alembic/alembic.ini \ No newline at end of file diff --git a/alembic/versions/2024_08_11_0914-2455bc245a0b_create_products_table.py b/alembic/versions/2024_08_11_0914-2455bc245a0b_create_products_table.py new file mode 100644 index 0000000..f2046e7 --- /dev/null +++ b/alembic/versions/2024_08_11_0914-2455bc245a0b_create_products_table.py @@ -0,0 +1,47 @@ +"""create products table + +Revision ID: 2455bc245a0b +Revises: +Create Date: 2024-08-11 09:14:13.312515 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = "2455bc245a0b" +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( + "ips", + sa.Column("ip", sa.String(), nullable=False), + sa.Column("in_31", sa.String(), nullable=True), + sa.Column("in_30", sa.String(), nullable=True), + sa.Column("in_29", sa.String(), nullable=True), + sa.Column("in_28", sa.String(), nullable=True), + sa.Column("in_27", sa.String(), nullable=True), + sa.Column("in_26", sa.String(), nullable=True), + sa.Column("in_25", sa.String(), nullable=True), + sa.Column("in_24", sa.String(), nullable=True), + sa.Column("min_not_full", sa.String(), nullable=True), + sa.Column("max_full", sa.String(), nullable=True), + sa.Column("domain", sa.String(), nullable=True), + sa.Column("id", sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint("id"), + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("ips") + # ### end Alembic commands ### diff --git a/api/__init__.py b/api/__init__.py index e69de29..5862289 100644 --- a/api/__init__.py +++ b/api/__init__.py @@ -0,0 +1,11 @@ +from fastapi import APIRouter + +from core.config import settings + +from api.ip import router as ip_router + +router = APIRouter() +router.include_router( + ip_router, + prefix=settings.api.ip, +) diff --git a/api/ip.py b/api/ip.py new file mode 100644 index 0000000..e43b637 --- /dev/null +++ b/api/ip.py @@ -0,0 +1,17 @@ +from typing import Annotated + +from fastapi import APIRouter, Depends +from sqlalchemy.ext.asyncio import AsyncSession + +from core.models import db_helper +from core.schemas.ip import IpRead + + +router = APIRouter( + tags=["Ip"], +) + + +@router.get("", response_model=list[IpRead]) +async def get_ip(session: AsyncSession = Depends(db_helper.session_dependency)): + pass diff --git a/core/config.py b/core/config.py index 6979155..8b21a5b 100644 --- a/core/config.py +++ b/core/config.py @@ -22,7 +22,7 @@ class DatabaseConfig(BaseModel): class ApiPrefix(BaseModel): - pass + ip: str = "/api/ip" class WebPrefix(BaseModel): @@ -51,16 +51,17 @@ class Settings(BaseSettings): db: DatabaseConfig -def config_logging(level: str): +settings = Settings() + + +def config_logging(level: str) -> None: logging.basicConfig( - level=level, - datefmt="%Y-%m-%d %H:%M:%S", - format="[%(asctime)s.%(msecs)03d] %(module)-15s:%(lineno)4d | %(funcName)-20s| %(levelname)-8s | %(message)s", + level=settings.log.log_level, + datefmt=settings.log.date_format, + format=settings.log.log_format, ) -settings = Settings() - config_logging(level=settings.log.log_level) log = logging.getLogger() log.info("Logging initialized") diff --git a/core/crud/__init__.py b/core/crud/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/models/__init__.py b/core/models/__init__.py index ce34e76..85d95cf 100644 --- a/core/models/__init__.py +++ b/core/models/__init__.py @@ -1,3 +1,9 @@ -__all__ = ("Base",) +__all__ = ( + "Base", + "Ip", + "db_helper", +) from .base import Base +from .ip import Ip +from .db_helper import DatabaseHelper, db_helper diff --git a/core/models/db_helper.py b/core/models/db_helper.py new file mode 100644 index 0000000..396bfe7 --- /dev/null +++ b/core/models/db_helper.py @@ -0,0 +1,43 @@ +from asyncio import current_task + +from sqlalchemy.ext.asyncio import ( + AsyncSession, + create_async_engine, + async_sessionmaker, + async_scoped_session, +) + +from core.config import settings + + +class DatabaseHelper: + def __init__(self, url: str, echo: bool = False): + self.engine = create_async_engine( + url=url, + echo=echo, + ) + self.session_factory = async_sessionmaker( + bind=self.engine, + autoflush=False, + autocommit=False, + expire_on_commit=False, + ) + + def get_scoped_session(self): + session = async_scoped_session( + session_factory=self.session_factory, + scopefunc=current_task, + ) + return session + + async def session_dependency(self) -> AsyncSession: + + async with self.session_factory() as session: + yield session + await session.close() + + +db_helper = DatabaseHelper( + url=settings.db.url, + echo=settings.db.echo, +) diff --git a/core/models/ip.py b/core/models/ip.py new file mode 100644 index 0000000..3bf8a03 --- /dev/null +++ b/core/models/ip.py @@ -0,0 +1,19 @@ +from sqlalchemy.orm import Mapped +from sqlalchemy.orm import mapped_column +from sqlalchemy.orm import relationship +from .base import Base + + +class Ip(Base): + ip: Mapped[str] + in_31: Mapped[str] = mapped_column(nullable=True) + in_30: Mapped[str] = mapped_column(nullable=True) + in_29: Mapped[str] = mapped_column(nullable=True) + in_28: Mapped[str] = mapped_column(nullable=True) + in_27: Mapped[str] = mapped_column(nullable=True) + in_26: Mapped[str] = mapped_column(nullable=True) + in_25: Mapped[str] = mapped_column(nullable=True) + in_24: Mapped[str] = mapped_column(nullable=True) + min_not_full: Mapped[str] = mapped_column(nullable=True) + max_full: Mapped[str] = mapped_column(nullable=True) + domain: Mapped[str] = mapped_column(nullable=True) diff --git a/core/schemas/__init__.py b/core/schemas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/schemas/ip.py b/core/schemas/ip.py new file mode 100644 index 0000000..2352066 --- /dev/null +++ b/core/schemas/ip.py @@ -0,0 +1,20 @@ +from pydantic import BaseModel + + +class IpBase(BaseModel): + ip: str + in_31: str + in_30: str + in_29: str + in_28: str + in_27: str + in_26: str + in_25: str + in_24: str + min_not_full: str + max_full: str + domain: str + + +class IpRead(IpBase): + id: int diff --git a/main.py b/main.py index ea1b7c0..2645569 100644 --- a/main.py +++ b/main.py @@ -6,18 +6,18 @@ from fastapi import FastAPI from fastapi.openapi.docs import get_swagger_ui_html from fastapi.responses import ORJSONResponse from starlette.staticfiles import StaticFiles +from api import router as api_router from core.config import BASE_DIR, settings +from core.models import db_helper log = logging.getLogger() @asynccontextmanager async def lifespan(app: FastAPI): - try: - yield - finally: - pass + yield + await db_helper.dispose() main_app = FastAPI( @@ -27,12 +27,14 @@ main_app = FastAPI( ) -main_app.mount("/static", StaticFiles(directory=BASE_DIR / "static"), name="static") +main_app.mount( + "/static", StaticFiles(directory=BASE_DIR / "web" / "static"), name="static" +) +main_app.include_router(api_router) @main_app.get("/docs", include_in_schema=False) async def custom_swagger_ui_html(): - print(main_app.openapi_url) return get_swagger_ui_html( openapi_url=main_app.openapi_url, title="Blocked IP API", diff --git a/poetry.lock b/poetry.lock index cae1a71..8b194b2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -50,6 +50,60 @@ doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphin test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] trio = ["trio (>=0.23)"] +[[package]] +name = "asyncpg" +version = "0.29.0" +description = "An asyncio PostgreSQL driver" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "asyncpg-0.29.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72fd0ef9f00aeed37179c62282a3d14262dbbafb74ec0ba16e1b1864d8a12169"}, + {file = "asyncpg-0.29.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52e8f8f9ff6e21f9b39ca9f8e3e33a5fcdceaf5667a8c5c32bee158e313be385"}, + {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e6823a7012be8b68301342ba33b4740e5a166f6bbda0aee32bc01638491a22"}, + {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:746e80d83ad5d5464cfbf94315eb6744222ab00aa4e522b704322fb182b83610"}, + {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ff8e8109cd6a46ff852a5e6bab8b0a047d7ea42fcb7ca5ae6eaae97d8eacf397"}, + {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97eb024685b1d7e72b1972863de527c11ff87960837919dac6e34754768098eb"}, + {file = "asyncpg-0.29.0-cp310-cp310-win32.whl", hash = "sha256:5bbb7f2cafd8d1fa3e65431833de2642f4b2124be61a449fa064e1a08d27e449"}, + {file = "asyncpg-0.29.0-cp310-cp310-win_amd64.whl", hash = "sha256:76c3ac6530904838a4b650b2880f8e7af938ee049e769ec2fba7cd66469d7772"}, + {file = "asyncpg-0.29.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4900ee08e85af01adb207519bb4e14b1cae8fd21e0ccf80fac6aa60b6da37b4"}, + {file = "asyncpg-0.29.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a65c1dcd820d5aea7c7d82a3fdcb70e096f8f70d1a8bf93eb458e49bfad036ac"}, + {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b52e46f165585fd6af4863f268566668407c76b2c72d366bb8b522fa66f1870"}, + {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc600ee8ef3dd38b8d67421359779f8ccec30b463e7aec7ed481c8346decf99f"}, + {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:039a261af4f38f949095e1e780bae84a25ffe3e370175193174eb08d3cecab23"}, + {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6feaf2d8f9138d190e5ec4390c1715c3e87b37715cd69b2c3dfca616134efd2b"}, + {file = "asyncpg-0.29.0-cp311-cp311-win32.whl", hash = "sha256:1e186427c88225ef730555f5fdda6c1812daa884064bfe6bc462fd3a71c4b675"}, + {file = "asyncpg-0.29.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfe73ffae35f518cfd6e4e5f5abb2618ceb5ef02a2365ce64f132601000587d3"}, + {file = "asyncpg-0.29.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6011b0dc29886ab424dc042bf9eeb507670a3b40aece3439944006aafe023178"}, + {file = "asyncpg-0.29.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b544ffc66b039d5ec5a7454667f855f7fec08e0dfaf5a5490dfafbb7abbd2cfb"}, + {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d84156d5fb530b06c493f9e7635aa18f518fa1d1395ef240d211cb563c4e2364"}, + {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54858bc25b49d1114178d65a88e48ad50cb2b6f3e475caa0f0c092d5f527c106"}, + {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bde17a1861cf10d5afce80a36fca736a86769ab3579532c03e45f83ba8a09c59"}, + {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:37a2ec1b9ff88d8773d3eb6d3784dc7e3fee7756a5317b67f923172a4748a175"}, + {file = "asyncpg-0.29.0-cp312-cp312-win32.whl", hash = "sha256:bb1292d9fad43112a85e98ecdc2e051602bce97c199920586be83254d9dafc02"}, + {file = "asyncpg-0.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:2245be8ec5047a605e0b454c894e54bf2ec787ac04b1cb7e0d3c67aa1e32f0fe"}, + {file = "asyncpg-0.29.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0009a300cae37b8c525e5b449233d59cd9868fd35431abc470a3e364d2b85cb9"}, + {file = "asyncpg-0.29.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cad1324dbb33f3ca0cd2074d5114354ed3be2b94d48ddfd88af75ebda7c43cc"}, + {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:012d01df61e009015944ac7543d6ee30c2dc1eb2f6b10b62a3f598beb6531548"}, + {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000c996c53c04770798053e1730d34e30cb645ad95a63265aec82da9093d88e7"}, + {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e0bfe9c4d3429706cf70d3249089de14d6a01192d617e9093a8e941fea8ee775"}, + {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:642a36eb41b6313ffa328e8a5c5c2b5bea6ee138546c9c3cf1bffaad8ee36dd9"}, + {file = "asyncpg-0.29.0-cp38-cp38-win32.whl", hash = "sha256:a921372bbd0aa3a5822dd0409da61b4cd50df89ae85150149f8c119f23e8c408"}, + {file = "asyncpg-0.29.0-cp38-cp38-win_amd64.whl", hash = "sha256:103aad2b92d1506700cbf51cd8bb5441e7e72e87a7b3a2ca4e32c840f051a6a3"}, + {file = "asyncpg-0.29.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5340dd515d7e52f4c11ada32171d87c05570479dc01dc66d03ee3e150fb695da"}, + {file = "asyncpg-0.29.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e17b52c6cf83e170d3d865571ba574577ab8e533e7361a2b8ce6157d02c665d3"}, + {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f100d23f273555f4b19b74a96840aa27b85e99ba4b1f18d4ebff0734e78dc090"}, + {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48e7c58b516057126b363cec8ca02b804644fd012ef8e6c7e23386b7d5e6ce83"}, + {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f9ea3f24eb4c49a615573724d88a48bd1b7821c890c2effe04f05382ed9e8810"}, + {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8d36c7f14a22ec9e928f15f92a48207546ffe68bc412f3be718eedccdf10dc5c"}, + {file = "asyncpg-0.29.0-cp39-cp39-win32.whl", hash = "sha256:797ab8123ebaed304a1fad4d7576d5376c3a006a4100380fb9d517f0b59c1ab2"}, + {file = "asyncpg-0.29.0-cp39-cp39-win_amd64.whl", hash = "sha256:cce08a178858b426ae1aa8409b5cc171def45d4293626e7aa6510696d46decd8"}, + {file = "asyncpg-0.29.0.tar.gz", hash = "sha256:d1c49e1f44fffafd9a55e1a9b101590859d881d639ea2922516f5d9c512d354e"}, +] + +[package.extras] +docs = ["Sphinx (>=5.3.0,<5.4.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +test = ["flake8 (>=6.1,<7.0)", "uvloop (>=0.15.3)"] + [[package]] name = "black" version = "24.8.0" @@ -2084,4 +2138,4 @@ test = ["pytest"] [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "c95e844e16dac52a1d26b4f1c4d6dbefda69cf7b5b311581048a58ff10791c08" +content-hash = "b47ab2c35ee5526f7f4269b0d38f832aeda60b141f4f41cd5252ff2e05c68c74" diff --git a/pyproject.toml b/pyproject.toml index 81be12e..38f20de 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ pydantic-settings = "^2.4.0" sqlalchemy = {extras = ["asyncio"], version = "^2.0.32"} poetry = "^1.8.3" alembic = "^1.13.2" +asyncpg = "^0.29.0" [tool.poetry.group.dev.dependencies] black = "^24.8.0" diff --git a/static/js/htmx.js b/web/static/js/htmx.js similarity index 100% rename from static/js/htmx.js rename to web/static/js/htmx.js diff --git a/static/js/htmx.min.js b/web/static/js/htmx.min.js similarity index 100% rename from static/js/htmx.min.js rename to web/static/js/htmx.min.js diff --git a/static/swagger/favicon.png b/web/static/swagger/favicon.png similarity index 100% rename from static/swagger/favicon.png rename to web/static/swagger/favicon.png diff --git a/static/swagger/swagger-ui-bundle.js b/web/static/swagger/swagger-ui-bundle.js similarity index 100% rename from static/swagger/swagger-ui-bundle.js rename to web/static/swagger/swagger-ui-bundle.js diff --git a/static/swagger/swagger-ui.css b/web/static/swagger/swagger-ui.css similarity index 100% rename from static/swagger/swagger-ui.css rename to web/static/swagger/swagger-ui.css