[+] add ruff

1. add ruff recipies to Makefile;
  2. reformat source code with ruff;
This commit is contained in:
Siarhei Siniak 2025-07-18 10:11:28 +03:00
parent 207a8737ba
commit f77399b1d2
10 changed files with 130 additions and 105 deletions

@ -37,6 +37,19 @@ pyright:
-p pyproject.toml \ -p pyproject.toml \
--pythonpath $(PYTHON_PATH) --pythonpath $(PYTHON_PATH)
ruff_check:
$(ENV_PATH)/bin/python3 -m ruff \
check
ruff_format_check:
$(ENV_PATH)/bin/python3 -m ruff \
format --check
ruff_format:
$(ENV_PATH)/bin/python3 -m ruff \
format
ruff: ruff_format_check ruff_check
compose_env: compose_env:
cat docker/postgresql/.env .env/postgresql.env > .env/postgresql.patched.env cat docker/postgresql/.env .env/postgresql.env > .env/postgresql.patched.env

@ -4,12 +4,9 @@ import sqlalchemy.ext.asyncio
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.ext.asyncio import async_sessionmaker from sqlalchemy.ext.asyncio import async_sessionmaker
def create_engine() -> 'async_sessionmaker[AsyncSession]': def create_engine() -> 'async_sessionmaker[AsyncSession]':
engine = sqlalchemy.ext.asyncio.create_async_engine( engine = sqlalchemy.ext.asyncio.create_async_engine(ModelsSettings.singleton().db_url)
ModelsSettings.singleton().db_url async_session = sqlalchemy.ext.asyncio.async_sessionmaker(engine)
)
async_session = sqlalchemy.ext.asyncio.async_sessionmaker(
engine
)
return async_session return async_session

@ -11,7 +11,12 @@ from .settings import Settings as APISettings
from .db import create_engine from .db import create_engine
# from .websocket_api import WebsocketAPI # from .websocket_api import WebsocketAPI
from typing import (Any, Optional, Literal, Annotated,) from typing import (
Any,
Optional,
Literal,
Annotated,
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -35,6 +40,7 @@ logger = logging.getLogger(__name__)
# finally: # finally:
# await websocket_api.disconnect(websocket) # await websocket_api.disconnect(websocket)
def create_app() -> fastapi.FastAPI: def create_app() -> fastapi.FastAPI:
async_session = create_engine() async_session = create_engine()
@ -55,6 +61,7 @@ def create_app() -> fastapi.FastAPI:
return app return app
def run(args: list[str]): def run(args: list[str]):
log_config = copy.deepcopy(uvicorn.config.LOGGING_CONFIG) log_config = copy.deepcopy(uvicorn.config.LOGGING_CONFIG)
@ -67,5 +74,6 @@ def run(args: list[str]):
log_level=logging.INFO, log_level=logging.INFO,
) )
if __name__ == '__main__': if __name__ == '__main__':
run(sys.argv[1:]) run(sys.argv[1:])

@ -1,7 +1,10 @@
import pydantic import pydantic
import decimal import decimal
from typing import (Literal, Annotated,) from typing import (
Literal,
Annotated,
)
# class SubscribeAction(pydantic.BaseModel): # class SubscribeAction(pydantic.BaseModel):
# action: Literal['subscribe'] # action: Literal['subscribe']

@ -1,14 +1,17 @@
import pydantic import pydantic
import pydantic_settings import pydantic_settings
from typing import (ClassVar, Optional,) from typing import (
ClassVar,
Optional,
)
class Settings(pydantic_settings.BaseSettings): class Settings(pydantic_settings.BaseSettings):
uvicorn_port : int = 80 uvicorn_port: int = 80
uvicorn_host : str = '127.0.0.1' uvicorn_host: str = '127.0.0.1'
_singleton : ClassVar[Optional['Settings']] = None _singleton: ClassVar[Optional['Settings']] = None
@classmethod @classmethod
def singleton(cls) -> 'Settings': def singleton(cls) -> 'Settings':

@ -14,18 +14,15 @@ from alembic import context
from online.fxreader.pr34.test_task_2025_07_17_v2.payloads.settings import Settings from online.fxreader.pr34.test_task_2025_07_17_v2.payloads.settings import Settings
from online.fxreader.pr34.test_task_2025_07_17_v2.payloads.models import ( from online.fxreader.pr34.test_task_2025_07_17_v2.payloads.models import (
Base, Base,
# Market, # Market,
) )
# this is the Alembic Config object, which provides # this is the Alembic Config object, which provides
# access to the values within the .ini file in use. # access to the values within the .ini file in use.
config = context.config config = context.config
config.set_main_option( config.set_main_option('sqlalchemy.url', Settings.singleton().db_url)
'sqlalchemy.url',
Settings.singleton().db_url
)
# Interpret the config file for Python logging. # Interpret the config file for Python logging.
# This line sets up loggers basically. # This line sets up loggers basically.
@ -33,7 +30,7 @@ config.set_main_option(
# fileConfig(config.config_file_name) # fileConfig(config.config_file_name)
# else: # else:
if True: if True:
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -51,67 +48,67 @@ target_metadata = Base.metadata
def do_run_migrations( def do_run_migrations(
connection: Connection, connection: Connection,
): ):
context.configure(connection=connection, target_metadata=target_metadata) context.configure(connection=connection, target_metadata=target_metadata)
with context.begin_transaction():
context.run_migrations()
with context.begin_transaction():
context.run_migrations()
async def run_async_migrations(): async def run_async_migrations():
"""In this scenario we need to create an Engine """In this scenario we need to create an Engine
and associate a connection with the context. and associate a connection with the context.
""" """
logger.info(dict(msg='started')) logger.info(dict(msg='started'))
connectable = async_engine_from_config( connectable = async_engine_from_config(
config.get_section(config.config_ini_section, {}), config.get_section(config.config_ini_section, {}),
prefix="sqlalchemy.", prefix='sqlalchemy.',
poolclass=pool.NullPool, poolclass=pool.NullPool,
) )
async with connectable.connect() as connection:
await connection.run_sync(do_run_migrations)
async with connectable.connect() as connection: await connectable.dispose()
await connection.run_sync(do_run_migrations)
await connectable.dispose() logger.info(dict(msg='done'))
logger.info(dict(msg='done'))
def run_migrations_offline(): def run_migrations_offline():
"""Run migrations in 'offline' mode. """Run migrations in 'offline' mode.
This configures the context with just a URL This configures the context with just a URL
and not an Engine, though an Engine is acceptable and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation here as well. By skipping the Engine creation
we don't even need a DBAPI to be available. we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the Calls to context.execute() here emit the given string to the
script output. script output.
""" """
url = config.get_main_option("sqlalchemy.url") url = config.get_main_option('sqlalchemy.url')
context.configure( context.configure(
url=url, url=url,
target_metadata=target_metadata, target_metadata=target_metadata,
literal_binds=True, literal_binds=True,
dialect_opts={"paramstyle": "named"}, dialect_opts={'paramstyle': 'named'},
) )
with context.begin_transaction(): with context.begin_transaction():
context.run_migrations() context.run_migrations()
def run_migrations_online(): def run_migrations_online():
"""Run migrations in 'online' mode.""" """Run migrations in 'online' mode."""
asyncio.run(run_async_migrations()) asyncio.run(run_async_migrations())
if context.is_offline_mode(): if context.is_offline_mode():
raise NotImplementedError raise NotImplementedError
# run_migrations_offline() # run_migrations_offline()
else: else:
run_migrations_online() run_migrations_online()

@ -1,10 +1,11 @@
"""add payloads models """add payloads models
Revision ID: f7fa90d3339d Revision ID: f7fa90d3339d
Revises: Revises:
Create Date: 2025-07-18 09:58:54.099010 Create Date: 2025-07-18 09:58:54.099010
""" """
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op from alembic import op
@ -19,22 +20,23 @@ depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None: def upgrade() -> None:
"""Upgrade schema.""" """Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.create_table('payloads_payload', op.create_table(
sa.Column('id', sa.Integer(), nullable=False), 'payloads_payload',
sa.Column('output', sa.JSON(), nullable=False), sa.Column('id', sa.Integer(), nullable=False),
sa.Column('list_1', sa.JSON(), nullable=False), sa.Column('output', sa.JSON(), nullable=False),
sa.Column('list_2', sa.JSON(), nullable=False), sa.Column('list_1', sa.JSON(), nullable=False),
sa.Column('input_hash', sa.String(), nullable=False), sa.Column('list_2', sa.JSON(), nullable=False),
sa.PrimaryKeyConstraint('id'), sa.Column('input_hash', sa.String(), nullable=False),
sa.UniqueConstraint('input_hash') sa.PrimaryKeyConstraint('id'),
) sa.UniqueConstraint('input_hash'),
# ### end Alembic commands ### )
# ### end Alembic commands ###
def downgrade() -> None: def downgrade() -> None:
"""Downgrade schema.""" """Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.drop_table('payloads_payload') op.drop_table('payloads_payload')
# ### end Alembic commands ### # ### end Alembic commands ###

@ -17,11 +17,15 @@ from sqlalchemy import (
JSON, JSON,
) )
from typing import (Optional,) from typing import (
Optional,
)
class Base(DeclarativeBase): class Base(DeclarativeBase):
pass pass
class Payload(Base): class Payload(Base):
__tablename__ = 'payloads_payload' __tablename__ = 'payloads_payload'
@ -31,16 +35,16 @@ class Payload(Base):
list_2: Mapped[list[str]] = mapped_column(JSON()) list_2: Mapped[list[str]] = mapped_column(JSON())
input_hash: Mapped[str] = mapped_column() input_hash: Mapped[str] = mapped_column()
__table_args__ = ( __table_args__ = (UniqueConstraint('input_hash'),)
UniqueConstraint('input_hash'),
)
def __repr__(self) -> str: def __repr__(self) -> str:
return json.dumps(dict( return json.dumps(
model=str(type(self)), dict(
id=self.id, model=str(type(self)),
output=self.output, id=self.id,
list_1=self.list_1, output=self.output,
list_2=self.list_2, list_1=self.list_1,
input_hash=self.input_hash, list_2=self.list_2,
)) input_hash=self.input_hash,
)
)

@ -1,13 +1,16 @@
import pydantic import pydantic
import pydantic_settings import pydantic_settings
from typing import (ClassVar, Optional,) from typing import (
ClassVar,
Optional,
)
class Settings(pydantic_settings.BaseSettings): class Settings(pydantic_settings.BaseSettings):
db_url : str db_url: str
_singleton : ClassVar[Optional['Settings']] = None _singleton: ClassVar[Optional['Settings']] = None
@classmethod @classmethod
def singleton(cls) -> 'Settings': def singleton(cls) -> 'Settings':

@ -1,4 +1,9 @@
from typing import (TypeVar, Optional, Any, cast,) from typing import (
TypeVar,
Optional,
Any,
cast,
)
from sqlalchemy.ext.asyncio import AsyncSessionTransaction, AsyncSession from sqlalchemy.ext.asyncio import AsyncSessionTransaction, AsyncSession
from sqlalchemy.future import select from sqlalchemy.future import select
from sqlalchemy.orm import DeclarativeBase from sqlalchemy.orm import DeclarativeBase
@ -6,22 +11,12 @@ from sqlalchemy.exc import NoResultFound, IntegrityError
M = TypeVar('M', bound='DeclarativeBase') M = TypeVar('M', bound='DeclarativeBase')
async def get_or_create( async def get_or_create(
session: AsyncSession, session: AsyncSession, model: type[M], create_method: Optional[str] = None, create_method_kwargs: Optional[dict[str, Any]] = None, **kwargs: Any
model: type[M],
create_method: Optional[str] = None,
create_method_kwargs: Optional[dict[str, Any]] = None,
**kwargs: Any
) -> tuple[M, bool]: ) -> tuple[M, bool]:
async def select_row() -> M: async def select_row() -> M:
res = await session.execute( res = await session.execute(select(model).where(*[getattr(model, k) == v for k, v in kwargs.items()]))
select(model).where(
*[
getattr(model, k) == v
for k, v in kwargs.items()
]
)
)
row = res.one()[0] row = res.one()[0]