From da97fada0e77a9cd3cc6c4b4999dc1cc49e445f5 Mon Sep 17 00:00:00 2001 From: Eden Kirin Date: Fri, 31 Oct 2025 13:34:56 +0100 Subject: [PATCH] Initial --- .gitignore | 1 + CLAUDE.md | 52 ++++++++++++ example/cashbag_confirms.sql | 100 ++++++++++++++++++++++++ example/cashbag_conform/__init__.py | 0 example/cashbag_conform/enum.py | 9 +++ example/cashbag_conform/factory.py | 84 ++++++++++++++++++++ example/cashbag_conform/filter.py | 14 ++++ example/cashbag_conform/load_options.py | 12 +++ example/cashbag_conform/manager.py | 14 ++++ example/cashbag_conform/model.py | 54 +++++++++++++ example/cashbag_conform/repository.py | 9 +++ example/cashbag_conform/table.py | 78 ++++++++++++++++++ 12 files changed, 427 insertions(+) create mode 100644 .gitignore create mode 100644 CLAUDE.md create mode 100644 example/cashbag_confirms.sql create mode 100644 example/cashbag_conform/__init__.py create mode 100644 example/cashbag_conform/enum.py create mode 100644 example/cashbag_conform/factory.py create mode 100644 example/cashbag_conform/filter.py create mode 100644 example/cashbag_conform/load_options.py create mode 100644 example/cashbag_conform/manager.py create mode 100644 example/cashbag_conform/model.py create mode 100644 example/cashbag_conform/repository.py create mode 100644 example/cashbag_conform/table.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1f57b97 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/output diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..5ebfe16 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,52 @@ +# Entity maker + +## Description + +Application connects to postgres database, inspects targeted table and creates: + +- sqlalchemy table +- model +- filter +- load options +- repository +- manager +- factory + +Generated source files are targeted for Python 3.13 using SQLAlchemy. + +## Input parameters + +Application have following command line parameters: +- optional db host - default `localhost` +- optional db port - default `5432` +- optional db name +- optional db schema - default `public` +- optional db user - default `postgres` +- optional db password - default `postgres` +- optional db table +- optional output directory +- optional entity name + +When application starts, it will offer users to enter missing parameters from command line, using defaults as listed above. Save entered values in `~/.config/entity-maker` and use it for the next time. + +Generated files are placed in specificied output directory, in subdirectory named after table name, but in sigular. Check naming section for details. + +## Examples + +Example sql table structure is located in `./example/cashbag_conforms.sql`. +Example output is located in `./example/cashbag_conform`. + +## Naming example + +Table name: `cashbag_conforms`. +Output subdirectory: `cashbag_conform` +Model name: `CashbagConform` +Filter name `CashbagConformFilter` +Load options name `CashbagConformLoadOptions` +Repository name `CashbagConformRepository` +Manager name `CashbagConformManager` +Factory name `CashbagConformFactory` + +## Technologies used + +This is command line application. Application is written in golang. Application needs to be run on Linux only, no need to support other operating systems. Make application output modern and colorful. diff --git a/example/cashbag_confirms.sql b/example/cashbag_confirms.sql new file mode 100644 index 0000000..c293915 --- /dev/null +++ b/example/cashbag_confirms.sql @@ -0,0 +1,100 @@ +CREATE TABLE cashbag_conforms +( + id integer DEFAULT NEXTVAL('cashbag_conforms_id_seq'::regclass) NOT NULL + PRIMARY KEY, + alive boolean NOT NULL, + count_coins numeric(12, 4) NOT NULL, + count_bills numeric(12, 4) NOT NULL, + tokens_number numeric(12, 4) NOT NULL, + tokens_total numeric(12, 4) NOT NULL, + count_timestamp timestamp with time zone, + count_receive_timestamp timestamp with time zone, + collect numeric(12, 4) NOT NULL, + collect_timestamp timestamp with time zone, + diff numeric(12, 4) NOT NULL, + author_info_id integer NOT NULL + UNIQUE + CONSTRAINT cashbag_conf_author_info_id_1663387ed38e04b8_fk_author_infos_id + REFERENCES author_infos + DEFERRABLE INITIALLY DEFERRED, + cashbag_id integer + CONSTRAINT cashbag_conforms_cashbag_id_83139826_fk_cashbags_id + REFERENCES cashbags + DEFERRABLE INITIALLY DEFERRED, + cashflow_collection_id integer + CONSTRAINT "D4f92ee700d19ebb5990b785681a01fe" + REFERENCES cashflow_collections + DEFERRABLE INITIALLY DEFERRED, + collect_user_id integer + CONSTRAINT ca_collect_user_id_7d633e574f6c25a9_fk_custom_users_user_ptr_id + REFERENCES custom_users + DEFERRABLE INITIALLY DEFERRED, + count_user_id integer + CONSTRAINT cash_count_user_id_2601834f194be798_fk_custom_users_user_ptr_id + REFERENCES custom_users + DEFERRABLE INITIALLY DEFERRED, + machine_id integer + CONSTRAINT cashbag_conforms_machine_id_246129c5a92a0a91_fk_machines_id + REFERENCES machines + DEFERRABLE INITIALLY DEFERRED, + route_id integer + CONSTRAINT cashbag_conforms_route_id_18cfcd0d417870ef_fk_route_route_id + REFERENCES route_route + DEFERRABLE INITIALLY DEFERRED, + external_route_id bigint, + external_route_name varchar(255), + no_cashbag_reason integer, + _ver bigint, + description text, + status cashbag_conform_status_enum +); + +ALTER TABLE cashbag_conforms + OWNER TO svc_cloud; + +CREATE INDEX cashbag_conforms_6788849c + ON cashbag_conforms (cashbag_id); + +CREATE INDEX cashbag_conforms_6fce81cc + ON cashbag_conforms (cashflow_collection_id); + +CREATE INDEX cashbag_conforms_7016d9d6 + ON cashbag_conforms (collect_user_id); + +CREATE INDEX cashbag_conforms_b4347999 + ON cashbag_conforms (route_id); + +CREATE INDEX cashbag_conforms_d8f07203 + ON cashbag_conforms (count_user_id); + +CREATE INDEX cashbag_conforms_external_route_id_12045111 + ON cashbag_conforms (external_route_id); + +CREATE INDEX idx_cashbag_conforms_ver + ON cashbag_conforms (COALESCE(_ver, 0::bigint)); + +CREATE INDEX idx_cashbag_conforms_collect_timestamp + ON cashbag_conforms (collect_timestamp); + +CREATE INDEX idx_cashbag_conforms_count_timestamp + ON cashbag_conforms (count_timestamp); + +CREATE INDEX idx_cashbag_conforms_machine_collect_timestamp + ON cashbag_conforms (machine_id, collect_timestamp); + +CREATE TRIGGER tr_ver_i_cashbag_conforms + BEFORE INSERT + ON cashbag_conforms + FOR EACH ROW +EXECUTE PROCEDURE tr_ver_cashbag_conforms(); + +CREATE TRIGGER tr_ver_u_cashbag_conforms + BEFORE UPDATE + ON cashbag_conforms + FOR EACH ROW + WHEN (old.* IS DISTINCT FROM new.*) +EXECUTE PROCEDURE tr_ver_cashbag_conforms(); + +GRANT INSERT, UPDATE ON cashbag_conforms TO cloud_write; + +GRANT SELECT ON cashbag_conforms TO cloud_read; diff --git a/example/cashbag_conform/__init__.py b/example/cashbag_conform/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/example/cashbag_conform/enum.py b/example/cashbag_conform/enum.py new file mode 100644 index 0000000..0e6861e --- /dev/null +++ b/example/cashbag_conform/enum.py @@ -0,0 +1,9 @@ +from enum import StrEnum + +from televend_core.databases.enum import EnumMixin + + +class CashBagConformStatusEnum(EnumMixin, StrEnum): + OPEN = "OPEN" + IN_PROGRESS = "IN_PROGRESS" + DONE = "DONE" diff --git a/example/cashbag_conform/factory.py b/example/cashbag_conform/factory.py new file mode 100644 index 0000000..2fd200a --- /dev/null +++ b/example/cashbag_conform/factory.py @@ -0,0 +1,84 @@ +from __future__ import annotations + +from typing import Type + +import factory + +from televend_core.databases.televend_repositories.author_info.factory import ( + AuthorInfoFactory, +) +from televend_core.databases.televend_repositories.cashbag.factory import ( + CashBagFactory, +) +from televend_core.databases.televend_repositories.cashbag_conform.model import ( + CashBagConform, +) +from televend_core.databases.televend_repositories.cashflow_collection.factory import ( + CashFlowCollectionFactory, +) +from televend_core.databases.televend_repositories.custom_user.factory import ( + CustomUserFactory, +) +from televend_core.databases.televend_repositories.machine.factory import MachineFactory +from televend_core.test_extras.factory_boy_utils import ( + CustomSelfAttribute, + TelevendBaseFactory, +) + + +class CashBagConformFactory(TelevendBaseFactory): + alive = True + id = None + + count_coins = factory.Faker("pydecimal", left_digits=7, right_digits=4, positive=True) + count_bills = factory.Faker("pydecimal", left_digits=7, right_digits=4, positive=True) + tokens_number = factory.Faker("pydecimal", left_digits=7, right_digits=4, positive=True) + tokens_total = factory.Faker("pydecimal", left_digits=7, right_digits=4, positive=True) + count_timestamp = factory.Faker("date_time") + count_receive_timestamp = factory.Faker("date_time") + collect = factory.Faker("pydecimal", left_digits=7, right_digits=4, positive=True) + collect_timestamp = factory.Faker("date_time") + diff = factory.Faker("pydecimal", left_digits=7, right_digits=4, positive=True) + + author_info = CustomSelfAttribute("..author_info", AuthorInfoFactory) + author_info_id = factory.LazyAttribute(lambda a: a.author_info.id if a.author_info else None) + + cashbag = CustomSelfAttribute("..cashbag", CashBagFactory) + cashbag_id = factory.LazyAttribute(lambda a: a.cashbag.id if a.cashbag else None) + + cashflow_collection = CustomSelfAttribute("..cashflow_collection", CashFlowCollectionFactory) + cashflow_collection_id = factory.LazyAttribute( + lambda a: a.cashflow_collection.id if a.cashflow_collection else None + ) + + collect_user = CustomSelfAttribute("..collect_user", CustomUserFactory) + collect_user_id = factory.LazyAttribute( + lambda a: a.collect_user.user_ptr_id if a.collect_user else None + ) + count_user = CustomSelfAttribute("..count_user", CustomUserFactory) + count_user_id = factory.LazyAttribute( + lambda a: a.count_user.user_ptr_id if a.count_user else None + ) + + machine = CustomSelfAttribute("..machine", MachineFactory) + machine_id = factory.LazyAttribute(lambda a: a.machine.id if a.machine else None) + + external_route_id = factory.Faker("pyint") + external_route_name = factory.Faker("pystr", max_chars=255) + no_cashbag_reason = factory.Faker("pyint") + + class Meta: + model = CashBagConform + + @classmethod + def create_minimal(cls: Type[CashBagConformFactory], **kwargs) -> CashBagConform: + minimal_params = { + "author_info": kwargs.pop("author_info", None) or AuthorInfoFactory.create_minimal(), + "cashbag": None, + "cashflow_collection": None, + "collect_user": None, + "count_user": None, + "machine": None, + } + minimal_params.update(kwargs) + return cls.create(**minimal_params) diff --git a/example/cashbag_conform/filter.py b/example/cashbag_conform/filter.py new file mode 100644 index 0000000..03431dd --- /dev/null +++ b/example/cashbag_conform/filter.py @@ -0,0 +1,14 @@ +from televend_core.databases.base_filter import BaseFilter +from televend_core.databases.common.filters.filters import EQ, IN, filterfield +from televend_core.databases.televend_repositories.cashbag_conform.model import CashBagConform + + +class CashBagConformFilter(BaseFilter): + model_cls = CashBagConform + + alive: bool | None = filterfield(operator=EQ, default=True) + ids: list[int] | None = filterfield(field="id", operator=IN) + machine_ids: list[int] | None = filterfield(field="machine_id", operator=IN) + cashflow_collections_ids: list[int] | None = filterfield( + field="cashflow_collections_id", operator=IN + ) diff --git a/example/cashbag_conform/load_options.py b/example/cashbag_conform/load_options.py new file mode 100644 index 0000000..84043c1 --- /dev/null +++ b/example/cashbag_conform/load_options.py @@ -0,0 +1,12 @@ +from televend_core.databases.base_load_options import LoadOptions +from televend_core.databases.common.load_options import joinload +from televend_core.databases.televend_repositories.cashbag_conform.model import CashBagConform + + +class CashBagConformLoadOptions(LoadOptions): + model_cls = CashBagConform + + load_cashflow_collection: bool = joinload(relations=["cashflow_collection"]) + load_machine: bool = joinload(relations=["machine"]) + load_cashbag: bool = joinload(relations=["cashbag"]) + load_denominations: bool = joinload(relations=["denominations"]) diff --git a/example/cashbag_conform/manager.py b/example/cashbag_conform/manager.py new file mode 100644 index 0000000..74265c2 --- /dev/null +++ b/example/cashbag_conform/manager.py @@ -0,0 +1,14 @@ +from televend_core.databases.base_manager import CRUDManager +from televend_core.databases.televend_repositories.cashbag_conform.filter import ( + CashBagConformFilter, +) +from televend_core.databases.televend_repositories.cashbag_conform.model import CashBagConform +from televend_core.databases.televend_repositories.cashbag_conform.repository import ( + CashBagConformRepository, +) + + +class CashBagConformManager( + CRUDManager[CashBagConform, CashBagConformFilter, CashBagConformRepository] +): + repository_cls = CashBagConformRepository diff --git a/example/cashbag_conform/model.py b/example/cashbag_conform/model.py new file mode 100644 index 0000000..f3334c5 --- /dev/null +++ b/example/cashbag_conform/model.py @@ -0,0 +1,54 @@ +from dataclasses import dataclass, field +from datetime import datetime +from decimal import Decimal + +from televend_core.databases.base_model import Base +from televend_core.databases.televend_repositories.author_info.model import AuthorInfo +from televend_core.databases.televend_repositories.cashbag.model import CashBag +from televend_core.databases.televend_repositories.cashbag_conform.enum import ( + CashBagConformStatusEnum, +) +from televend_core.databases.televend_repositories.cashbag_conform_denominations.model import ( + CashBagConformDenomination, +) +from televend_core.databases.televend_repositories.cashflow_collection.model import ( + CashFlowCollection, +) +from televend_core.databases.televend_repositories.custom_user.model import CustomUser +from televend_core.databases.televend_repositories.machine.model import Machine + + +@dataclass +class CashBagConform(Base): + count_coins: Decimal + count_bills: Decimal + tokens_number: Decimal + tokens_total: Decimal + collect: Decimal + diff: Decimal + author_info_id: int + author_info: AuthorInfo + + count_timestamp: datetime | None = None + count_receive_timestamp: datetime | None = None + collect_timestamp: datetime | None = None + cashbag_id: int | None = None + cashbag: CashBag | None = None + cashflow_collection_id: int | None = None + cashflow_collection: CashFlowCollection | None = None + collect_user_id: int | None = None + collect_user: CustomUser | None = None + count_user_id: int | None = None + count_user: CustomUser | None = None + machine_id: int | None = None + machine: Machine | None = None + external_route_id: int | None = None + external_route_name: str | None = None + no_cashbag_reason: int | None = None + description: str | None = None + status: CashBagConformStatusEnum | None = None + + denominations: list[CashBagConformDenomination] | None = field(default_factory=list) + + alive: bool = True + id: int | None = None diff --git a/example/cashbag_conform/repository.py b/example/cashbag_conform/repository.py new file mode 100644 index 0000000..f5b8b10 --- /dev/null +++ b/example/cashbag_conform/repository.py @@ -0,0 +1,9 @@ +from televend_core.databases.base_repository import CRUDRepository +from televend_core.databases.televend_repositories.cashbag_conform.filter import ( + CashBagConformFilter, +) +from televend_core.databases.televend_repositories.cashbag_conform.model import CashBagConform + + +class CashBagConformRepository(CRUDRepository[CashBagConform, CashBagConformFilter]): + model_cls = CashBagConform diff --git a/example/cashbag_conform/table.py b/example/cashbag_conform/table.py new file mode 100644 index 0000000..cb360d7 --- /dev/null +++ b/example/cashbag_conform/table.py @@ -0,0 +1,78 @@ +from sqlalchemy import ( + BigInteger, + Boolean, + Column, + DateTime, + Enum, + ForeignKey, + Integer, + Numeric, + String, + Table, + Text, +) + +from televend_core.databases.televend_repositories.cashbag_conform.enum import ( + CashBagConformStatusEnum, +) +from televend_core.databases.televend_repositories.table_meta import metadata_obj + +CASHBAG_CONFORM_TABLE = Table( + "cashbag_conforms", + metadata_obj, + Column("id", Integer, primary_key=True, autoincrement=True), + Column("alive", Boolean, nullable=False), + Column("count_coins", Numeric(12, 4), nullable=False), + Column("count_bills", Numeric(12, 4), nullable=False), + Column("tokens_number", Numeric(12, 4), nullable=False), + Column("tokens_total", Numeric(12, 4), nullable=False), + Column("count_timestamp", DateTime(timezone=True)), + Column("count_receive_timestamp", DateTime(timezone=True)), + Column("collect", Numeric(12, 4), nullable=False), + Column("collect_timestamp", DateTime(timezone=True)), + Column("diff", Numeric(12, 4), nullable=False), + Column( + "author_info_id", + Integer, + ForeignKey("author_infos.id", deferrable=True, initially="DEFERRED"), + nullable=False, + unique=True, + ), + Column( + "cashbag_id", + Integer, + ForeignKey("cashbags.id", deferrable=True, initially="DEFERRED"), + ), + Column( + "cashflow_collection_id", + Integer, + ForeignKey("cashflow_collections.id", deferrable=True, initially="DEFERRED"), + ), + Column( + "collect_user_id", + Integer, + ForeignKey("custom_users.user_ptr_id", deferrable=True, initially="DEFERRED"), + ), + Column( + "count_user_id", + Integer, + ForeignKey("custom_users.user_ptr_id", deferrable=True, initially="DEFERRED"), + ), + Column( + "machine_id", + Integer, + ForeignKey("machines.id", deferrable=True, initially="DEFERRED"), + ), + Column("external_route_id", BigInteger), + Column("external_route_name", String(255)), + Column("no_cashbag_reason", Integer), + Column("description", Text, nullable=True), + Column( + "status", + Enum( + *CashBagConformStatusEnum.to_value_list(), + name="cashbag_conform_status_enum", + ), + nullable=True, + ), +)