initializer service deployed and tested
This commit is contained in:
parent
834c78d814
commit
1d4f00e8b2
|
|
@ -0,0 +1 @@
|
|||
3.12
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
1. endpoint uuid must be imported from a common folder
|
||||
2. authservice must be seperated from application checkers service even though it serves values from redis
|
||||
3. Initial services are not functioning well enough
|
||||
4. ApiResponse and SystemResponse must be seperated
|
||||
5.
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /
|
||||
|
||||
# Install system dependencies and Poetry
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends gcc && rm -rf /var/lib/apt/lists/* && pip install --no-cache-dir poetry
|
||||
|
||||
# Copy Poetry configuration
|
||||
COPY /pyproject.toml ./pyproject.toml
|
||||
|
||||
# Configure Poetry and install dependencies with optimizations
|
||||
RUN poetry config virtualenvs.create false && poetry install --no-interaction --no-ansi --no-root --only main && pip cache purge && rm -rf ~/.cache/pypoetry
|
||||
|
||||
# Copy application code
|
||||
COPY /api_services/api_initializer /api_initializer
|
||||
COPY /api_services/api_controllers /api_controllers
|
||||
COPY /api_services/schemas /schemas
|
||||
|
||||
COPY /api_services/api_middlewares /middlewares
|
||||
COPY /api_services/api_builds/auth-service/endpoints /api_initializer/endpoints
|
||||
COPY /api_services/api_builds/auth-service/events /api_initializer/events
|
||||
COPY /api_services/api_builds/auth-service/validations /api_initializer/validations
|
||||
|
||||
# Set Python path to include app directory
|
||||
ENV PYTHONPATH=/ PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1
|
||||
|
||||
# Run the application using the configured uvicorn server
|
||||
CMD ["poetry", "run", "python", "/api_initializer/app.py"]
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
|
||||
__all__ = []
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
endpoints_index: dict = {
|
||||
"Name": "d538deb4-38f4-4913-a1af-bbef14cf6873",
|
||||
"Slot1": "c0f5ccb1-1e56-4653-af13-ec0bf5e6aa51",
|
||||
"Slot2": "034a7eb7-0186-4f48-bb8c-165c429ad5c1",
|
||||
"Slot3": "ec1f3ec3-3f28-4eaf-b89a-c463632c0b90",
|
||||
"Slot4": "2cf99f10-72f0-4c2b-98be-3082d67b950d",
|
||||
"Slot5": "15c24c6c-651b-4c5d-9c2b-5c6c6c6c6c6c",
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
from fastapi import APIRouter
|
||||
|
||||
|
||||
def get_routes() -> list[APIRouter]:
|
||||
return []
|
||||
|
||||
|
||||
def get_safe_endpoint_urls() -> list[tuple[str, str]]:
|
||||
return [
|
||||
("/", "GET"),
|
||||
("/docs", "GET"),
|
||||
("/redoc", "GET"),
|
||||
("/openapi.json", "GET"),
|
||||
("/metrics", "GET"),
|
||||
]
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
|
||||
__all__ = []
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
|
||||
events_index: dict = {
|
||||
"Slot1": "",
|
||||
"Slot2": "",
|
||||
"Slot3": "",
|
||||
"Slot4": "",
|
||||
"Slot5": "",
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /
|
||||
|
||||
# Install system dependencies and Poetry
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends gcc && rm -rf /var/lib/apt/lists/* && pip install --no-cache-dir poetry
|
||||
|
||||
# Copy Poetry configuration
|
||||
COPY /pyproject.toml ./pyproject.toml
|
||||
|
||||
# Configure Poetry and install dependencies with optimizations
|
||||
RUN poetry config virtualenvs.create false && poetry install --no-interaction --no-ansi --no-root --only main && pip cache purge && rm -rf ~/.cache/pypoetry
|
||||
|
||||
# Copy application code
|
||||
COPY /api_services/api_controllers /api_controllers
|
||||
COPY /api_services/schemas /schemas
|
||||
|
||||
COPY /api_services/api_builds/initial-service /initial-service
|
||||
COPY /api_services/api_builds/initial-service /
|
||||
|
||||
# Set Python path to include app directory
|
||||
ENV PYTHONPATH=/ PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1
|
||||
|
||||
# Run the application using the configured uvicorn server
|
||||
CMD ["poetry", "run", "python", "initial-service/app.py"]
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
# A generic, single database configuration.
|
||||
|
||||
[alembic]
|
||||
# path to migration scripts
|
||||
# Use forward slashes (/) also on windows to provide an os agnostic path
|
||||
script_location = alembic
|
||||
|
||||
# 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 and tzdata 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 alembic/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:alembic/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 = newline
|
||||
#
|
||||
# Use os.pathsep. Default configuration used for new projects.
|
||||
version_path_separator = os
|
||||
|
||||
# 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 = postgresql+psycopg2://postgres:password@10.10.2.14:5432/postgres
|
||||
|
||||
|
||||
[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 = check --fix REVISION_SCRIPT_FILENAME
|
||||
|
||||
# Logging configuration
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
|
||||
[formatters]
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = WARNING
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = WARNING
|
||||
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
|
||||
|
|
@ -0,0 +1 @@
|
|||
Generic single-database configuration.
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
import os
|
||||
from logging.config import fileConfig
|
||||
|
||||
from sqlalchemy import engine_from_config
|
||||
from sqlalchemy import pool
|
||||
from sqlalchemy import create_engine
|
||||
|
||||
from alembic import context
|
||||
from schemas import *
|
||||
from api_controllers.postgres.engine import Base
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
config = context.config
|
||||
|
||||
# Override sqlalchemy.url with environment variables if they exist
|
||||
db_host = os.getenv("POSTGRES_HOST", None)
|
||||
db_port = os.getenv("POSTGRES_PORT", None)
|
||||
db_user = os.getenv("POSTGRES_USER", None)
|
||||
db_password = os.getenv("POSTGRES_PASSWORD", None)
|
||||
db_name = os.getenv("POSTGRES_DB", None)
|
||||
|
||||
# Build the connection URL from environment variables
|
||||
db_url = f"postgresql+psycopg2://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}"
|
||||
|
||||
# Override the sqlalchemy.url in the alembic.ini file
|
||||
config.set_main_option("sqlalchemy.url", db_url)
|
||||
|
||||
# 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)
|
||||
|
||||
target_metadata = Base.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,
|
||||
)
|
||||
|
||||
with connectable.connect() as connection:
|
||||
context.configure(connection=connection, target_metadata=target_metadata)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
run_migrations_online()
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
"""${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
|
||||
${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:
|
||||
"""Upgrade schema."""
|
||||
${upgrades if upgrades else "pass"}
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
${downgrades if downgrades else "pass"}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import os
|
||||
|
||||
from api_controllers.postgres.engine import get_db
|
||||
from init_app_defaults import create_application_defaults
|
||||
from init_enums import init_api_enums_build_types
|
||||
from init_alembic import generate_alembic
|
||||
from init_occupant_types import create_occupant_types_defaults
|
||||
from init_services import create_modules_and_services_and_actions
|
||||
from init_address import create_one_address
|
||||
from init_occ_defaults import create_occupant_defaults
|
||||
|
||||
set_alembic = bool(int(os.getenv("SET_ALEMBIC"), 0))
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(f"Set alembic: {set_alembic}")
|
||||
|
||||
with get_db() as db_session:
|
||||
if set_alembic:
|
||||
generate_alembic(session=db_session)
|
||||
try:
|
||||
create_one_address(db_session=db_session)
|
||||
except Exception as e:
|
||||
print(f"Error creating address: {e}")
|
||||
try:
|
||||
init_api_enums_build_types(db_session=db_session)
|
||||
except Exception as e:
|
||||
print(f"Error creating enums: {e}")
|
||||
try:
|
||||
create_application_defaults(db_session=db_session)
|
||||
except Exception as e:
|
||||
print(f"Error creating application defaults: {e}")
|
||||
try:
|
||||
create_occupant_types_defaults(db_session=db_session)
|
||||
except Exception as e:
|
||||
print(f"Error creating occupant types defaults: {e}")
|
||||
try:
|
||||
create_modules_and_services_and_actions(db_session=db_session)
|
||||
except Exception as e:
|
||||
print(f"Error creating modules and services and actions: {e}")
|
||||
try:
|
||||
create_occupant_defaults(db_session=db_session)
|
||||
except Exception as e:
|
||||
print(f"Error creating occupant defaults: {e}")
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
from schemas import (
|
||||
Addresses,
|
||||
AddressCity,
|
||||
AddressStreet,
|
||||
AddressLocality,
|
||||
AddressDistrict,
|
||||
AddressNeighborhood,
|
||||
AddressState,
|
||||
AddressCountry,
|
||||
)
|
||||
|
||||
|
||||
def create_one_address(db_session):
|
||||
|
||||
address_list = []
|
||||
AddressCountry.set_session(db_session)
|
||||
country = AddressCountry.query.filter_by(country_name="TÜRKİYE", country_code="TR").first()
|
||||
if not country:
|
||||
country = AddressCountry.create(
|
||||
country_name="TÜRKİYE", country_code="TR", is_confirmed=True
|
||||
)
|
||||
country.save()
|
||||
address_list.append(country)
|
||||
else:
|
||||
print(f"Country already exists {country.to_dict()}")
|
||||
|
||||
AddressState.set_session(db_session)
|
||||
state = AddressState.query.filter_by(state_name="TÜRKİYE", state_code="TR").first()
|
||||
if not state:
|
||||
state = AddressState.create(
|
||||
state_name="TÜRKİYE",
|
||||
state_code="TR",
|
||||
phone_code="90",
|
||||
country_id=country.id,
|
||||
country_uu_id=str(country.uu_id),
|
||||
is_confirmed=True,
|
||||
)
|
||||
state.save()
|
||||
address_list.append(state)
|
||||
else:
|
||||
print(f"State already exists {state.to_dict()}")
|
||||
|
||||
AddressCity.set_session(db_session)
|
||||
city = AddressCity.query.filter_by(city_name="ANKARA", city_code="6").first()
|
||||
if not city:
|
||||
city = AddressCity.create(
|
||||
city_name="ANKARA",
|
||||
city_code="6",
|
||||
licence_plate="06",
|
||||
state_id=state.id,
|
||||
state_uu_id=str(state.uu_id),
|
||||
is_confirmed=True,
|
||||
)
|
||||
city.save()
|
||||
address_list.append(city)
|
||||
else:
|
||||
print(f"City already exists {city.to_dict()}")
|
||||
|
||||
AddressDistrict.set_session(db_session)
|
||||
district = AddressDistrict.query.filter_by(district_name="ÇANKAYA", district_code="1231").first()
|
||||
if not district:
|
||||
district = AddressDistrict.create(
|
||||
district_name="ÇANKAYA",
|
||||
district_code="1231",
|
||||
city_id=city.id,
|
||||
city_uu_id=str(city.uu_id),
|
||||
is_confirmed=True,
|
||||
)
|
||||
district.save()
|
||||
address_list.append(district)
|
||||
else:
|
||||
print(f"District already exists {district.to_dict()}")
|
||||
|
||||
AddressLocality.set_session(db_session)
|
||||
locality = AddressLocality.query.filter_by(locality_name="MERKEZ", locality_code="2431").first()
|
||||
if not locality:
|
||||
locality = AddressLocality.create(
|
||||
locality_name="MERKEZ",
|
||||
locality_code="2431",
|
||||
type_code="3",
|
||||
type_description=None,
|
||||
district_id=district.id,
|
||||
district_uu_id=str(district.uu_id),
|
||||
is_confirmed=True,
|
||||
)
|
||||
locality.save()
|
||||
address_list.append(locality)
|
||||
else:
|
||||
print(f"Locality already exists {locality.to_dict()}")
|
||||
|
||||
AddressNeighborhood.set_session(db_session)
|
||||
neighborhood = AddressNeighborhood.query.filter_by(neighborhood_name="AYRANCI MAHALLESİ", neighborhood_code="1522").first()
|
||||
if not neighborhood:
|
||||
neighborhood = AddressNeighborhood.create(
|
||||
neighborhood_name="AYRANCI MAHALLESİ",
|
||||
neighborhood_code="1522",
|
||||
type_code="1",
|
||||
type_description="MAHALLESİ",
|
||||
locality_id=locality.id,
|
||||
locality_uu_id=str(locality.uu_id),
|
||||
is_confirmed=True,
|
||||
)
|
||||
neighborhood.save()
|
||||
address_list.append(neighborhood)
|
||||
else:
|
||||
print(f"Neighborhood already exists {neighborhood.to_dict()}")
|
||||
|
||||
AddressStreet.set_session(db_session)
|
||||
street = AddressStreet.query.filter_by(street_name="REŞAT NURİ CADDESİ", street_code="52270").first()
|
||||
if not street:
|
||||
street = AddressStreet.create(
|
||||
street_name="REŞAT NURİ CADDESİ",
|
||||
type_description="CADDESİ",
|
||||
type_code="3",
|
||||
street_code="52270",
|
||||
neighborhood_id=neighborhood.id,
|
||||
neighborhood_uu_id=str(neighborhood.uu_id),
|
||||
is_confirmed=True,
|
||||
)
|
||||
street.save()
|
||||
address_list.append(street)
|
||||
else:
|
||||
print(f"Street already exists {street.to_dict()}")
|
||||
|
||||
Addresses.set_session(db_session)
|
||||
address = Addresses.query.filter_by(street_id=street.id, street_uu_id=str(street.uu_id)).first()
|
||||
if not address:
|
||||
address = Addresses.create(
|
||||
street_id=street.id,
|
||||
street_uu_id=str(street.uu_id),
|
||||
build_number="Ex1",
|
||||
door_number="1",
|
||||
floor_number="1",
|
||||
comment_address="Example Address",
|
||||
letter_address="Example Address",
|
||||
short_letter_address="Example Address",
|
||||
latitude=0,
|
||||
longitude=0,
|
||||
is_confirmed=True,
|
||||
)
|
||||
address.save()
|
||||
address_list.append(address)
|
||||
else:
|
||||
print(f"Address already exists {address.to_dict()}")
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import os
|
||||
from sqlalchemy import text
|
||||
|
||||
def generate_alembic(session):
|
||||
|
||||
try:
|
||||
result = session.execute(
|
||||
text(
|
||||
"SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = "
|
||||
"'alembic_version') AS table_existence;"
|
||||
)
|
||||
)
|
||||
if result.first()[0]:
|
||||
session.execute(text("delete from alembic_version;"))
|
||||
session.commit()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
finally:
|
||||
run_command = "python -m alembic stamp head;"
|
||||
run_command += (
|
||||
"python -m alembic revision --autogenerate;python -m alembic upgrade head;"
|
||||
)
|
||||
os.system(run_command)
|
||||
|
|
@ -0,0 +1,660 @@
|
|||
import arrow
|
||||
|
||||
from modules.Token.password_module import PasswordModule
|
||||
from api_controllers.mongo.database import mongo_handler
|
||||
from schemas import (
|
||||
Companies,
|
||||
Departments,
|
||||
Duty,
|
||||
Duties,
|
||||
Employees,
|
||||
People,
|
||||
Users,
|
||||
Staff,
|
||||
RelationshipDutyCompany,
|
||||
)
|
||||
|
||||
def create_application_defaults(db_session):
|
||||
created_list, created_by, confirmed_by = [], "System", "System"
|
||||
active_row = dict(is_confirmed=True, active=True, deleted=False, is_notification_send=True)
|
||||
Companies.set_session(db_session)
|
||||
Departments.set_session(db_session)
|
||||
Duties.set_session(db_session)
|
||||
Duty.set_session(db_session)
|
||||
Staff.set_session(db_session)
|
||||
People.set_session(db_session)
|
||||
Users.set_session(db_session)
|
||||
Employees.set_session(db_session)
|
||||
RelationshipDutyCompany.set_session(db_session)
|
||||
|
||||
company_management = Companies.query.filter_by(company_tag="Evyos",).first()
|
||||
if not company_management:
|
||||
company_management = Companies.find_or_create(
|
||||
**{
|
||||
"formal_name": "Evyos LTD",
|
||||
"public_name": "Evyos Verimlilik Sistemleri",
|
||||
"company_type": "LTD",
|
||||
"commercial_type": "Commercial",
|
||||
"tax_no": "123132123132",
|
||||
"company_tag": "Evyos",
|
||||
"default_lang_type": "TR",
|
||||
"default_money_type": "TL",
|
||||
"is_commercial": True,
|
||||
"is_confirmed": True,
|
||||
}
|
||||
)
|
||||
created_list.append(company_management)
|
||||
else:
|
||||
print(f"Company Management Found {company_management.to_dict()}")
|
||||
company_id, company_uu_id = company_management.id, str(company_management.uu_id)
|
||||
|
||||
execution = Departments.query.filter_by(department_code="EO001", company_id=company_id).first()
|
||||
if not execution:
|
||||
execution = Departments.create(
|
||||
department_name="Execution Office",
|
||||
department_code="EO001",
|
||||
company_id=company_id,
|
||||
company_uu_id=str(company_uu_id),
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(execution)
|
||||
else:
|
||||
print(f"Execution Found {execution.to_dict()}")
|
||||
|
||||
gen_man = Departments.query.filter_by(department_code="GM001", company_id=company_id).first()
|
||||
if not gen_man:
|
||||
gen_man = Departments.create(
|
||||
department_name="General Manager Example",
|
||||
department_code="GM001",
|
||||
company_id=company_id,
|
||||
company_uu_id=str(company_uu_id),
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(gen_man)
|
||||
else:
|
||||
print(f"General Manager Found {gen_man.to_dict()}")
|
||||
|
||||
it_dept = Departments.query.filter_by(department_code="ITD001", company_id=company_id).first()
|
||||
if not it_dept:
|
||||
it_dept = Departments.create(
|
||||
department_name="IT Department",
|
||||
department_code="ITD001",
|
||||
company_id=company_id,
|
||||
company_uu_id=str(company_uu_id),
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(it_dept)
|
||||
else:
|
||||
print(f"IT Department Found {it_dept.to_dict()}")
|
||||
|
||||
gen_duty = Duty.query.filter_by(duty_code="GM0001").first()
|
||||
if not gen_duty:
|
||||
gen_duty = Duty.create(
|
||||
duty_name="General Manager",
|
||||
duty_code="GM0001",
|
||||
duty_description="General Manager",
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(gen_duty)
|
||||
else:
|
||||
print(f"General Manager Found {gen_duty.to_dict()}")
|
||||
|
||||
bm_duty = Duty.query.filter_by(duty_code="BM0001").first()
|
||||
if not bm_duty:
|
||||
bm_duty = Duty.create(
|
||||
duty_name="Business Manager",
|
||||
duty_code="BM0001",
|
||||
duty_description="Business Manager",
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(bm_duty)
|
||||
else:
|
||||
print(f"Business Manager Found {bm_duty.to_dict()}")
|
||||
it_duty = Duty.query.filter_by(duty_code="IT0001").first()
|
||||
if not it_duty:
|
||||
it_duty = Duty.create(
|
||||
duty_name="IT Manager",
|
||||
duty_code="IT0001",
|
||||
duty_description="IT Manager",
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(it_duty)
|
||||
else:
|
||||
print(f"IT Manager Found {it_duty.to_dict()}")
|
||||
bulk_duty = Duty.query.filter_by(duty_code="BULK").first()
|
||||
if not bulk_duty:
|
||||
bulk_duty = Duty.create(
|
||||
duty_name="BULK",
|
||||
duty_code="BULK",
|
||||
duty_description="BULK RECORDS OF THE COMPANY",
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(bulk_duty)
|
||||
else:
|
||||
print(f"Bulk Duty Found {bulk_duty.to_dict()}")
|
||||
occu_duty = Duty.query.filter_by(duty_code="OCCUPANT").first()
|
||||
if not occu_duty:
|
||||
occu_duty = Duty.create(
|
||||
duty_name="OCCUPANT",
|
||||
duty_code="OCCUPANT",
|
||||
duty_description="OCCUPANT RECORDS OF THE COMPANY",
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(occu_duty)
|
||||
else:
|
||||
print(f"Occupant Duty Found {occu_duty.to_dict()}")
|
||||
|
||||
duties_gen_man = Duties.query.filter_by(company_id=company_id, duties_id=gen_duty.id, department_id=gen_man.id).first()
|
||||
if not duties_gen_man:
|
||||
duties_gen_man = Duties.create(
|
||||
company_id=company_id,
|
||||
company_uu_id=str(company_uu_id),
|
||||
duties_id=gen_duty.id,
|
||||
duties_uu_id=str(gen_duty.uu_id),
|
||||
department_id=gen_man.id,
|
||||
department_uu_id=str(gen_man.uu_id),
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(duties_gen_man)
|
||||
else:
|
||||
print(f"Duties General Manager Found {duties_gen_man.to_dict()}")
|
||||
|
||||
duties_created_bm = Duties.query.filter_by(company_id=company_id, duties_id=bm_duty.id, department_id=execution.id).first()
|
||||
if not duties_created_bm:
|
||||
duties_created_bm = Duties.create(
|
||||
company_id=company_id,
|
||||
company_uu_id=str(company_uu_id),
|
||||
duties_id=bm_duty.id,
|
||||
duties_uu_id=str(bm_duty.uu_id),
|
||||
department_id=execution.id,
|
||||
department_uu_id=str(execution.uu_id),
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(duties_created_bm)
|
||||
else:
|
||||
print(f"Duties Business Manager Found {duties_created_bm.to_dict()}")
|
||||
duties_created_it = Duties.query.filter_by(company_id=company_id, duties_id=bulk_duty.id, department_id=execution.id).first()
|
||||
if not duties_created_it:
|
||||
duties_created_it = Duties.create(
|
||||
company_id=company_id,
|
||||
company_uu_id=str(company_uu_id),
|
||||
duties_id=bulk_duty.id,
|
||||
duties_uu_id=str(bulk_duty.uu_id),
|
||||
department_id=execution.id,
|
||||
department_uu_id=str(execution.uu_id),
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(duties_created_it)
|
||||
else:
|
||||
print(f"Duties Bulk Found {duties_created_it.to_dict()}")
|
||||
duties_created_occupant = Duties.query.filter_by(company_id=company_id, duties_id=occu_duty.id, department_id=execution.id).first()
|
||||
if not duties_created_occupant:
|
||||
duties_created_occupant = Duties.create(
|
||||
company_id=company_id,
|
||||
company_uu_id=str(company_uu_id),
|
||||
duties_id=occu_duty.id,
|
||||
duties_uu_id=str(occu_duty.uu_id),
|
||||
department_id=execution.id,
|
||||
department_uu_id=str(execution.uu_id),
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(duties_created_occupant)
|
||||
else:
|
||||
print(f"Duties Occupant Found {duties_created_occupant.to_dict()}")
|
||||
bulk_duty = Duty.query.filter_by(duty_code="BULK").first()
|
||||
if not bulk_duty:
|
||||
bulk_duty = Duty.create(
|
||||
duty_name="BULK",
|
||||
duty_code="BULK",
|
||||
duty_description="BULK RECORDS OF THE COMPANY",
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(bulk_duty)
|
||||
else:
|
||||
print(f"Bulk Duty Found {bulk_duty.to_dict()}")
|
||||
it_dept = Departments.query.filter_by(department_code="ITD001", company_id=company_id).first()
|
||||
if not it_dept:
|
||||
it_dept = Departments.create(
|
||||
department_name="IT Department",
|
||||
department_code="ITD001",
|
||||
company_id=company_id,
|
||||
company_uu_id=str(company_uu_id),
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(it_dept)
|
||||
else:
|
||||
print(f"IT Department Found {it_dept.to_dict()}")
|
||||
created_duty = Duty.query.filter_by(duty_code="DM").first()
|
||||
if not created_duty:
|
||||
created_duty = Duty.create(
|
||||
duty_name="Database Manager",
|
||||
duty_code="DM",
|
||||
duty_description="Database Manager",
|
||||
created_by=created_by,
|
||||
confirmed_by=confirmed_by,
|
||||
is_confirmed=True,
|
||||
active=True,
|
||||
deleted=False,
|
||||
is_notification_send=True,
|
||||
)
|
||||
created_list.append(created_duty)
|
||||
created_duty = Duty.query.filter_by(duty_code="NM").first()
|
||||
if not created_duty:
|
||||
created_duty = Duty.create(
|
||||
duty_name="Network Manager",
|
||||
duty_code="NM",
|
||||
duty_description="Network Manager",
|
||||
created_by=created_by,
|
||||
confirmed_by=confirmed_by,
|
||||
is_confirmed=True,
|
||||
active=True,
|
||||
deleted=False,
|
||||
is_notification_send=True,
|
||||
)
|
||||
created_list.append(created_duty)
|
||||
application_manager_duty = Duty.query.filter_by(duty_code="AM").first()
|
||||
if not application_manager_duty:
|
||||
application_manager_duty = Duty.create(
|
||||
duty_name="Application Manager",
|
||||
duty_code="AM",
|
||||
duty_description="Application Manager",
|
||||
created_by=created_by,
|
||||
confirmed_by=confirmed_by,
|
||||
is_confirmed=True,
|
||||
active=True,
|
||||
deleted=False,
|
||||
is_notification_send=True,
|
||||
)
|
||||
created_list.append(application_manager_duty)
|
||||
application_super_user_duty = Duty.query.filter_by(duty_code="SUE").first()
|
||||
if not application_super_user_duty:
|
||||
application_super_user_duty = Duty.create(
|
||||
duty_name="Super User",
|
||||
duty_code="SUE",
|
||||
duty_description="Super User",
|
||||
created_by=created_by,
|
||||
confirmed_by=confirmed_by,
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(application_super_user_duty)
|
||||
|
||||
application_manager_duties = Duties.query.filter_by(
|
||||
department_id=it_dept.id,
|
||||
duties_id=application_manager_duty.id,
|
||||
company_id=company_id,
|
||||
).first()
|
||||
if not application_manager_duties:
|
||||
application_manager_duties = Duties.create(
|
||||
department_id=it_dept.id,
|
||||
department_uu_id=str(it_dept.uu_id),
|
||||
duties_id=application_manager_duty.id,
|
||||
duties_uu_id=str(application_manager_duty.uu_id),
|
||||
company_id=company_id,
|
||||
company_uu_id=str(company_uu_id),
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(application_manager_duties)
|
||||
else:
|
||||
print(f"Application Manager Duties Found {application_manager_duties.to_dict()}")
|
||||
|
||||
|
||||
super_user_duties = Duties.query.filter_by(
|
||||
department_id=it_dept.id,
|
||||
duties_id=application_super_user_duty.id,
|
||||
company_id=company_id,
|
||||
).first()
|
||||
if not super_user_duties:
|
||||
super_user_duties = Duties.create(
|
||||
department_id=it_dept.id,
|
||||
department_uu_id=str(it_dept.uu_id),
|
||||
duties_id=application_super_user_duty.id,
|
||||
duties_uu_id=str(application_manager_duty.uu_id),
|
||||
company_id=company_id,
|
||||
company_uu_id=str(company_uu_id),
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(super_user_duties)
|
||||
else:
|
||||
print(f"Super User Duties Found {super_user_duties.to_dict()}")
|
||||
relation_super_user_duties = RelationshipDutyCompany.query.filter_by(
|
||||
duties_id=super_user_duties.id,
|
||||
owner_id=company_id,
|
||||
member_id=company_id,
|
||||
).first()
|
||||
if not relation_super_user_duties:
|
||||
relation_super_user_duties = RelationshipDutyCompany.create(
|
||||
duties_id=super_user_duties.id,
|
||||
owner_id=company_id,
|
||||
member_id=company_id,
|
||||
parent_id=None,
|
||||
child_count=0,
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(super_user_duties)
|
||||
|
||||
relation_application_manager_duties = RelationshipDutyCompany.query.filter_by(
|
||||
duties_id=application_manager_duties.id,
|
||||
owner_id=company_id,
|
||||
member_id=company_id,
|
||||
).first()
|
||||
if not relation_application_manager_duties:
|
||||
relation_application_manager_duties = RelationshipDutyCompany.create(
|
||||
duties_id=application_manager_duties.id,
|
||||
owner_id=company_id,
|
||||
member_id=company_id,
|
||||
parent_id=None,
|
||||
child_count=0,
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(relation_application_manager_duties)
|
||||
|
||||
app_manager = People.query.filter_by(
|
||||
person_tag="BAM-System",
|
||||
).first()
|
||||
if not app_manager:
|
||||
app_manager = People.create(
|
||||
**{
|
||||
"person_tag": "BAM-System",
|
||||
"firstname": "Berkay Application Manager",
|
||||
"surname": "Karatay",
|
||||
"sex_code": "M",
|
||||
"middle_name": "",
|
||||
"father_name": "Father",
|
||||
"mother_name": "Mother",
|
||||
"country_code": "TR",
|
||||
"national_identity_id": "12312312312",
|
||||
"birth_place": "Ankara",
|
||||
"birth_date": "01.07.1990",
|
||||
"tax_no": "1231231231",
|
||||
**active_row,
|
||||
},
|
||||
)
|
||||
created_list.append(app_manager)
|
||||
else:
|
||||
print(f"Application Manager Found {app_manager.to_dict()}")
|
||||
|
||||
sup_manager = People.query.filter_by(person_tag="BSU-System").first()
|
||||
if not sup_manager:
|
||||
sup_manager = People.create(
|
||||
**{
|
||||
"person_tag": "BSU-System",
|
||||
"firstname": "Berkay Super User",
|
||||
"surname": "Karatay",
|
||||
"sex_code": "M",
|
||||
"middle_name": "",
|
||||
"father_name": "Father",
|
||||
"mother_name": "Mother",
|
||||
"country_code": "TR",
|
||||
"national_identity_id": "12312312313",
|
||||
"birth_place": "Ankara",
|
||||
"birth_date": "01.07.1990",
|
||||
"tax_no": "1231231232",
|
||||
**active_row,
|
||||
},
|
||||
)
|
||||
created_list.append(sup_manager)
|
||||
else:
|
||||
print(f"Super User Found {sup_manager.to_dict()}")
|
||||
|
||||
gen_manager_people = People.query.filter_by(person_tag="BM-System").first()
|
||||
if not gen_manager_people:
|
||||
gen_manager_people = People.create(
|
||||
**{
|
||||
"person_tag": "BM-System",
|
||||
"firstname": "Example General Manager",
|
||||
"surname": "Example",
|
||||
"sex_code": "M",
|
||||
"middle_name": "",
|
||||
"father_name": "Father",
|
||||
"mother_name": "Mother",
|
||||
"country_code": "TR",
|
||||
"national_identity_id": "12312312314",
|
||||
"birth_place": "Ankara",
|
||||
"birth_date": "01.07.1990",
|
||||
"tax_no": "1231231233",
|
||||
**active_row,
|
||||
},
|
||||
)
|
||||
created_list.append(gen_manager_people)
|
||||
else:
|
||||
print(f"General Manager Found {gen_manager_people.to_dict()}")
|
||||
|
||||
application_manager_staff = Staff.query.filter_by(staff_code="AME", duties_id=application_manager_duties.id).first()
|
||||
if not application_manager_staff:
|
||||
application_manager_staff = Staff.create(
|
||||
**{
|
||||
"staff_code": "AME",
|
||||
"staff_name": "Application Manager Employee",
|
||||
"staff_description": "Application Manager Employee",
|
||||
"duties_id": application_manager_duties.id,
|
||||
"duties_uu_id": str(application_manager_duty.uu_id),
|
||||
**active_row,
|
||||
},
|
||||
)
|
||||
created_list.append(application_manager_staff)
|
||||
else:
|
||||
print(f"Application Manager Found {application_manager_staff.to_dict()}")
|
||||
|
||||
super_user_staff = Staff.query.filter_by(staff_code="SUE", duties_id=super_user_duties.id).first()
|
||||
if not super_user_staff:
|
||||
super_user_staff = Staff.create(
|
||||
**{
|
||||
"staff_code": "SUE",
|
||||
"staff_name": "Super User Employee",
|
||||
"staff_description": "Super User Employee",
|
||||
"duties_id": super_user_duties.id,
|
||||
"duties_uu_id": str(super_user_duties.uu_id),
|
||||
**active_row,
|
||||
},
|
||||
)
|
||||
created_list.append(super_user_staff)
|
||||
else:
|
||||
print(f"Super User Found {super_user_staff.to_dict()}")
|
||||
|
||||
gen_manager_staff = Staff.query.filter_by(staff_code="GME", duties_id=duties_gen_man.id).first()
|
||||
if not gen_manager_staff:
|
||||
gen_manager_staff = Staff.create(
|
||||
**{
|
||||
"staff_code": "GME",
|
||||
"staff_name": "General Manager Employee",
|
||||
"staff_description": "General Manager Employee",
|
||||
"duties_id": duties_gen_man.id,
|
||||
"duties_uu_id": str(duties_gen_man.uu_id),
|
||||
**active_row,
|
||||
},
|
||||
)
|
||||
created_list.append(gen_manager_staff)
|
||||
else:
|
||||
print(f"General Manager Found {gen_manager_staff.to_dict()}")
|
||||
|
||||
application_manager_staff = Staff.query.filter_by(staff_code="AME", duties_id=application_manager_duty.id).first()
|
||||
if not application_manager_staff:
|
||||
application_manager_staff = Staff.create(
|
||||
**{
|
||||
"staff_code": "AME",
|
||||
"staff_name": "Application Manager Employee",
|
||||
"staff_description": "Application Manager Employee",
|
||||
"duties_id": application_manager_duty.id,
|
||||
"duties_uu_id": str(application_manager_duty.uu_id),
|
||||
**active_row,
|
||||
},
|
||||
)
|
||||
created_list.append(application_manager_staff)
|
||||
gen_man_staff = Staff.query.filter_by(staff_code="GME", duties_id=duties_gen_man.id).first()
|
||||
if not gen_man_staff:
|
||||
gen_man_staff = Staff.create(
|
||||
**{
|
||||
"staff_code": "GME",
|
||||
"staff_name": "General Manager Employee",
|
||||
"staff_description": "General Manager Employee",
|
||||
"duties_id": duties_gen_man.id,
|
||||
"duties_uu_id": str(gen_duty.uu_id),
|
||||
**active_row,
|
||||
},
|
||||
)
|
||||
created_list.append(gen_man_staff)
|
||||
|
||||
gen_man_employee = Employees.query.filter_by(staff_id=gen_man_staff.id, people_id=gen_manager_people.id).first()
|
||||
if not gen_man_employee:
|
||||
gen_man_employee = Employees.create(
|
||||
staff_id=gen_man_staff.id,
|
||||
staff_uu_id=str(gen_man_staff.uu_id),
|
||||
people_id=gen_manager_people.id,
|
||||
people_uu_id=str(gen_manager_people.uu_id),
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(gen_man_employee)
|
||||
|
||||
app_manager_employee = Employees.query.filter_by(staff_id=application_manager_staff.id, people_id=app_manager.id).first()
|
||||
if not app_manager_employee:
|
||||
app_manager_employee = Employees.create(
|
||||
staff_id=application_manager_staff.id,
|
||||
staff_uu_id=str(application_manager_staff.uu_id),
|
||||
people_id=app_manager.id,
|
||||
people_uu_id=str(app_manager.uu_id),
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(app_manager_employee)
|
||||
|
||||
super_user_employee = Employees.query.filter_by(staff_id=super_user_staff.id, people_id=sup_manager.id).first()
|
||||
if not super_user_employee:
|
||||
super_user_employee = Employees.create(
|
||||
staff_id=super_user_staff.id,
|
||||
staff_uu_id=str(super_user_staff.uu_id),
|
||||
people_id=sup_manager.id,
|
||||
people_uu_id=str(sup_manager.uu_id),
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(super_user_employee)
|
||||
|
||||
gen_manager_user = Users.query.filter_by(person_id=gen_manager_people.id, user_tag=gen_manager_people.person_tag).first()
|
||||
if not gen_manager_user:
|
||||
gen_manager_user = Users.create(
|
||||
person_id=gen_manager_people.id,
|
||||
person_uu_id=str(gen_manager_people.uu_id),
|
||||
user_tag=gen_manager_people.person_tag,
|
||||
email="example.general@evyos.com.tr",
|
||||
phone_number="+901111111111",
|
||||
avatar="https://s.tmimgcdn.com/scr/800x500/276800/building-home-nature-logo-vector-template-3_276851-original.jpg",
|
||||
related_company=str(company_management.uu_id),
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(gen_manager_user)
|
||||
gen_manager_user.password_expiry_begins = str(arrow.now())
|
||||
gen_manager_user.password_token = PasswordModule.generate_refresher_token()
|
||||
main_domain, collection_name = (
|
||||
"evyos.com.tr",
|
||||
f"{str(company_management.uu_id)}*Domain",
|
||||
)
|
||||
with mongo_handler.collection(collection_name) as mongo_engine:
|
||||
existing_record = mongo_engine.find_one(
|
||||
{"user_uu_id": str(gen_manager_user.uu_id)}
|
||||
)
|
||||
if not existing_record:
|
||||
mongo_engine.insert_one(
|
||||
document={
|
||||
"user_uu_id": str(gen_manager_user.uu_id),
|
||||
"other_domains_list": [main_domain],
|
||||
"main_domain": main_domain,
|
||||
"modified_at": arrow.now().timestamp(),
|
||||
}
|
||||
)
|
||||
else:
|
||||
mongo_engine.update_one(
|
||||
{"user_uu_id": str(gen_manager_user.uu_id)},
|
||||
{
|
||||
"$set": {
|
||||
"other_domains_list": [main_domain],
|
||||
"main_domain": main_domain,
|
||||
"modified_at": arrow.now().timestamp(),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
app_manager_user = Users.query.filter_by(person_id=app_manager.id, user_tag=app_manager.person_tag).first()
|
||||
if not app_manager_user:
|
||||
app_manager_user = Users.create(
|
||||
person_id=app_manager.id,
|
||||
user_tag=app_manager.person_tag,
|
||||
email="karatay.berkay.man@evyos.com.tr",
|
||||
phone_number="+901111111111",
|
||||
avatar="https://s.tmimgcdn.com/scr/800x500/276800/building-home-nature-logo-vector-template-3_276851-original.jpg",
|
||||
related_company=str(company_management.uu_id),
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(app_manager_user)
|
||||
app_manager_user.password_expiry_begins = str(arrow.now())
|
||||
app_manager_user.password_token = PasswordModule.generate_refresher_token()
|
||||
|
||||
with mongo_handler.collection(collection_name) as mongo_engine:
|
||||
existing_record = mongo_engine.find_one(
|
||||
{"user_uu_id": str(app_manager_user.uu_id)}
|
||||
)
|
||||
if not existing_record:
|
||||
mongo_engine.insert_one(
|
||||
document={
|
||||
"user_uu_id": str(app_manager_user.uu_id),
|
||||
"other_domains_list": [main_domain],
|
||||
"main_domain": main_domain,
|
||||
"modified_at": arrow.now().timestamp(),
|
||||
}
|
||||
)
|
||||
else:
|
||||
mongo_engine.update_one(
|
||||
{"user_uu_id": str(app_manager_user.uu_id)},
|
||||
{
|
||||
"$set": {
|
||||
"other_domains_list": [main_domain],
|
||||
"main_domain": main_domain,
|
||||
"modified_at": arrow.now().timestamp(),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
sup_manager_user = Users.query.filter_by(person_id=sup_manager.id, user_tag=sup_manager.person_tag).first()
|
||||
if not sup_manager_user:
|
||||
sup_manager_user = Users.create(
|
||||
person_id=sup_manager.id,
|
||||
user_tag=sup_manager.person_tag,
|
||||
email="karatay.berkay.sup@evyos.com.tr",
|
||||
phone_number="+901111111112",
|
||||
avatar="https://s.tmimgcdn.com/scr/800x500/276800/building-home-nature-logo-vector-template-3_276851-original.jpg",
|
||||
created_by=created_by,
|
||||
confirmed_by=confirmed_by,
|
||||
related_company=str(company_management.uu_id),
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(sup_manager_user)
|
||||
|
||||
sup_manager_user.password_expiry_begins = str(arrow.now())
|
||||
sup_manager_user.password_token = PasswordModule.generate_refresher_token()
|
||||
with mongo_handler.collection(collection_name) as mongo_engine:
|
||||
existing_record = mongo_engine.find_one(
|
||||
{"user_uu_id": str(sup_manager_employee.uu_id)}
|
||||
)
|
||||
if not existing_record:
|
||||
print("insert sup existing record", existing_record)
|
||||
mongo_engine.insert_one(
|
||||
document={
|
||||
"user_uu_id": str(sup_manager_employee.uu_id),
|
||||
"other_domains_list": [main_domain, "management.com.tr"],
|
||||
"main_domain": main_domain,
|
||||
"modified_at": arrow.now().timestamp(),
|
||||
}
|
||||
)
|
||||
else:
|
||||
print("update sup existing record", existing_record)
|
||||
# Optionally update the existing record if needed
|
||||
mongo_engine.update_one(
|
||||
{"user_uu_id": str(sup_manager_employee.uu_id)},
|
||||
{
|
||||
"$set": {
|
||||
"other_domains_list": [main_domain, "management.com.tr"],
|
||||
"main_domain": main_domain,
|
||||
"modified_at": arrow.now().timestamp(),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
db_session.commit()
|
||||
print("All Defaults Create is now completed")
|
||||
|
|
@ -0,0 +1,266 @@
|
|||
from pydantic import BaseModel
|
||||
from schemas import BuildTypes, ApiEnumDropdown
|
||||
|
||||
|
||||
class InsertBuildTypes(BaseModel):
|
||||
function_code: str
|
||||
type_code: str
|
||||
lang: str
|
||||
type_name: str
|
||||
|
||||
|
||||
def init_api_enums_build_types(db_session):
|
||||
|
||||
BuildTypes.set_session(db_session)
|
||||
ApiEnumDropdown.set_session(db_session)
|
||||
|
||||
insert_types = [
|
||||
{
|
||||
"function_code": "EVYOS",
|
||||
"type_code": "APT_KZN",
|
||||
"type_name": "Apartman Kazan Dairesi",
|
||||
"lang": "TR",
|
||||
},
|
||||
{
|
||||
"function_code": "EVYOS",
|
||||
"type_code": "APT_GRJ",
|
||||
"type_name": "Apartman Garaj",
|
||||
"lang": "TR",
|
||||
},
|
||||
{
|
||||
"function_code": "EVYOS",
|
||||
"type_code": "APT_DP",
|
||||
"type_name": "Apartman Depo",
|
||||
"lang": "TR",
|
||||
},
|
||||
{
|
||||
"function_code": "EVYOS",
|
||||
"type_code": "DAIRE",
|
||||
"type_name": "Apartman Dairesi",
|
||||
"lang": "TR",
|
||||
},
|
||||
{
|
||||
"function_code": "EVYOS",
|
||||
"type_code": "APT",
|
||||
"type_name": "Apartman Binası",
|
||||
"lang": "TR",
|
||||
},
|
||||
{
|
||||
"function_code": "EVYOS",
|
||||
"type_code": "APT_YNT",
|
||||
"type_name": "Apartman Yönetimi",
|
||||
"lang": "TR",
|
||||
},
|
||||
{
|
||||
"function_code": "EVYOS",
|
||||
"type_code": "APT_PRK",
|
||||
"type_name": "Apartman Açık Park Alanı",
|
||||
"lang": "TR",
|
||||
},
|
||||
{
|
||||
"function_code": "EVYOS",
|
||||
"type_code": "APT_YSL",
|
||||
"type_name": "Apartman Yeşil Alan",
|
||||
"lang": "TR",
|
||||
},
|
||||
{
|
||||
"function_code": "EVYOS",
|
||||
"type_code": "APT_YOL",
|
||||
"type_name": "Apartman Ara Yol",
|
||||
"lang": "TR",
|
||||
},
|
||||
]
|
||||
|
||||
for insert_type in insert_types:
|
||||
build_types = InsertBuildTypes(
|
||||
function_code="EVYOS",
|
||||
lang=insert_type["lang"],
|
||||
type_code=str(insert_type["type_code"]).upper(),
|
||||
type_name=insert_type["type_name"],
|
||||
)
|
||||
created_build_type = BuildTypes.query.filter_by(
|
||||
function_code=build_types.function_code,
|
||||
type_code=build_types.type_code,
|
||||
).first()
|
||||
if not created_build_type:
|
||||
created_build_type = BuildTypes.find_or_create(
|
||||
**build_types.model_dump(), is_confirmed=True, db=db_session
|
||||
)
|
||||
created_build_type.save()
|
||||
|
||||
insert_enums = [
|
||||
{"enum_class": "BuildDuesTypes", "type_code": "BDT-D", "type_name": "Debit"},
|
||||
{
|
||||
"enum_class": "BuildDuesTypes",
|
||||
"type_code": "BDT-A",
|
||||
"type_name": "Add Debit",
|
||||
},
|
||||
{
|
||||
"enum_class": "BuildDuesTypes",
|
||||
"type_code": "BDT-R",
|
||||
"type_name": "Renovation",
|
||||
},
|
||||
{
|
||||
"enum_class": "BuildDuesTypes",
|
||||
"type_code": "BDT-L",
|
||||
"type_name": "Lawyer expence",
|
||||
},
|
||||
{
|
||||
"enum_class": "BuildDuesTypes",
|
||||
"type_code": "BDT-S",
|
||||
"type_name": "Service fee",
|
||||
},
|
||||
{
|
||||
"enum_class": "BuildDuesTypes",
|
||||
"type_code": "BDT-I",
|
||||
"type_name": "Information",
|
||||
},
|
||||
{
|
||||
"enum_class": "AccountingReceiptTypes",
|
||||
"type_code": "ART-A",
|
||||
"type_name": "Kasa Tahsil Fişi",
|
||||
},
|
||||
{
|
||||
"enum_class": "AccountingReceiptTypes",
|
||||
"type_code": "ART-E",
|
||||
"type_name": "Kasa Tediye Fişi",
|
||||
},
|
||||
{
|
||||
"enum_class": "AccountingReceiptTypes",
|
||||
"type_code": "ART-M",
|
||||
"type_name": "Mahsup Fişi",
|
||||
},
|
||||
{
|
||||
"enum_class": "AccountingReceiptTypes",
|
||||
"type_code": "ART-O",
|
||||
"type_name": "Açılış Fişi",
|
||||
},
|
||||
{
|
||||
"enum_class": "AccountingReceiptTypes",
|
||||
"type_code": "ART-C",
|
||||
"type_name": "Kapanış Fişi",
|
||||
},
|
||||
{"enum_class": "IbanBudgetType", "type_code": "IBT-I", "type_name": "Iban"},
|
||||
{"enum_class": "IbanBudgetType", "type_code": "IBT-B", "type_name": "Budget"},
|
||||
{
|
||||
"enum_class": "IbanBudgetType",
|
||||
"type_code": "IBT-TR",
|
||||
"type_name": "Transaction records",
|
||||
},
|
||||
{"enum_class": "ProjectTypes", "type_code": "R", "type_name": "Tadilat"},
|
||||
{
|
||||
"enum_class": "ProjectTypes",
|
||||
"type_code": "PT-C",
|
||||
"type_name": "Mahkeme süreçleri",
|
||||
},
|
||||
{
|
||||
"enum_class": "ProjectTypes",
|
||||
"type_code": "PT-Z",
|
||||
"type_name": "Sıfır Bakiye",
|
||||
},
|
||||
{
|
||||
"enum_class": "EdmBudgetType",
|
||||
"type_code": "PT-B",
|
||||
"type_name": "Banka records",
|
||||
},
|
||||
{
|
||||
"enum_class": "EdmBudgetType",
|
||||
"type_code": "PT-S",
|
||||
"type_name": "Sistem kaydı",
|
||||
},
|
||||
{
|
||||
"enum_class": "EdmBudgetType",
|
||||
"type_code": "EBT-C",
|
||||
"type_name": "Build, Flat or Site records",
|
||||
},
|
||||
{"enum_class": "ExpireType", "type_code": "1", "type_name": "daily"},
|
||||
{"enum_class": "ExpireType", "type_code": "7", "type_name": "weekly"},
|
||||
{"enum_class": "ExpireType", "type_code": "30", "type_name": "monthly"},
|
||||
{"enum_class": "ExpireType", "type_code": "90", "type_name": "quarter"},
|
||||
{"enum_class": "ExpireType", "type_code": "180", "type_name": "six_month"},
|
||||
{"enum_class": "ExpireType", "type_code": "365", "type_name": "yearly"},
|
||||
{"enum_class": "PhoneType", "type_code": "M", "type_name": "cep tel"},
|
||||
{"enum_class": "PhoneType", "type_code": "L", "type_name": "sabit telefon"},
|
||||
{"enum_class": "PhoneType", "type_code": "F", "type_name": "fax"},
|
||||
{"enum_class": "PhoneType", "type_code": "C", "type_name": "santral"},
|
||||
{
|
||||
"enum_class": "PhoneType",
|
||||
"type_code": "G",
|
||||
"type_name": "ülke genelindeki hatlar 444",
|
||||
},
|
||||
{"enum_class": "PerComType", "type_code": "1", "type_name": "Person"},
|
||||
{"enum_class": "PerComType", "type_code": "2", "type_name": "Company"},
|
||||
{"enum_class": "Directions", "type_code": "NN", "type_name": "North"},
|
||||
{"enum_class": "Directions", "type_code": "EE", "type_name": "East"},
|
||||
{"enum_class": "Directions", "type_code": "SS", "type_name": "South"},
|
||||
{"enum_class": "Directions", "type_code": "WW", "type_name": "West"},
|
||||
{"enum_class": "Directions", "type_code": "NE", "type_name": "North East"},
|
||||
{"enum_class": "Directions", "type_code": "NW", "type_name": "North West"},
|
||||
{"enum_class": "Directions", "type_code": "SE", "type_name": "South East"},
|
||||
{"enum_class": "Directions", "type_code": "SW", "type_name": "South West"},
|
||||
{
|
||||
"enum_class": "MeetingTypes",
|
||||
"type_code": "MT-RBM",
|
||||
"type_name": "Regular Building Meeting",
|
||||
},
|
||||
{
|
||||
"enum_class": "MeetingTypes",
|
||||
"type_code": "MT-DBM",
|
||||
"type_name": "Disaster Building Meeting",
|
||||
},
|
||||
{
|
||||
"enum_class": "MeetingTypes",
|
||||
"type_code": "MT-EBM",
|
||||
"type_name": "Emergency Building Meeting",
|
||||
},
|
||||
{
|
||||
"enum_class": "DebitTypes",
|
||||
"type_code": "DT-D",
|
||||
"type_name": "Debit Sender",
|
||||
},
|
||||
{
|
||||
"enum_class": "DebitTypes",
|
||||
"type_code": "DT-R",
|
||||
"type_name": "Credit Receiver",
|
||||
},
|
||||
{
|
||||
"enum_class": "DebitTypes",
|
||||
"type_code": "DT-Z",
|
||||
"type_name": "Zero Balance",
|
||||
},
|
||||
{
|
||||
"enum_class": "TimePeriod",
|
||||
"type_code": "TP-W",
|
||||
"type_name": "Weekly",
|
||||
},
|
||||
{
|
||||
"enum_class": "TimePeriod",
|
||||
"type_code": "TP-M",
|
||||
"type_name": "Monthly",
|
||||
},
|
||||
{
|
||||
"enum_class": "TimePeriod",
|
||||
"type_code": "TP-Q",
|
||||
"type_name": "Quarterly",
|
||||
},
|
||||
{
|
||||
"enum_class": "TimePeriod",
|
||||
"type_code": "TP-Y",
|
||||
"type_name": "Yearly",
|
||||
},
|
||||
]
|
||||
|
||||
for insert_enum in insert_enums:
|
||||
created_api_enum = ApiEnumDropdown.query.filter_by(
|
||||
enum_class=insert_enum["enum_class"],
|
||||
key=str(insert_enum["type_code"]).upper(),
|
||||
).first()
|
||||
if not created_api_enum:
|
||||
created_api_enum = ApiEnumDropdown.create(
|
||||
enum_class=insert_enum["enum_class"],
|
||||
value=insert_enum["type_name"],
|
||||
key=str(insert_enum["type_code"]).upper(),
|
||||
description=insert_enum["type_name"],
|
||||
is_confirmed=True,
|
||||
)
|
||||
created_api_enum.save()
|
||||
|
|
@ -0,0 +1,299 @@
|
|||
import arrow
|
||||
from modules.Token.password_module import PasswordModule
|
||||
from api_controllers.mongo.database import mongo_handler
|
||||
from schemas import (
|
||||
Addresses,
|
||||
BuildLivingSpace,
|
||||
Users,
|
||||
People,
|
||||
Build,
|
||||
BuildParts,
|
||||
BuildTypes,
|
||||
ApiEnumDropdown,
|
||||
Companies,
|
||||
OccupantTypes,
|
||||
)
|
||||
|
||||
def create_occupant_defaults(db_session):
|
||||
|
||||
created_list = []
|
||||
Addresses.set_session(db_session)
|
||||
BuildLivingSpace.set_session(db_session)
|
||||
Users.set_session(db_session)
|
||||
People.set_session(db_session)
|
||||
Build.set_session(db_session)
|
||||
BuildParts.set_session(db_session)
|
||||
BuildTypes.set_session(db_session)
|
||||
ApiEnumDropdown.set_session(db_session)
|
||||
Companies.set_session(db_session)
|
||||
OccupantTypes.set_session(db_session)
|
||||
|
||||
company_management = Companies.query.filter_by(formal_name = "Evyos LTD",).first()
|
||||
if not company_management:
|
||||
raise Exception("Company not found")
|
||||
|
||||
company_id, company_uu_id = company_management.id, str(company_management.uu_id)
|
||||
active_row = dict(is_confirmed=True, active=True, deleted=False, is_notification_send=True)
|
||||
build_type = BuildTypes.query.filter_by(type_code = "APT").first()
|
||||
address = Addresses.query.filter_by(letter_address = "Example Address").first()
|
||||
|
||||
created_build = Build.query.filter_by(build_name = "Build Example").first()
|
||||
if not created_build:
|
||||
created_build = Build.create(
|
||||
build_name="Build Example",
|
||||
build_code="B001",
|
||||
build_no="B001",
|
||||
build_date="01.07.1980",
|
||||
address_id=address.id,
|
||||
address_uu_id=str(address.uu_id),
|
||||
build_types_id=build_type.id,
|
||||
build_types_uu_id=str(build_type.uu_id),
|
||||
**active_row
|
||||
)
|
||||
created_list.append(created_build)
|
||||
|
||||
build_type_created = BuildTypes.query.filter_by(type_code = "APT").first()
|
||||
build_type_flat = BuildTypes.query.filter_by(type_code = "DAIRE").first()
|
||||
enum_dropdown = ApiEnumDropdown.query.filter_by(key = "NE", enum_class = "Directions").first()
|
||||
|
||||
occupant_type_prs = OccupantTypes.query.filter_by(occupant_code = "MT-PRS").first()
|
||||
occupant_type_owner = OccupantTypes.query.filter_by(occupant_code = "FL-OWN").first()
|
||||
occupant_type_tenant = OccupantTypes.query.filter_by(occupant_code = "FL-TEN").first()
|
||||
created_managment_room = BuildParts.query.filter_by(part_code = "MR001").first()
|
||||
|
||||
if not created_managment_room:
|
||||
created_managment_room = BuildParts.create(
|
||||
address_gov_code="123123123123",
|
||||
build_id=created_build.id,
|
||||
build_uu_id=str(created_build.uu_id),
|
||||
part_code="MR001",
|
||||
part_net_size=100,
|
||||
part_no=0,
|
||||
part_level=0,
|
||||
part_type_id=build_type_created.id,
|
||||
part_type_uu_id=str(build_type_created.uu_id),
|
||||
part_direction_id=enum_dropdown.id,
|
||||
part_direction_uu_id=str(enum_dropdown.uu_id),
|
||||
human_livable=True,
|
||||
due_part_key="Example",
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(created_managment_room)
|
||||
|
||||
created_flat = BuildParts.query.filter_by(part_code = "MF001").first()
|
||||
if not created_flat:
|
||||
created_flat = BuildParts.create(
|
||||
address_gov_code="123123123124",
|
||||
build_id=created_build.id,
|
||||
build_uu_id=str(created_build.uu_id),
|
||||
part_code="MF001",
|
||||
part_net_size=100,
|
||||
part_no=1,
|
||||
part_level=1,
|
||||
part_type_id=build_type_flat.id,
|
||||
part_type_uu_id=str(build_type_flat.uu_id),
|
||||
part_direction_id=enum_dropdown.id,
|
||||
part_direction_uu_id=str(enum_dropdown.uu_id),
|
||||
human_livable=True,
|
||||
due_part_key="Example",
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(created_flat)
|
||||
|
||||
build_manager_people = People.query.filter_by(person_tag = "Build Manager Example").first()
|
||||
if not build_manager_people:
|
||||
build_manager_people = People.create(
|
||||
**{
|
||||
"person_tag": "Build Manager Example",
|
||||
"firstname": "Example Build Manager",
|
||||
"surname": "Example",
|
||||
"sex_code": "M",
|
||||
"middle_name": "",
|
||||
"father_name": "Father",
|
||||
"mother_name": "Mother",
|
||||
"country_code": "TR",
|
||||
"national_identity_id": "12312312315",
|
||||
"birth_place": "Ankara",
|
||||
"birth_date": "01.07.1990",
|
||||
"tax_no": "1231231234",
|
||||
}
|
||||
)
|
||||
created_list.append(build_manager_people)
|
||||
|
||||
owner_people = People.query.filter_by(person_tag = "Owner Example").first()
|
||||
if not owner_people:
|
||||
owner_people = People.create(
|
||||
**{
|
||||
"person_tag": "Owner Example",
|
||||
"firstname": "Example Owner",
|
||||
"surname": "Example",
|
||||
"sex_code": "M",
|
||||
"middle_name": "",
|
||||
"father_name": "Father",
|
||||
"mother_name": "Mother",
|
||||
"country_code": "TR",
|
||||
"national_identity_id": "12312312316",
|
||||
"birth_place": "Ankara",
|
||||
"birth_date": "01.07.1990",
|
||||
"tax_no": "1231231234",
|
||||
}
|
||||
)
|
||||
created_list.append(owner_people)
|
||||
|
||||
tenant_people = People.query.filter_by(person_tag = "Tenant Example").first()
|
||||
if not tenant_people:
|
||||
tenant_people = People.create(
|
||||
**{
|
||||
"person_tag": "Tenant Example",
|
||||
"firstname": "Example Tenant",
|
||||
"surname": "Example",
|
||||
"sex_code": "M",
|
||||
"middle_name": "",
|
||||
"father_name": "Father",
|
||||
"mother_name": "Mother",
|
||||
"country_code": "TR",
|
||||
"national_identity_id": "12312312317",
|
||||
"birth_place": "Ankara",
|
||||
"birth_date": "01.07.1990",
|
||||
"tax_no": "1231231234",
|
||||
}
|
||||
)
|
||||
created_list.append(tenant_people)
|
||||
|
||||
main_domain, collection_name = "evyos.com.tr", f"{str(company_management.uu_id)}*Domain"
|
||||
user_build_manager = Users.query.filter_by(user_tag = "Build Manager Example").first()
|
||||
if not user_build_manager:
|
||||
user_build_manager = Users.create(
|
||||
person_id=build_manager_people.id,
|
||||
person_uu_id=str(build_manager_people.uu_id),
|
||||
user_tag=build_manager_people.person_tag,
|
||||
email="example.build.manager@gmail.com",
|
||||
phone_number="+901111111111",
|
||||
avatar="https://s.tmimgcdn.com/scr/800x500/276800/building-home-nature-logo-vector-template-3_276851-original.jpg",
|
||||
related_company=str(company_management.uu_id),
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(user_build_manager)
|
||||
user_build_manager.password_expiry_begins = str(arrow.now())
|
||||
user_build_manager.password_token = PasswordModule.generate_refresher_token()
|
||||
|
||||
user_owner = Users.query.filter_by(user_tag = "Owner Example").first()
|
||||
if not user_owner:
|
||||
user_owner = Users.create(
|
||||
person_id=owner_people.id,
|
||||
person_uu_id=str(owner_people.uu_id),
|
||||
user_tag=owner_people.person_tag,
|
||||
email="example.owner@gmail.com",
|
||||
phone_number="+901111111111",
|
||||
avatar="https://s.tmimgcdn.com/scr/800x500/276800/building-home-nature-logo-vector-template-3_276851-original.jpg",
|
||||
related_company=str(company_management.uu_id),
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(user_owner)
|
||||
user_owner.password_expiry_begins = str(arrow.now())
|
||||
user_owner.password_token = PasswordModule.generate_refresher_token()
|
||||
|
||||
user_tenant = Users.query.filter_by(user_tag = "Tenant Example").first()
|
||||
if not user_tenant:
|
||||
user_tenant = Users.create(
|
||||
person_id=tenant_people.id,
|
||||
person_uu_id=str(tenant_people.uu_id),
|
||||
user_tag=tenant_people.person_tag,
|
||||
email="example.tenant@gmail.com",
|
||||
phone_number="+901111111111",
|
||||
avatar="https://s.tmimgcdn.com/scr/800x500/276800/building-home-nature-logo-vector-template-3_276851-original.jpg",
|
||||
related_company=str(company_management.uu_id),
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(user_tenant)
|
||||
user_tenant.password_expiry_begins = str(arrow.now())
|
||||
user_tenant.password_token = PasswordModule.generate_refresher_token()
|
||||
|
||||
with mongo_handler.collection(collection_name) as mongo_engine:
|
||||
existing_record = mongo_engine.find_one({"user_uu_id": str(user_build_manager.uu_id)})
|
||||
if not existing_record:
|
||||
mongo_engine.insert_one(
|
||||
document={"user_uu_id": str(user_build_manager.uu_id), "other_domains_list": [main_domain], "main_domain": main_domain, "modified_at": arrow.now().timestamp()}
|
||||
)
|
||||
else:
|
||||
mongo_engine.update_one(
|
||||
{"user_uu_id": str(user_build_manager.uu_id)},
|
||||
{"$set": {"other_domains_list": [main_domain], "main_domain": main_domain, "modified_at": arrow.now().timestamp()}}
|
||||
)
|
||||
|
||||
with mongo_handler.collection(collection_name) as mongo_engine:
|
||||
existing_record = mongo_engine.find_one({"user_uu_id": str(user_owner.uu_id)})
|
||||
if not existing_record:
|
||||
mongo_engine.insert_one(
|
||||
document={"user_uu_id": str(user_owner.uu_id), "other_domains_list": [main_domain], "main_domain": main_domain, "modified_at": arrow.now().timestamp()}
|
||||
)
|
||||
else:
|
||||
mongo_engine.update_one(
|
||||
{"user_uu_id": str(user_owner.uu_id)},
|
||||
{"$set": {"other_domains_list": [main_domain], "main_domain": main_domain, "modified_at": arrow.now().timestamp()}}
|
||||
)
|
||||
|
||||
with mongo_handler.collection(collection_name) as mongo_engine:
|
||||
existing_record = mongo_engine.find_one({"user_uu_id": str(user_tenant.uu_id)})
|
||||
if not existing_record:
|
||||
mongo_engine.insert_one(
|
||||
document={"user_uu_id": str(user_tenant.uu_id), "other_domains_list": [main_domain], "main_domain": main_domain, "modified_at": arrow.now().timestamp()}
|
||||
)
|
||||
else:
|
||||
mongo_engine.update_one(
|
||||
{"user_uu_id": str(user_tenant.uu_id)},
|
||||
{"$set": {"other_domains_list": [main_domain], "main_domain": main_domain, "modified_at": arrow.now().timestamp()}})
|
||||
|
||||
created_build_living_space_prs = BuildLivingSpace.query.filter_by(
|
||||
build_id=created_build.id, build_parts_id=created_managment_room.id, person_id=build_manager_people.id
|
||||
).first()
|
||||
if not created_build_living_space_prs:
|
||||
created_build_living_space_prs = BuildLivingSpace.create(
|
||||
build_id=created_build.id,
|
||||
build_uu_id=str(created_build.uu_id),
|
||||
build_parts_id=created_managment_room.id,
|
||||
build_parts_uu_id=str(created_managment_room.uu_id),
|
||||
person_id=build_manager_people.id,
|
||||
person_uu_id=str(build_manager_people.uu_id),
|
||||
occupant_type_id=occupant_type_prs.id,
|
||||
occupant_type_uu_id=str(occupant_type_prs.uu_id),
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(created_build_living_space_prs)
|
||||
|
||||
created_build_living_space_owner = BuildLivingSpace.query.filter_by(
|
||||
build_id=created_build.id, build_parts_id=created_flat.id, person_id=owner_people.id
|
||||
).first()
|
||||
if not created_build_living_space_owner:
|
||||
created_build_living_space_owner = BuildLivingSpace.create(
|
||||
build_id=created_build.id,
|
||||
build_uu_id=str(created_build.uu_id),
|
||||
build_parts_id=created_flat.id,
|
||||
build_parts_uu_id=str(created_flat.uu_id),
|
||||
person_id=owner_people.id,
|
||||
person_uu_id=str(owner_people.uu_id),
|
||||
occupant_type_id=occupant_type_owner.id,
|
||||
occupant_type_uu_id=str(occupant_type_owner.uu_id),
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(created_build_living_space_owner)
|
||||
|
||||
created_build_living_space_tenant = BuildLivingSpace.query.filter_by(
|
||||
build_id=created_build.id, build_parts_id=created_flat.id, person_id=tenant_people.id
|
||||
).first()
|
||||
if not created_build_living_space_tenant:
|
||||
created_build_living_space_tenant = BuildLivingSpace.create(
|
||||
build_id=created_build.id,
|
||||
build_uu_id=str(created_build.uu_id),
|
||||
build_parts_id=created_flat.id,
|
||||
build_parts_uu_id=str(created_flat.uu_id),
|
||||
person_id=tenant_people.id,
|
||||
person_uu_id=str(tenant_people.uu_id),
|
||||
occupant_type_id=occupant_type_tenant.id,
|
||||
occupant_type_uu_id=str(occupant_type_tenant.uu_id),
|
||||
**active_row,
|
||||
)
|
||||
created_list.append(created_build_living_space_tenant)
|
||||
|
||||
db_session.commit()
|
||||
print("Occupant Defaults Create is now completed")
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
from schemas import OccupantTypes
|
||||
|
||||
|
||||
def create_occupant_types_defaults(db_session):
|
||||
|
||||
"""
|
||||
occupant_category = mapped_column(String, server_default="")
|
||||
occupant_category_type = mapped_column(String, server_default="")
|
||||
occupant_is_unique = mapped_column(Boolean, server_default="0")
|
||||
"""
|
||||
|
||||
OccupantTypes.set_session(db_session)
|
||||
list_occupant_types = [
|
||||
{
|
||||
"occupant_type": "Toplantı Başkanı",
|
||||
"occupant_description": "Toplantı Başkanı",
|
||||
"occupant_code": "MT-PRS",
|
||||
"occupant_category": "Toplantı",
|
||||
"occupant_category_type": "MT",
|
||||
"occupant_is_unique": True,
|
||||
},
|
||||
{
|
||||
"occupant_type": "Toplantı Katip",
|
||||
"occupant_description": "Toplantıda tutanak tutan kişi",
|
||||
"occupant_code": "MT-WRT",
|
||||
"occupant_category": "Toplantı",
|
||||
"occupant_category_type": "MT",
|
||||
"occupant_is_unique": True,
|
||||
},
|
||||
{
|
||||
"occupant_type": "Toplantı Katılımcısı",
|
||||
"occupant_description": "Toplantıda sadece katılan kişi",
|
||||
"occupant_code": "MT-ATT",
|
||||
"occupant_category": "Toplantı",
|
||||
"occupant_category_type": "MT",
|
||||
"occupant_is_unique": False,
|
||||
},
|
||||
{
|
||||
"occupant_type": "Toplantı Danışman",
|
||||
"occupant_description": "Toplantıda danışmanlık yapan kişi",
|
||||
"occupant_code": "MT-ADV",
|
||||
"occupant_category": "Toplantı",
|
||||
"occupant_category_type": "MT",
|
||||
"occupant_is_unique": False,
|
||||
},
|
||||
{
|
||||
"occupant_type": "Toplantı Seçilmiş Başkanı",
|
||||
"occupant_description": "Toplantı Seçilmiş Başkanı",
|
||||
"occupant_code": "MT-VPR",
|
||||
"occupant_category": "Toplantı",
|
||||
"occupant_category_type": "MT",
|
||||
"occupant_is_unique": True,
|
||||
},
|
||||
{
|
||||
"occupant_type": "Daire Sahibi",
|
||||
"occupant_description": "Daire Sahibi",
|
||||
"occupant_code": "FL-OWN",
|
||||
"occupant_category": "Daire",
|
||||
"occupant_category_type": "FL",
|
||||
"occupant_is_unique": True,
|
||||
},
|
||||
{
|
||||
"occupant_type": "Daire Kiracısı",
|
||||
"occupant_description": "Daire Kiracısı",
|
||||
"occupant_code": "FL-TEN",
|
||||
"occupant_category": "Daire",
|
||||
"occupant_category_type": "FL",
|
||||
"occupant_is_unique": True,
|
||||
},
|
||||
{
|
||||
"occupant_type": "Daire Sakini",
|
||||
"occupant_description": "Daire Sakini",
|
||||
"occupant_code": "FL-RES",
|
||||
"occupant_category": "Daire",
|
||||
"occupant_category_type": "FL",
|
||||
"occupant_is_unique": False,
|
||||
},
|
||||
{
|
||||
"occupant_type": "Daire Sakini Vekili",
|
||||
"occupant_description": "Daire Sakini Vekili",
|
||||
"occupant_code": "FL-REP",
|
||||
"occupant_category": "Daire",
|
||||
"occupant_category_type": "FL",
|
||||
"occupant_is_unique": False,
|
||||
},
|
||||
{
|
||||
"occupant_type": "Bina Avukatı",
|
||||
"occupant_description": "Bina Avukatı",
|
||||
"occupant_code": "BU-ATT",
|
||||
"occupant_category": "Bina",
|
||||
"occupant_category_type": "BU",
|
||||
"occupant_is_unique": False,
|
||||
},
|
||||
{
|
||||
"occupant_type": "Bina Avukatı Yardımcısı",
|
||||
"occupant_description": "Bina Avukatı Yardımcısı",
|
||||
"occupant_code": "BU-ATA",
|
||||
"occupant_category": "Bina",
|
||||
"occupant_category_type": "BU",
|
||||
"occupant_is_unique": False,
|
||||
},
|
||||
{
|
||||
"occupant_type": "Bina Denetmen Yardımcısı",
|
||||
"occupant_description": "Bina Denetmen Yardımcısı",
|
||||
"occupant_code": "BU-SPA",
|
||||
"occupant_category": "Bina",
|
||||
"occupant_category_type": "BU",
|
||||
"occupant_is_unique": False,
|
||||
},
|
||||
{
|
||||
"occupant_type": "Bina Denetmeni",
|
||||
"occupant_description": "Bina Denetmeni",
|
||||
"occupant_code": "BU-SPV",
|
||||
"occupant_category": "Bina",
|
||||
"occupant_category_type": "BU",
|
||||
"occupant_is_unique": False,
|
||||
},
|
||||
{
|
||||
"occupant_type": "Bina Yönetici Yardımcısı",
|
||||
"occupant_description": "Bina Yönetici Yardımcısı",
|
||||
"occupant_code": "BU-MNA",
|
||||
"occupant_category": "Bina",
|
||||
"occupant_category_type": "BU",
|
||||
"occupant_is_unique": False,
|
||||
},
|
||||
{
|
||||
"occupant_type": "Bina Yöneticisi",
|
||||
"occupant_description": "Bina Yöneticisi",
|
||||
"occupant_code": "BU-MNG",
|
||||
"occupant_category": "Bina",
|
||||
"occupant_category_type": "BU",
|
||||
"occupant_is_unique": True,
|
||||
},
|
||||
{
|
||||
"occupant_type": "Bina Muhasabecisi",
|
||||
"occupant_description": "Bina Muhasabecisi",
|
||||
"occupant_code": "BU-ACC",
|
||||
"occupant_category": "Bina",
|
||||
"occupant_category_type": "BU",
|
||||
"occupant_is_unique": False,
|
||||
},
|
||||
{
|
||||
"occupant_type": "Proje Lideri",
|
||||
"occupant_description": "Proje Lideri",
|
||||
"occupant_code": "PRJ-LDR",
|
||||
"occupant_category": "Proje",
|
||||
"occupant_category_type": "PRJ",
|
||||
"occupant_is_unique": False,
|
||||
},
|
||||
{
|
||||
"occupant_type": "Proje Sorumlusu",
|
||||
"occupant_description": "Proje Sorumlusu",
|
||||
"occupant_code": "PRJ-RES",
|
||||
"occupant_category": "Proje",
|
||||
"occupant_category_type": "PRJ",
|
||||
"occupant_is_unique": False,
|
||||
},
|
||||
{
|
||||
"occupant_type": "Proje Ekibi",
|
||||
"occupant_description": "Proje Ekibi",
|
||||
"occupant_code": "PRJ-EMP",
|
||||
"occupant_category": "Proje",
|
||||
"occupant_category_type": "PRJ",
|
||||
"occupant_is_unique": False,
|
||||
},
|
||||
{
|
||||
"occupant_type": "Proje Finans Sorumlusu",
|
||||
"occupant_description": "Proje Finans Sorumlusu",
|
||||
"occupant_code": "PRJ-FIN",
|
||||
"occupant_category": "Proje",
|
||||
"occupant_category_type": "PRJ",
|
||||
"occupant_is_unique": False,
|
||||
},
|
||||
{
|
||||
"occupant_type": "Proje Teknik Sorumlusu",
|
||||
"occupant_description": "Proje Teknik Sorumlusu",
|
||||
"occupant_code": "PRJ-TEC",
|
||||
"occupant_category": "Proje",
|
||||
"occupant_category_type": "PRJ",
|
||||
"occupant_is_unique": False,
|
||||
},
|
||||
{
|
||||
"occupant_type": "Daire Mülkiyet Vekili",
|
||||
"occupant_description": "Daire Mülkiyet Vekili",
|
||||
"occupant_code": "FL-DEP", # deputy
|
||||
"occupant_category": "Daire",
|
||||
"occupant_category_type": "FL",
|
||||
"occupant_is_unique": False,
|
||||
},
|
||||
{
|
||||
"occupant_type": "Bina Teknik Sorumlusu",
|
||||
"occupant_description": "Bina Teknik Sorumlusu",
|
||||
"occupant_code": "BU-TEC",
|
||||
"occupant_category": "Bina",
|
||||
"occupant_category_type": "BU",
|
||||
"occupant_is_unique": False,
|
||||
},
|
||||
{
|
||||
"occupant_type": "Bina Teknik Elemanı",
|
||||
"occupant_description": "Bina Teknik Elemanı",
|
||||
"occupant_code": "BU-EMP",
|
||||
"occupant_category": "Bina",
|
||||
"occupant_category_type": "BU",
|
||||
"occupant_is_unique": False,
|
||||
},
|
||||
{
|
||||
"occupant_type": "Bina Teknik Freelancer",
|
||||
"occupant_description": "Bina Teknik Freelancer",
|
||||
"occupant_code": "BU-FLC",
|
||||
"occupant_category": "Bina",
|
||||
"occupant_category_type": "BU",
|
||||
"occupant_is_unique": False,
|
||||
},
|
||||
]
|
||||
for list_occupant_type in list_occupant_types:
|
||||
try:
|
||||
created_type = OccupantTypes.query.filter_by(
|
||||
occupant_code=list_occupant_type["occupant_code"],
|
||||
occupant_category=list_occupant_type["occupant_category_type"],
|
||||
).first()
|
||||
if not created_type:
|
||||
created_type = OccupantTypes.create(**list_occupant_type, is_confirmed=True)
|
||||
created_type.save()
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
from schemas import (
|
||||
Duty,
|
||||
OccupantTypes,
|
||||
Modules,
|
||||
Services,
|
||||
)
|
||||
|
||||
def create_modules_and_services_and_actions(db_session):
|
||||
|
||||
Duty.set_session(db_session)
|
||||
Services.set_session(db_session)
|
||||
Modules.set_session(db_session)
|
||||
OccupantTypes.set_session(db_session)
|
||||
|
||||
erp_module = Modules.query.filter_by(module_code = "EVYOS-ERP").first()
|
||||
if not erp_module:
|
||||
erp_module = Modules.create(
|
||||
**{
|
||||
"module_name": "EVYOS ERP", "module_description": "EVYOS Enterprise Resource Planning", "module_code": "EVYOS-ERP",
|
||||
"module_layer": 1, "is_default_module": False, "is_confirmed": True,
|
||||
}
|
||||
)
|
||||
erp_module.save()
|
||||
|
||||
build_module = Modules.query.filter_by(module_code = "BLD-MNG").first()
|
||||
if not build_module:
|
||||
build_module = Modules.create(
|
||||
**{
|
||||
"module_name": "Bina Yönetim Modülü", "module_description": "Building Management Module", "module_code": "BLD-MNG",
|
||||
"module_layer": 1, "is_default_module": False, "is_confirmed": True,
|
||||
}
|
||||
)
|
||||
build_module.save()
|
||||
|
||||
user_module = Modules.query.filter_by(module_code = "USR-PUB").first()
|
||||
if not user_module:
|
||||
user_module = Modules.create(
|
||||
**{
|
||||
"module_name": "Kullancı Modülü", "module_description": "Kullanıcı Genel Modülü", "module_code": "USR-PUB", "module_layer": 1,
|
||||
"is_default_module": True, "is_confirmed": True
|
||||
}
|
||||
)
|
||||
user_module.save()
|
||||
|
||||
erp_module_module_dict = dict(module_id=erp_module.id, module_uu_id=str(erp_module.uu_id))
|
||||
build_module_module_dict = dict(module_id=build_module.id, module_uu_id=str(build_module.uu_id))
|
||||
duty_objects = Duty.query.filter(Duty.module_id == erp_module.id).all()
|
||||
if not duty_objects:
|
||||
raise Exception("Duty objects not found")
|
||||
|
||||
for duty_object in duty_objects:
|
||||
created_service = Services.query.filter(Services.service_code == f"SRE-{duty_object.duty_code}").first()
|
||||
if not created_service:
|
||||
created_service = Services.create(
|
||||
**erp_module_module_dict,
|
||||
service_name=duty_object.duty_name,
|
||||
service_description=duty_object.duty_description,
|
||||
service_code=f"SRE-{duty_object.duty_code}",
|
||||
related_responsibility=duty_object.duty_code,
|
||||
is_confirmed=True,
|
||||
)
|
||||
created_service.save()
|
||||
|
||||
occupant_types = OccupantTypes.query.filter(OccupantTypes.module_id == build_module.id).all()
|
||||
if not occupant_types:
|
||||
raise Exception("Occupant types not found")
|
||||
for occupant_type in occupant_types:
|
||||
created_service = Services.query.filter(Services.service_code == f"SRO-{occupant_type.occupant_code}").first()
|
||||
if not created_service:
|
||||
created_service = Services.create(
|
||||
**build_module_module_dict,
|
||||
service_name=occupant_type.occupant_type,
|
||||
service_description=occupant_type.occupant_description,
|
||||
service_code=f"SRO-{occupant_type.occupant_code}",
|
||||
related_responsibility=occupant_type.occupant_code,
|
||||
is_confirmed=True,
|
||||
)
|
||||
created_service.save()
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class Configs(BaseSettings):
|
||||
"""
|
||||
ApiTemplate configuration settings.
|
||||
"""
|
||||
|
||||
ACCESS_TOKEN_LENGTH: int = 90
|
||||
REFRESHER_TOKEN_LENGTH: int = 144
|
||||
|
||||
model_config = SettingsConfigDict(env_prefix="API_")
|
||||
|
||||
|
||||
token_config = Configs()
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import hashlib
|
||||
import uuid
|
||||
import secrets
|
||||
import random
|
||||
|
||||
from .config import token_config
|
||||
|
||||
|
||||
class PasswordModule:
|
||||
|
||||
@staticmethod
|
||||
def generate_random_uu_id(str_std: bool = True):
|
||||
return str(uuid.uuid4()) if str_std else uuid.uuid4()
|
||||
|
||||
@staticmethod
|
||||
def generate_token(length=32) -> str:
|
||||
letters = "abcdefghijklmnopqrstuvwxyz"
|
||||
merged_letters = [letter for letter in letters] + [
|
||||
letter.upper() for letter in letters
|
||||
]
|
||||
token_generated = secrets.token_urlsafe(length)
|
||||
for i in str(token_generated):
|
||||
if i not in merged_letters:
|
||||
token_generated = token_generated.replace(
|
||||
i, random.choice(merged_letters), 1
|
||||
)
|
||||
return token_generated
|
||||
raise ValueError("EYS_0004")
|
||||
|
||||
@classmethod
|
||||
def generate_access_token(cls) -> str:
|
||||
return cls.generate_token(int(token_config.ACCESS_TOKEN_LENGTH))
|
||||
|
||||
@classmethod
|
||||
def generate_refresher_token(cls) -> str:
|
||||
return cls.generate_token(int(token_config.REFRESHER_TOKEN_LENGTH))
|
||||
|
||||
@staticmethod
|
||||
def create_hashed_password(domain: str, id_: str, password: str) -> str:
|
||||
return hashlib.sha256(f"{domain}:{id_}:{password}".encode("utf-8")).hexdigest()
|
||||
|
||||
@classmethod
|
||||
def check_password(cls, domain, id_, password, password_hashed) -> bool:
|
||||
return cls.create_hashed_password(domain, id_, password) == password_hashed
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
# Database Handlers
|
||||
|
||||
This directory contains database handlers for MongoDB and PostgreSQL used in the backend automate services.
|
||||
|
||||
## Overview
|
||||
|
||||
The database handlers provide a consistent interface for interacting with different database systems. They implement:
|
||||
|
||||
- Connection pooling
|
||||
- Retry mechanisms
|
||||
- Error handling
|
||||
- Thread safety
|
||||
- Context managers for resource management
|
||||
|
||||
## MongoDB Handler
|
||||
|
||||
The MongoDB handler is implemented as a singleton pattern to ensure efficient connection management across the application. It provides:
|
||||
|
||||
- Connection pooling via PyMongo's built-in connection pool
|
||||
- Automatic retry capabilities for MongoDB operations
|
||||
- Context manager for MongoDB collections to ensure connections are properly closed
|
||||
- Thread safety for concurrent operations
|
||||
|
||||
### MongoDB Performance
|
||||
|
||||
The MongoDB handler has been tested with a concurrent load test:
|
||||
|
||||
```
|
||||
Concurrent Operation Test Results:
|
||||
Total threads: 100
|
||||
Passed: 100
|
||||
Failed: 0
|
||||
Execution time: 0.73 seconds
|
||||
Operations per second: 137.61
|
||||
```
|
||||
|
||||
## PostgreSQL Handler
|
||||
|
||||
The PostgreSQL handler leverages SQLAlchemy for ORM capabilities and connection management. It provides:
|
||||
|
||||
- Connection pooling via SQLAlchemy's connection pool
|
||||
- ORM models with CRUD operations
|
||||
- Filter methods for querying data
|
||||
- Transaction management
|
||||
|
||||
### PostgreSQL Performance
|
||||
|
||||
The PostgreSQL handler has been tested with a concurrent load test:
|
||||
|
||||
```
|
||||
Concurrent Operation Test Results:
|
||||
Total threads: 100
|
||||
Passed: 100
|
||||
Failed: 0
|
||||
Execution time: 0.30 seconds
|
||||
Operations per second: 332.11
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### MongoDB Example
|
||||
|
||||
```python
|
||||
from Controllers.Mongo.database import mongo_handler
|
||||
|
||||
# Using the context manager for automatic connection management
|
||||
with mongo_handler.collection("users") as users_collection:
|
||||
# Perform operations
|
||||
users_collection.insert_one({"name": "John", "email": "john@example.com"})
|
||||
user = users_collection.find_one({"email": "john@example.com"})
|
||||
```
|
||||
|
||||
### PostgreSQL Example
|
||||
|
||||
```python
|
||||
from Controllers.Postgres.schema import EndpointRestriction
|
||||
|
||||
# Using the session context manager
|
||||
with EndpointRestriction.new_session() as db_session:
|
||||
# Create a new record
|
||||
new_endpoint = EndpointRestriction(
|
||||
endpoint_code="TEST_API",
|
||||
endpoint_name="Test API",
|
||||
endpoint_method="GET",
|
||||
endpoint_function="test_function",
|
||||
endpoint_desc="Test description",
|
||||
is_confirmed=True
|
||||
)
|
||||
new_endpoint.save(db=db_session)
|
||||
|
||||
# Query records
|
||||
result = EndpointRestriction.filter_one(
|
||||
EndpointRestriction.endpoint_code == "TEST_API",
|
||||
db=db_session
|
||||
).data
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Both handlers are configured via environment variables:
|
||||
|
||||
### MongoDB Configuration
|
||||
|
||||
- `MONGO_ENGINE`: Database engine (mongodb)
|
||||
- `MONGO_HOST`: Database host
|
||||
- `MONGO_PORT`: Database port
|
||||
- `MONGO_USER`: Database username
|
||||
- `MONGO_PASSWORD`: Database password
|
||||
- `MONGO_DB`: Database name
|
||||
- `MONGO_AUTH_DB`: Authentication database
|
||||
|
||||
### PostgreSQL Configuration
|
||||
|
||||
- `POSTGRES_ENGINE`: Database engine (postgresql+psycopg2)
|
||||
- `POSTGRES_HOST`: Database host
|
||||
- `POSTGRES_PORT`: Database port
|
||||
- `POSTGRES_USER`: Database username
|
||||
- `POSTGRES_PASSWORD`: Database password
|
||||
- `POSTGRES_DB`: Database name
|
||||
- `POSTGRES_POOL_SIZE`: Connection pool size
|
||||
- `POSTGRES_POOL_PRE_PING`: Whether to ping the database before using a connection
|
||||
|
||||
## Testing
|
||||
|
||||
Both handlers include comprehensive test suites that verify:
|
||||
|
||||
- Basic CRUD operations
|
||||
- Complex queries
|
||||
- Nested documents (MongoDB)
|
||||
- Array operations (MongoDB)
|
||||
- Aggregation (MongoDB)
|
||||
- Index operations
|
||||
- Concurrent operations
|
||||
|
||||
To run the tests:
|
||||
|
||||
```bash
|
||||
# MongoDB tests
|
||||
python -m Controllers.Mongo.implementations
|
||||
|
||||
# PostgreSQL tests
|
||||
python -m Controllers.Postgres.implementations
|
||||
```
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class Configs(BaseSettings):
|
||||
"""
|
||||
Email configuration settings.
|
||||
"""
|
||||
|
||||
HOST: str = ""
|
||||
USERNAME: str = ""
|
||||
PASSWORD: str = ""
|
||||
PORT: int = 0
|
||||
SEND: bool = True
|
||||
|
||||
@property
|
||||
def is_send(self):
|
||||
return bool(self.SEND)
|
||||
|
||||
def as_dict(self):
|
||||
return dict(
|
||||
host=self.HOST,
|
||||
port=self.PORT,
|
||||
username=self.USERNAME,
|
||||
password=self.PASSWORD,
|
||||
)
|
||||
|
||||
model_config = SettingsConfigDict(env_prefix="EMAIL_")
|
||||
|
||||
|
||||
# singleton instance of the POSTGRESQL configuration settings
|
||||
email_configs = Configs()
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
from send_email import EmailService, EmailSendModel
|
||||
|
||||
|
||||
# Create email parameters
|
||||
email_params = EmailSendModel(
|
||||
subject="Test Email",
|
||||
html="<p>Hello world!</p>",
|
||||
receivers=["recipient@example.com"],
|
||||
text="Hello world!",
|
||||
)
|
||||
|
||||
another_email_params = EmailSendModel(
|
||||
subject="Test Email2",
|
||||
html="<p>Hello world!2</p>",
|
||||
receivers=["recipient@example.com"],
|
||||
text="Hello world!2",
|
||||
)
|
||||
|
||||
|
||||
# The context manager handles connection errors
|
||||
with EmailService.new_session() as email_session:
|
||||
# Send email - any exceptions here will propagate up
|
||||
EmailService.send_email(email_session, email_params)
|
||||
|
||||
# Or send directly through the session
|
||||
email_session.send(email_params)
|
||||
|
||||
# Send more emails in the same session if needed
|
||||
EmailService.send_email(email_session, another_email_params)
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
from redmail import EmailSender
|
||||
from typing import List, Optional, Dict
|
||||
from pydantic import BaseModel
|
||||
from contextlib import contextmanager
|
||||
from .config import email_configs
|
||||
|
||||
|
||||
class EmailSendModel(BaseModel):
|
||||
subject: str
|
||||
html: str = ""
|
||||
receivers: List[str]
|
||||
text: Optional[str] = ""
|
||||
cc: Optional[List[str]] = None
|
||||
bcc: Optional[List[str]] = None
|
||||
headers: Optional[Dict] = None
|
||||
attachments: Optional[Dict] = None
|
||||
|
||||
|
||||
class EmailSession:
|
||||
|
||||
def __init__(self, email_sender):
|
||||
self.email_sender = email_sender
|
||||
|
||||
def send(self, params: EmailSendModel) -> bool:
|
||||
"""Send email using this session."""
|
||||
if not email_configs.is_send:
|
||||
print("Email sending is disabled", params)
|
||||
return False
|
||||
receivers = [email_configs.USERNAME]
|
||||
|
||||
# Ensure connection is established before sending
|
||||
try:
|
||||
# Check if connection exists, if not establish it
|
||||
if not hasattr(self.email_sender, '_connected') or not self.email_sender._connected:
|
||||
self.email_sender.connect()
|
||||
|
||||
self.email_sender.send(
|
||||
subject=params.subject,
|
||||
receivers=receivers,
|
||||
text=params.text + f" : Gonderilen [{str(receivers)}]",
|
||||
html=params.html,
|
||||
cc=params.cc,
|
||||
bcc=params.bcc,
|
||||
headers=params.headers or {},
|
||||
attachments=params.attachments or {},
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error sending email: {e}")
|
||||
raise
|
||||
|
||||
|
||||
class EmailService:
|
||||
_instance = None
|
||||
|
||||
def __new__(cls):
|
||||
if cls._instance is None:
|
||||
cls._instance = super(EmailService, cls).__new__(cls)
|
||||
return cls._instance
|
||||
|
||||
@classmethod
|
||||
@contextmanager
|
||||
def new_session(cls):
|
||||
"""Create and yield a new email session with active connection."""
|
||||
email_sender = EmailSender(**email_configs.as_dict())
|
||||
session = EmailSession(email_sender)
|
||||
connection_established = False
|
||||
try:
|
||||
# Establish connection and set flag
|
||||
email_sender.connect()
|
||||
# Set a flag to track connection state
|
||||
email_sender._connected = True
|
||||
connection_established = True
|
||||
yield session
|
||||
except Exception as e:
|
||||
print(f"Error with email connection: {e}")
|
||||
raise
|
||||
finally:
|
||||
# Only close if connection was successfully established
|
||||
if connection_established:
|
||||
try:
|
||||
email_sender.close()
|
||||
email_sender._connected = False
|
||||
except Exception as e:
|
||||
print(f"Error closing email connection: {e}")
|
||||
|
||||
@classmethod
|
||||
def send_email(cls, session: EmailSession, params: EmailSendModel) -> bool:
|
||||
"""Send email using the provided session."""
|
||||
return session.send(params)
|
||||
|
|
@ -0,0 +1,219 @@
|
|||
# MongoDB Handler
|
||||
|
||||
A singleton MongoDB handler with context manager support for MongoDB collections and automatic retry capabilities.
|
||||
|
||||
## Features
|
||||
|
||||
- **Singleton Pattern**: Ensures only one instance of the MongoDB handler exists
|
||||
- **Context Manager**: Automatically manages connection lifecycle
|
||||
- **Retry Capability**: Automatically retries MongoDB operations on failure
|
||||
- **Connection Pooling**: Configurable connection pooling
|
||||
- **Graceful Degradation**: Handles connection failures without crashing
|
||||
|
||||
## Usage
|
||||
|
||||
```python
|
||||
from Controllers.Mongo.database import mongo_handler
|
||||
|
||||
# Use the context manager to access a collection
|
||||
with mongo_handler.collection("users") as users_collection:
|
||||
# Perform operations on the collection
|
||||
users_collection.insert_one({"username": "john", "email": "john@example.com"})
|
||||
user = users_collection.find_one({"username": "john"})
|
||||
# Connection is automatically closed when exiting the context
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
MongoDB connection settings are configured via environment variables with the `MONGO_` prefix:
|
||||
|
||||
- `MONGO_ENGINE`: Database engine (e.g., "mongodb")
|
||||
- `MONGO_USER`: MongoDB username
|
||||
- `MONGO_PASSWORD`: MongoDB password
|
||||
- `MONGO_HOST`: MongoDB host
|
||||
- `MONGO_PORT`: MongoDB port
|
||||
- `MONGO_DB`: Database name
|
||||
- `MONGO_AUTH_DB`: Authentication database
|
||||
|
||||
## Monitoring Connection Closure
|
||||
|
||||
To verify that MongoDB sessions are properly closed, you can implement one of the following approaches:
|
||||
|
||||
### 1. Add Logging to the `__exit__` Method
|
||||
|
||||
```python
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
"""
|
||||
Exit context, closing the connection.
|
||||
"""
|
||||
if self.client:
|
||||
print(f"Closing MongoDB connection for collection: {self.collection_name}")
|
||||
# Or use a proper logger
|
||||
# logger.info(f"Closing MongoDB connection for collection: {self.collection_name}")
|
||||
self.client.close()
|
||||
self.client = None
|
||||
self.collection = None
|
||||
print(f"MongoDB connection closed successfully")
|
||||
```
|
||||
|
||||
### 2. Add Connection Tracking
|
||||
|
||||
```python
|
||||
class MongoDBHandler:
|
||||
# Add these to your class
|
||||
_open_connections = 0
|
||||
|
||||
def get_connection_stats(self):
|
||||
"""Return statistics about open connections"""
|
||||
return {"open_connections": self._open_connections}
|
||||
```
|
||||
|
||||
Then modify the `CollectionContext` class:
|
||||
|
||||
```python
|
||||
def __enter__(self):
|
||||
try:
|
||||
# Create a new client connection
|
||||
self.client = MongoClient(self.db_handler.uri, **self.db_handler.client_options)
|
||||
# Increment connection counter
|
||||
self.db_handler._open_connections += 1
|
||||
# Rest of your code...
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if self.client:
|
||||
# Decrement connection counter
|
||||
self.db_handler._open_connections -= 1
|
||||
self.client.close()
|
||||
self.client = None
|
||||
self.collection = None
|
||||
```
|
||||
|
||||
### 3. Use MongoDB's Built-in Monitoring
|
||||
|
||||
```python
|
||||
from pymongo import monitoring
|
||||
|
||||
class ConnectionCommandListener(monitoring.CommandListener):
|
||||
def started(self, event):
|
||||
print(f"Command {event.command_name} started on server {event.connection_id}")
|
||||
|
||||
def succeeded(self, event):
|
||||
print(f"Command {event.command_name} succeeded in {event.duration_micros} microseconds")
|
||||
|
||||
def failed(self, event):
|
||||
print(f"Command {event.command_name} failed in {event.duration_micros} microseconds")
|
||||
|
||||
# Register the listener
|
||||
monitoring.register(ConnectionCommandListener())
|
||||
```
|
||||
|
||||
### 4. Add a Test Function
|
||||
|
||||
```python
|
||||
def test_connection_closure():
|
||||
"""Test that MongoDB connections are properly closed."""
|
||||
print("\nTesting connection closure...")
|
||||
|
||||
# Record initial connection count (if you implemented the counter)
|
||||
initial_count = mongo_handler.get_connection_stats()["open_connections"]
|
||||
|
||||
# Use multiple nested contexts
|
||||
for i in range(5):
|
||||
with mongo_handler.collection("test_collection") as collection:
|
||||
# Do some simple operation
|
||||
collection.find_one({})
|
||||
|
||||
# Check final connection count
|
||||
final_count = mongo_handler.get_connection_stats()["open_connections"]
|
||||
|
||||
if final_count == initial_count:
|
||||
print("Test passed: All connections were properly closed")
|
||||
return True
|
||||
else:
|
||||
print(f"Test failed: {final_count - initial_count} connections remain open")
|
||||
return False
|
||||
```
|
||||
|
||||
### 5. Use MongoDB Server Logs
|
||||
|
||||
You can also check the MongoDB server logs to see connection events:
|
||||
|
||||
```bash
|
||||
# Run this on your MongoDB server
|
||||
tail -f /var/log/mongodb/mongod.log | grep "connection"
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Always use the context manager pattern to ensure connections are properly closed
|
||||
2. Keep operations within the context manager as concise as possible
|
||||
3. Handle exceptions within the context to prevent unexpected behavior
|
||||
4. Avoid nesting multiple context managers unnecessarily
|
||||
5. Use the retry decorator for operations that might fail due to transient issues
|
||||
|
||||
## LXC Container Configuration
|
||||
|
||||
### Authentication Issues
|
||||
|
||||
If you encounter authentication errors when connecting to the MongoDB container at 10.10.2.13:27017, you may need to update the container configuration:
|
||||
|
||||
1. **Check MongoDB Authentication**: Ensure the MongoDB container is configured with the correct authentication mechanism
|
||||
|
||||
2. **Verify Network Configuration**: Make sure the container network allows connections from your application
|
||||
|
||||
3. **Update MongoDB Configuration**:
|
||||
- Edit the MongoDB configuration file in the container
|
||||
- Ensure `bindIp` is set correctly (e.g., `0.0.0.0` to allow connections from any IP)
|
||||
- Check that authentication is enabled with the correct mechanism
|
||||
|
||||
4. **User Permissions**:
|
||||
- Verify that the application user (`appuser`) exists in the MongoDB instance
|
||||
- Ensure the user has the correct roles and permissions for the database
|
||||
|
||||
### Example MongoDB Container Configuration
|
||||
|
||||
```yaml
|
||||
# Example docker-compose.yml configuration
|
||||
services:
|
||||
mongodb:
|
||||
image: mongo:latest
|
||||
container_name: mongodb
|
||||
environment:
|
||||
- MONGO_INITDB_ROOT_USERNAME=admin
|
||||
- MONGO_INITDB_ROOT_PASSWORD=password
|
||||
volumes:
|
||||
- ./init-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js:ro
|
||||
ports:
|
||||
- "27017:27017"
|
||||
command: mongod --auth
|
||||
```
|
||||
|
||||
```javascript
|
||||
// Example init-mongo.js
|
||||
db.createUser({
|
||||
user: 'appuser',
|
||||
pwd: 'apppassword',
|
||||
roles: [
|
||||
{ role: 'readWrite', db: 'appdb' }
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Authentication Failed**:
|
||||
- Verify username and password in environment variables
|
||||
- Check that the user exists in the specified authentication database
|
||||
- Ensure the user has appropriate permissions
|
||||
|
||||
2. **Connection Refused**:
|
||||
- Verify the MongoDB host and port are correct
|
||||
- Check network connectivity between application and MongoDB container
|
||||
- Ensure MongoDB is running and accepting connections
|
||||
|
||||
3. **Resource Leaks**:
|
||||
- Use the context manager pattern to ensure connections are properly closed
|
||||
- Monitor connection pool size and active connections
|
||||
- Implement proper error handling to close connections in case of exceptions
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import os
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class Configs(BaseSettings):
|
||||
"""
|
||||
MongoDB configuration settings.
|
||||
"""
|
||||
|
||||
# MongoDB connection settings
|
||||
ENGINE: str = "mongodb"
|
||||
USERNAME: str = "appuser" # Application user
|
||||
PASSWORD: str = "apppassword" # Application password
|
||||
HOST: str = "10.10.2.13"
|
||||
PORT: int = 27017
|
||||
DB: str = "appdb" # The application database
|
||||
AUTH_DB: str = "appdb" # Authentication is done against admin database
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
"""Generate the database URL.
|
||||
mongodb://{MONGO_USERNAME}:{MONGO_PASSWORD}@{MONGO_HOST}:{MONGO_PORT}/{DB}?authSource={MONGO_AUTH_DB}
|
||||
"""
|
||||
# Include the database name in the URI
|
||||
return f"{self.ENGINE}://{self.USERNAME}:{self.PASSWORD}@{self.HOST}:{self.PORT}/{self.DB}?authSource={self.DB}"
|
||||
|
||||
model_config = SettingsConfigDict(env_prefix="_MONGO_")
|
||||
|
||||
|
||||
# Create a singleton instance of the MongoDB configuration settings
|
||||
mongo_configs = Configs()
|
||||
|
|
@ -0,0 +1,373 @@
|
|||
import time
|
||||
import functools
|
||||
|
||||
from pymongo import MongoClient
|
||||
from pymongo.errors import PyMongoError
|
||||
from .config import mongo_configs
|
||||
|
||||
|
||||
def retry_operation(max_attempts=3, delay=1.0, backoff=2.0, exceptions=(PyMongoError,)):
|
||||
"""
|
||||
Decorator for retrying MongoDB operations with exponential backoff.
|
||||
|
||||
Args:
|
||||
max_attempts: Maximum number of retry attempts
|
||||
delay: Initial delay between retries in seconds
|
||||
backoff: Multiplier for delay after each retry
|
||||
exceptions: Tuple of exceptions to catch and retry
|
||||
"""
|
||||
|
||||
def decorator(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
mtries, mdelay = max_attempts, delay
|
||||
while mtries > 1:
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except exceptions as e:
|
||||
time.sleep(mdelay)
|
||||
mtries -= 1
|
||||
mdelay *= backoff
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
class MongoDBHandler:
|
||||
"""
|
||||
A MongoDB handler that provides context manager access to specific collections
|
||||
with automatic retry capability. Implements singleton pattern.
|
||||
"""
|
||||
|
||||
_instance = None
|
||||
_debug_mode = False # Set to True to enable debug mode
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
"""
|
||||
Implement singleton pattern for the handler.
|
||||
"""
|
||||
if cls._instance is None:
|
||||
cls._instance = super(MongoDBHandler, cls).__new__(cls)
|
||||
cls._instance._initialized = False
|
||||
return cls._instance
|
||||
|
||||
def __init__(self, debug_mode=False, mock_mode=False):
|
||||
"""Initialize the MongoDB handler.
|
||||
|
||||
Args:
|
||||
debug_mode: If True, use a simplified connection for debugging
|
||||
mock_mode: If True, use mock collections instead of real MongoDB connections
|
||||
"""
|
||||
if not hasattr(self, "_initialized") or not self._initialized:
|
||||
self._debug_mode = debug_mode
|
||||
self._mock_mode = mock_mode
|
||||
|
||||
if mock_mode:
|
||||
# In mock mode, we don't need a real connection string
|
||||
self.uri = "mongodb://mock:27017/mockdb"
|
||||
print("MOCK MODE: Using simulated MongoDB connections")
|
||||
elif debug_mode:
|
||||
# Use a direct connection without authentication for testing
|
||||
self.uri = f"mongodb://{mongo_configs.HOST}:{mongo_configs.PORT}/{mongo_configs.DB}"
|
||||
print(f"DEBUG MODE: Using direct connection: {self.uri}")
|
||||
else:
|
||||
# Use the configured connection string with authentication
|
||||
self.uri = mongo_configs.url
|
||||
print(f"Connecting to MongoDB: {self.uri}")
|
||||
|
||||
# Define MongoDB client options with increased timeouts for better reliability
|
||||
self.client_options = {
|
||||
"maxPoolSize": 5,
|
||||
"minPoolSize": 1,
|
||||
"maxIdleTimeMS": 60000,
|
||||
"waitQueueTimeoutMS": 5000,
|
||||
"serverSelectionTimeoutMS": 10000,
|
||||
"connectTimeoutMS": 30000,
|
||||
"socketTimeoutMS": 45000,
|
||||
"retryWrites": True,
|
||||
"retryReads": True,
|
||||
}
|
||||
self._initialized = True
|
||||
|
||||
def collection(self, collection_name: str):
|
||||
"""
|
||||
Get a context manager for a specific collection.
|
||||
|
||||
Args:
|
||||
collection_name: Name of the collection to access
|
||||
|
||||
Returns:
|
||||
A context manager for the specified collection
|
||||
"""
|
||||
return CollectionContext(self, collection_name)
|
||||
|
||||
|
||||
class CollectionContext:
|
||||
"""
|
||||
Context manager for MongoDB collections with automatic retry capability.
|
||||
"""
|
||||
|
||||
def __init__(self, db_handler: MongoDBHandler, collection_name: str):
|
||||
"""
|
||||
Initialize collection context.
|
||||
|
||||
Args:
|
||||
db_handler: Reference to the MongoDB handler
|
||||
collection_name: Name of the collection to access
|
||||
"""
|
||||
self.db_handler = db_handler
|
||||
self.collection_name = collection_name
|
||||
self.client = None
|
||||
self.collection = None
|
||||
|
||||
def __enter__(self):
|
||||
"""
|
||||
Enter context, establishing a new connection.
|
||||
|
||||
Returns:
|
||||
The MongoDB collection object with retry capabilities
|
||||
"""
|
||||
# If we're in mock mode, return a mock collection immediately
|
||||
if self.db_handler._mock_mode:
|
||||
return self._create_mock_collection()
|
||||
|
||||
try:
|
||||
# Create a new client connection
|
||||
self.client = MongoClient(
|
||||
self.db_handler.uri, **self.db_handler.client_options
|
||||
)
|
||||
|
||||
if self.db_handler._debug_mode:
|
||||
# In debug mode, we explicitly use the configured DB
|
||||
db_name = mongo_configs.DB
|
||||
print(f"DEBUG MODE: Using database '{db_name}'")
|
||||
else:
|
||||
# In normal mode, extract database name from the URI
|
||||
try:
|
||||
db_name = self.client.get_database().name
|
||||
except Exception:
|
||||
db_name = mongo_configs.DB
|
||||
print(f"Using fallback database '{db_name}'")
|
||||
|
||||
self.collection = self.client[db_name][self.collection_name]
|
||||
|
||||
# Enhance collection methods with retry capabilities
|
||||
self._add_retry_capabilities()
|
||||
|
||||
return self.collection
|
||||
except pymongo.errors.OperationFailure as e:
|
||||
if "Authentication failed" in str(e):
|
||||
print(f"MongoDB authentication error: {e}")
|
||||
print("Attempting to reconnect with direct connection...")
|
||||
|
||||
try:
|
||||
# Try a direct connection without authentication for testing
|
||||
direct_uri = f"mongodb://{mongo_configs.HOST}:{mongo_configs.PORT}/{mongo_configs.DB}"
|
||||
print(f"Trying direct connection: {direct_uri}")
|
||||
self.client = MongoClient(
|
||||
direct_uri, **self.db_handler.client_options
|
||||
)
|
||||
self.collection = self.client[mongo_configs.DB][
|
||||
self.collection_name
|
||||
]
|
||||
self._add_retry_capabilities()
|
||||
return self.collection
|
||||
except Exception as inner_e:
|
||||
print(f"Direct connection also failed: {inner_e}")
|
||||
# Fall through to mock collection creation
|
||||
else:
|
||||
print(f"MongoDB operation error: {e}")
|
||||
if self.client:
|
||||
self.client.close()
|
||||
self.client = None
|
||||
except Exception as e:
|
||||
print(f"MongoDB connection error: {e}")
|
||||
if self.client:
|
||||
self.client.close()
|
||||
self.client = None
|
||||
|
||||
return self._create_mock_collection()
|
||||
|
||||
def _create_mock_collection(self):
|
||||
"""
|
||||
Create a mock collection for testing or graceful degradation.
|
||||
This prevents the application from crashing when MongoDB is unavailable.
|
||||
|
||||
Returns:
|
||||
A mock MongoDB collection with simulated behaviors
|
||||
"""
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
if self.db_handler._mock_mode:
|
||||
print(f"MOCK MODE: Using mock collection '{self.collection_name}'")
|
||||
else:
|
||||
print(
|
||||
f"Using mock MongoDB collection '{self.collection_name}' for graceful degradation"
|
||||
)
|
||||
|
||||
# Create in-memory storage for this mock collection
|
||||
if not hasattr(self.db_handler, "_mock_storage"):
|
||||
self.db_handler._mock_storage = {}
|
||||
|
||||
if self.collection_name not in self.db_handler._mock_storage:
|
||||
self.db_handler._mock_storage[self.collection_name] = []
|
||||
|
||||
mock_collection = MagicMock()
|
||||
mock_data = self.db_handler._mock_storage[self.collection_name]
|
||||
|
||||
# Define behavior for find operations
|
||||
def mock_find(query=None, *args, **kwargs):
|
||||
# Simple implementation that returns all documents
|
||||
return mock_data
|
||||
|
||||
def mock_find_one(query=None, *args, **kwargs):
|
||||
# Simple implementation that returns the first matching document
|
||||
if not mock_data:
|
||||
return None
|
||||
return mock_data[0]
|
||||
|
||||
def mock_insert_one(document, *args, **kwargs):
|
||||
# Add _id if not present
|
||||
if "_id" not in document:
|
||||
document["_id"] = f"mock_id_{len(mock_data)}"
|
||||
mock_data.append(document)
|
||||
result = MagicMock()
|
||||
result.inserted_id = document["_id"]
|
||||
return result
|
||||
|
||||
def mock_insert_many(documents, *args, **kwargs):
|
||||
inserted_ids = []
|
||||
for doc in documents:
|
||||
result = mock_insert_one(doc)
|
||||
inserted_ids.append(result.inserted_id)
|
||||
result = MagicMock()
|
||||
result.inserted_ids = inserted_ids
|
||||
return result
|
||||
|
||||
def mock_update_one(query, update, *args, **kwargs):
|
||||
result = MagicMock()
|
||||
result.modified_count = 1
|
||||
return result
|
||||
|
||||
def mock_update_many(query, update, *args, **kwargs):
|
||||
result = MagicMock()
|
||||
result.modified_count = len(mock_data)
|
||||
return result
|
||||
|
||||
def mock_delete_one(query, *args, **kwargs):
|
||||
result = MagicMock()
|
||||
result.deleted_count = 1
|
||||
if mock_data:
|
||||
mock_data.pop(0) # Just remove the first item for simplicity
|
||||
return result
|
||||
|
||||
def mock_delete_many(query, *args, **kwargs):
|
||||
count = len(mock_data)
|
||||
mock_data.clear()
|
||||
result = MagicMock()
|
||||
result.deleted_count = count
|
||||
return result
|
||||
|
||||
def mock_count_documents(query, *args, **kwargs):
|
||||
return len(mock_data)
|
||||
|
||||
def mock_aggregate(pipeline, *args, **kwargs):
|
||||
return []
|
||||
|
||||
def mock_create_index(keys, **kwargs):
|
||||
return f"mock_index_{keys}"
|
||||
|
||||
# Assign the mock implementations
|
||||
mock_collection.find.side_effect = mock_find
|
||||
mock_collection.find_one.side_effect = mock_find_one
|
||||
mock_collection.insert_one.side_effect = mock_insert_one
|
||||
mock_collection.insert_many.side_effect = mock_insert_many
|
||||
mock_collection.update_one.side_effect = mock_update_one
|
||||
mock_collection.update_many.side_effect = mock_update_many
|
||||
mock_collection.delete_one.side_effect = mock_delete_one
|
||||
mock_collection.delete_many.side_effect = mock_delete_many
|
||||
mock_collection.count_documents.side_effect = mock_count_documents
|
||||
mock_collection.aggregate.side_effect = mock_aggregate
|
||||
mock_collection.create_index.side_effect = mock_create_index
|
||||
|
||||
# Add retry capabilities to the mock collection
|
||||
self._add_retry_capabilities_to_mock(mock_collection)
|
||||
|
||||
self.collection = mock_collection
|
||||
return self.collection
|
||||
|
||||
def _add_retry_capabilities(self):
|
||||
"""
|
||||
Add retry capabilities to all collection methods.
|
||||
"""
|
||||
# Store original methods for common operations
|
||||
original_insert_one = self.collection.insert_one
|
||||
original_insert_many = self.collection.insert_many
|
||||
original_find_one = self.collection.find_one
|
||||
original_find = self.collection.find
|
||||
original_update_one = self.collection.update_one
|
||||
original_update_many = self.collection.update_many
|
||||
original_delete_one = self.collection.delete_one
|
||||
original_delete_many = self.collection.delete_many
|
||||
original_replace_one = self.collection.replace_one
|
||||
original_count_documents = self.collection.count_documents
|
||||
|
||||
# Add retry capabilities to methods
|
||||
self.collection.insert_one = retry_operation()(original_insert_one)
|
||||
self.collection.insert_many = retry_operation()(original_insert_many)
|
||||
self.collection.find_one = retry_operation()(original_find_one)
|
||||
self.collection.find = retry_operation()(original_find)
|
||||
self.collection.update_one = retry_operation()(original_update_one)
|
||||
self.collection.update_many = retry_operation()(original_update_many)
|
||||
self.collection.delete_one = retry_operation()(original_delete_one)
|
||||
self.collection.delete_many = retry_operation()(original_delete_many)
|
||||
self.collection.replace_one = retry_operation()(original_replace_one)
|
||||
self.collection.count_documents = retry_operation()(original_count_documents)
|
||||
|
||||
def _add_retry_capabilities_to_mock(self, mock_collection):
|
||||
"""
|
||||
Add retry capabilities to mock collection methods.
|
||||
This is a simplified version that just wraps the mock methods.
|
||||
|
||||
Args:
|
||||
mock_collection: The mock collection to enhance
|
||||
"""
|
||||
# List of common MongoDB collection methods to add retry capabilities to
|
||||
methods = [
|
||||
"insert_one",
|
||||
"insert_many",
|
||||
"find_one",
|
||||
"find",
|
||||
"update_one",
|
||||
"update_many",
|
||||
"delete_one",
|
||||
"delete_many",
|
||||
"replace_one",
|
||||
"count_documents",
|
||||
"aggregate",
|
||||
]
|
||||
|
||||
# Add retry decorator to each method
|
||||
for method_name in methods:
|
||||
if hasattr(mock_collection, method_name):
|
||||
original_method = getattr(mock_collection, method_name)
|
||||
setattr(
|
||||
mock_collection,
|
||||
method_name,
|
||||
retry_operation(max_retries=1, retry_interval=0)(original_method),
|
||||
)
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
"""
|
||||
Exit context, closing the connection.
|
||||
"""
|
||||
if self.client:
|
||||
self.client.close()
|
||||
self.client = None
|
||||
self.collection = None
|
||||
|
||||
|
||||
# Create a singleton instance of the MongoDB handler
|
||||
mongo_handler = MongoDBHandler()
|
||||
|
|
@ -0,0 +1,519 @@
|
|||
# Initialize the MongoDB handler with your configuration
|
||||
from datetime import datetime
|
||||
from .database import MongoDBHandler, mongo_handler
|
||||
|
||||
|
||||
def cleanup_test_data():
|
||||
"""Clean up any test data before running tests."""
|
||||
try:
|
||||
with mongo_handler.collection("test_collection") as collection:
|
||||
collection.delete_many({})
|
||||
print("Successfully cleaned up test data")
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not clean up test data: {e}")
|
||||
print("Continuing with tests using mock data...")
|
||||
|
||||
|
||||
def test_basic_crud_operations():
|
||||
"""Test basic CRUD operations on users collection."""
|
||||
print("\nTesting basic CRUD operations...")
|
||||
try:
|
||||
with mongo_handler.collection("users") as users_collection:
|
||||
# First, clear any existing data
|
||||
users_collection.delete_many({})
|
||||
print("Cleared existing data")
|
||||
|
||||
# Insert multiple documents
|
||||
insert_result = users_collection.insert_many(
|
||||
[
|
||||
{"username": "john", "email": "john@example.com", "role": "user"},
|
||||
{"username": "jane", "email": "jane@example.com", "role": "admin"},
|
||||
{"username": "bob", "email": "bob@example.com", "role": "user"},
|
||||
]
|
||||
)
|
||||
print(f"Inserted {len(insert_result.inserted_ids)} documents")
|
||||
|
||||
# Find with multiple conditions
|
||||
admin_users = list(users_collection.find({"role": "admin"}))
|
||||
print(f"Found {len(admin_users)} admin users")
|
||||
if admin_users:
|
||||
print(f"Admin user: {admin_users[0].get('username')}")
|
||||
|
||||
# Update multiple documents
|
||||
update_result = users_collection.update_many(
|
||||
{"role": "user"}, {"$set": {"last_login": datetime.now().isoformat()}}
|
||||
)
|
||||
print(f"Updated {update_result.modified_count} documents")
|
||||
|
||||
# Delete documents
|
||||
delete_result = users_collection.delete_many({"username": "bob"})
|
||||
print(f"Deleted {delete_result.deleted_count} documents")
|
||||
|
||||
# Count remaining documents
|
||||
remaining = users_collection.count_documents({})
|
||||
print(f"Remaining documents: {remaining}")
|
||||
|
||||
# Check each condition separately
|
||||
condition1 = len(admin_users) == 1
|
||||
condition2 = admin_users and admin_users[0].get("username") == "jane"
|
||||
condition3 = update_result.modified_count == 2
|
||||
condition4 = delete_result.deleted_count == 1
|
||||
|
||||
print(f"Condition 1 (admin count): {condition1}")
|
||||
print(f"Condition 2 (admin is jane): {condition2}")
|
||||
print(f"Condition 3 (updated 2 users): {condition3}")
|
||||
print(f"Condition 4 (deleted bob): {condition4}")
|
||||
|
||||
success = condition1 and condition2 and condition3 and condition4
|
||||
print(f"Test {'passed' if success else 'failed'}")
|
||||
return success
|
||||
except Exception as e:
|
||||
print(f"Test failed with exception: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_nested_documents():
|
||||
"""Test operations with nested documents in products collection."""
|
||||
print("\nTesting nested documents...")
|
||||
try:
|
||||
with mongo_handler.collection("products") as products_collection:
|
||||
# Clear any existing data
|
||||
products_collection.delete_many({})
|
||||
print("Cleared existing data")
|
||||
|
||||
# Insert a product with nested data
|
||||
insert_result = products_collection.insert_one(
|
||||
{
|
||||
"name": "Laptop",
|
||||
"price": 999.99,
|
||||
"specs": {"cpu": "Intel i7", "ram": "16GB", "storage": "512GB SSD"},
|
||||
"in_stock": True,
|
||||
"tags": ["electronics", "computers", "laptops"],
|
||||
}
|
||||
)
|
||||
print(f"Inserted document with ID: {insert_result.inserted_id}")
|
||||
|
||||
# Find with nested field query
|
||||
laptop = products_collection.find_one({"specs.cpu": "Intel i7"})
|
||||
print(f"Found laptop: {laptop is not None}")
|
||||
if laptop:
|
||||
print(f"Laptop RAM: {laptop.get('specs', {}).get('ram')}")
|
||||
|
||||
# Update nested field
|
||||
update_result = products_collection.update_one(
|
||||
{"name": "Laptop"}, {"$set": {"specs.ram": "32GB"}}
|
||||
)
|
||||
print(f"Update modified count: {update_result.modified_count}")
|
||||
|
||||
# Verify the update
|
||||
updated_laptop = products_collection.find_one({"name": "Laptop"})
|
||||
print(f"Found updated laptop: {updated_laptop is not None}")
|
||||
if updated_laptop:
|
||||
print(f"Updated laptop specs: {updated_laptop.get('specs')}")
|
||||
if "specs" in updated_laptop:
|
||||
print(f"Updated RAM: {updated_laptop['specs'].get('ram')}")
|
||||
|
||||
# Check each condition separately
|
||||
condition1 = laptop is not None
|
||||
condition2 = laptop and laptop.get("specs", {}).get("ram") == "16GB"
|
||||
condition3 = update_result.modified_count == 1
|
||||
condition4 = (
|
||||
updated_laptop and updated_laptop.get("specs", {}).get("ram") == "32GB"
|
||||
)
|
||||
|
||||
print(f"Condition 1 (laptop found): {condition1}")
|
||||
print(f"Condition 2 (original RAM is 16GB): {condition2}")
|
||||
print(f"Condition 3 (update modified 1 doc): {condition3}")
|
||||
print(f"Condition 4 (updated RAM is 32GB): {condition4}")
|
||||
|
||||
success = condition1 and condition2 and condition3 and condition4
|
||||
print(f"Test {'passed' if success else 'failed'}")
|
||||
return success
|
||||
except Exception as e:
|
||||
print(f"Test failed with exception: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_array_operations():
|
||||
"""Test operations with arrays in orders collection."""
|
||||
print("\nTesting array operations...")
|
||||
try:
|
||||
with mongo_handler.collection("orders") as orders_collection:
|
||||
# Clear any existing data
|
||||
orders_collection.delete_many({})
|
||||
print("Cleared existing data")
|
||||
|
||||
# Insert an order with array of items
|
||||
insert_result = orders_collection.insert_one(
|
||||
{
|
||||
"order_id": "ORD001",
|
||||
"customer": "john",
|
||||
"items": [
|
||||
{"product": "Laptop", "quantity": 1},
|
||||
{"product": "Mouse", "quantity": 2},
|
||||
],
|
||||
"total": 1099.99,
|
||||
"status": "pending",
|
||||
}
|
||||
)
|
||||
print(f"Inserted order with ID: {insert_result.inserted_id}")
|
||||
|
||||
# Find orders containing specific items
|
||||
laptop_orders = list(orders_collection.find({"items.product": "Laptop"}))
|
||||
print(f"Found {len(laptop_orders)} orders with Laptop")
|
||||
|
||||
# Update array elements
|
||||
update_result = orders_collection.update_one(
|
||||
{"order_id": "ORD001"},
|
||||
{"$push": {"items": {"product": "Keyboard", "quantity": 1}}},
|
||||
)
|
||||
print(f"Update modified count: {update_result.modified_count}")
|
||||
|
||||
# Verify the update
|
||||
updated_order = orders_collection.find_one({"order_id": "ORD001"})
|
||||
print(f"Found updated order: {updated_order is not None}")
|
||||
|
||||
if updated_order:
|
||||
print(
|
||||
f"Number of items in order: {len(updated_order.get('items', []))}"
|
||||
)
|
||||
items = updated_order.get("items", [])
|
||||
if items:
|
||||
last_item = items[-1] if items else None
|
||||
print(f"Last item in order: {last_item}")
|
||||
|
||||
# Check each condition separately
|
||||
condition1 = len(laptop_orders) == 1
|
||||
condition2 = update_result.modified_count == 1
|
||||
condition3 = updated_order and len(updated_order.get("items", [])) == 3
|
||||
condition4 = (
|
||||
updated_order
|
||||
and updated_order.get("items", [])
|
||||
and updated_order["items"][-1].get("product") == "Keyboard"
|
||||
)
|
||||
|
||||
print(f"Condition 1 (found 1 laptop order): {condition1}")
|
||||
print(f"Condition 2 (update modified 1 doc): {condition2}")
|
||||
print(f"Condition 3 (order has 3 items): {condition3}")
|
||||
print(f"Condition 4 (last item is keyboard): {condition4}")
|
||||
|
||||
success = condition1 and condition2 and condition3 and condition4
|
||||
print(f"Test {'passed' if success else 'failed'}")
|
||||
return success
|
||||
except Exception as e:
|
||||
print(f"Test failed with exception: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_aggregation():
|
||||
"""Test aggregation operations on sales collection."""
|
||||
print("\nTesting aggregation operations...")
|
||||
try:
|
||||
with mongo_handler.collection("sales") as sales_collection:
|
||||
# Clear any existing data
|
||||
sales_collection.delete_many({})
|
||||
print("Cleared existing data")
|
||||
|
||||
# Insert sample sales data
|
||||
insert_result = sales_collection.insert_many(
|
||||
[
|
||||
{"product": "Laptop", "amount": 999.99, "date": datetime.now()},
|
||||
{"product": "Mouse", "amount": 29.99, "date": datetime.now()},
|
||||
{"product": "Keyboard", "amount": 59.99, "date": datetime.now()},
|
||||
]
|
||||
)
|
||||
print(f"Inserted {len(insert_result.inserted_ids)} sales documents")
|
||||
|
||||
# Calculate total sales by product - use a simpler aggregation pipeline
|
||||
pipeline = [
|
||||
{"$match": {}}, # Match all documents
|
||||
{"$group": {"_id": "$product", "total": {"$sum": "$amount"}}},
|
||||
]
|
||||
|
||||
# Execute the aggregation
|
||||
sales_summary = list(sales_collection.aggregate(pipeline))
|
||||
print(f"Aggregation returned {len(sales_summary)} results")
|
||||
|
||||
# Print the results for debugging
|
||||
for item in sales_summary:
|
||||
print(f"Product: {item.get('_id')}, Total: {item.get('total')}")
|
||||
|
||||
# Check each condition separately
|
||||
condition1 = len(sales_summary) == 3
|
||||
condition2 = any(
|
||||
item.get("_id") == "Laptop"
|
||||
and abs(item.get("total", 0) - 999.99) < 0.01
|
||||
for item in sales_summary
|
||||
)
|
||||
condition3 = any(
|
||||
item.get("_id") == "Mouse" and abs(item.get("total", 0) - 29.99) < 0.01
|
||||
for item in sales_summary
|
||||
)
|
||||
condition4 = any(
|
||||
item.get("_id") == "Keyboard"
|
||||
and abs(item.get("total", 0) - 59.99) < 0.01
|
||||
for item in sales_summary
|
||||
)
|
||||
|
||||
print(f"Condition 1 (3 summary items): {condition1}")
|
||||
print(f"Condition 2 (laptop total correct): {condition2}")
|
||||
print(f"Condition 3 (mouse total correct): {condition3}")
|
||||
print(f"Condition 4 (keyboard total correct): {condition4}")
|
||||
|
||||
success = condition1 and condition2 and condition3 and condition4
|
||||
print(f"Test {'passed' if success else 'failed'}")
|
||||
return success
|
||||
except Exception as e:
|
||||
print(f"Test failed with exception: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_index_operations():
|
||||
"""Test index creation and unique constraints."""
|
||||
print("\nTesting index operations...")
|
||||
try:
|
||||
with mongo_handler.collection("test_collection") as collection:
|
||||
# Create indexes
|
||||
collection.create_index("email", unique=True)
|
||||
collection.create_index([("username", 1), ("role", 1)])
|
||||
|
||||
# Insert initial document
|
||||
collection.insert_one(
|
||||
{"username": "test_user", "email": "test@example.com"}
|
||||
)
|
||||
|
||||
# Try to insert duplicate email (should fail)
|
||||
try:
|
||||
collection.insert_one(
|
||||
{"username": "test_user2", "email": "test@example.com"}
|
||||
)
|
||||
success = False # Should not reach here
|
||||
except Exception:
|
||||
success = True
|
||||
|
||||
print(f"Test {'passed' if success else 'failed'}")
|
||||
return success
|
||||
except Exception as e:
|
||||
print(f"Test failed with exception: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_complex_queries():
|
||||
"""Test complex queries with multiple conditions."""
|
||||
print("\nTesting complex queries...")
|
||||
try:
|
||||
with mongo_handler.collection("products") as products_collection:
|
||||
# Insert test data
|
||||
products_collection.insert_many(
|
||||
[
|
||||
{
|
||||
"name": "Expensive Laptop",
|
||||
"price": 999.99,
|
||||
"tags": ["electronics", "computers"],
|
||||
"in_stock": True,
|
||||
},
|
||||
{
|
||||
"name": "Cheap Mouse",
|
||||
"price": 29.99,
|
||||
"tags": ["electronics", "peripherals"],
|
||||
"in_stock": True,
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
# Find products with price range and specific tags
|
||||
expensive_electronics = list(
|
||||
products_collection.find(
|
||||
{
|
||||
"price": {"$gt": 500},
|
||||
"tags": {"$in": ["electronics"]},
|
||||
"in_stock": True,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
# Update with multiple conditions - split into separate operations for better compatibility
|
||||
# First set the discount
|
||||
products_collection.update_many(
|
||||
{"price": {"$lt": 100}, "in_stock": True}, {"$set": {"discount": 0.1}}
|
||||
)
|
||||
|
||||
# Then update the price
|
||||
update_result = products_collection.update_many(
|
||||
{"price": {"$lt": 100}, "in_stock": True}, {"$inc": {"price": -10}}
|
||||
)
|
||||
|
||||
# Verify the update
|
||||
updated_product = products_collection.find_one({"name": "Cheap Mouse"})
|
||||
|
||||
# Print debug information
|
||||
print(f"Found expensive electronics: {len(expensive_electronics)}")
|
||||
if expensive_electronics:
|
||||
print(
|
||||
f"First expensive product: {expensive_electronics[0].get('name')}"
|
||||
)
|
||||
print(f"Modified count: {update_result.modified_count}")
|
||||
if updated_product:
|
||||
print(f"Updated product price: {updated_product.get('price')}")
|
||||
print(f"Updated product discount: {updated_product.get('discount')}")
|
||||
|
||||
# More flexible verification with approximate float comparison
|
||||
success = (
|
||||
len(expensive_electronics) >= 1
|
||||
and expensive_electronics[0].get("name")
|
||||
in ["Expensive Laptop", "Laptop"]
|
||||
and update_result.modified_count >= 1
|
||||
and updated_product is not None
|
||||
and updated_product.get("discount", 0)
|
||||
> 0 # Just check that discount exists and is positive
|
||||
)
|
||||
print(f"Test {'passed' if success else 'failed'}")
|
||||
return success
|
||||
except Exception as e:
|
||||
print(f"Test failed with exception: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def run_concurrent_operation_test(num_threads=100):
|
||||
"""Run a simple operation in multiple threads to verify connection pooling."""
|
||||
import threading
|
||||
import time
|
||||
import uuid
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
print(f"\nStarting concurrent operation test with {num_threads} threads...")
|
||||
|
||||
# Results tracking
|
||||
results = {"passed": 0, "failed": 0, "errors": []}
|
||||
results_lock = threading.Lock()
|
||||
|
||||
def worker(thread_id):
|
||||
# Create a unique collection name for this thread
|
||||
collection_name = f"concurrent_test_{thread_id}"
|
||||
|
||||
try:
|
||||
# Generate unique data for this thread
|
||||
unique_id = str(uuid.uuid4())
|
||||
|
||||
with mongo_handler.collection(collection_name) as collection:
|
||||
# Insert a document
|
||||
collection.insert_one(
|
||||
{
|
||||
"thread_id": thread_id,
|
||||
"uuid": unique_id,
|
||||
"timestamp": time.time(),
|
||||
}
|
||||
)
|
||||
|
||||
# Find the document
|
||||
doc = collection.find_one({"thread_id": thread_id})
|
||||
|
||||
# Update the document
|
||||
collection.update_one(
|
||||
{"thread_id": thread_id}, {"$set": {"updated": True}}
|
||||
)
|
||||
|
||||
# Verify update
|
||||
updated_doc = collection.find_one({"thread_id": thread_id})
|
||||
|
||||
# Clean up
|
||||
collection.delete_many({"thread_id": thread_id})
|
||||
|
||||
success = (
|
||||
doc is not None
|
||||
and updated_doc is not None
|
||||
and updated_doc.get("updated") is True
|
||||
)
|
||||
|
||||
# Update results with thread safety
|
||||
with results_lock:
|
||||
if success:
|
||||
results["passed"] += 1
|
||||
else:
|
||||
results["failed"] += 1
|
||||
results["errors"].append(f"Thread {thread_id} operation failed")
|
||||
except Exception as e:
|
||||
with results_lock:
|
||||
results["failed"] += 1
|
||||
results["errors"].append(f"Thread {thread_id} exception: {str(e)}")
|
||||
|
||||
# Create and start threads using a thread pool
|
||||
start_time = time.time()
|
||||
with ThreadPoolExecutor(max_workers=num_threads) as executor:
|
||||
futures = [executor.submit(worker, i) for i in range(num_threads)]
|
||||
|
||||
# Calculate execution time
|
||||
execution_time = time.time() - start_time
|
||||
|
||||
# Print results
|
||||
print(f"\nConcurrent Operation Test Results:")
|
||||
print(f"Total threads: {num_threads}")
|
||||
print(f"Passed: {results['passed']}")
|
||||
print(f"Failed: {results['failed']}")
|
||||
print(f"Execution time: {execution_time:.2f} seconds")
|
||||
print(f"Operations per second: {num_threads / execution_time:.2f}")
|
||||
|
||||
if results["failed"] > 0:
|
||||
print("\nErrors:")
|
||||
for error in results["errors"][
|
||||
:10
|
||||
]: # Show only first 10 errors to avoid flooding output
|
||||
print(f"- {error}")
|
||||
if len(results["errors"]) > 10:
|
||||
print(f"- ... and {len(results['errors']) - 10} more errors")
|
||||
|
||||
return results["failed"] == 0
|
||||
|
||||
|
||||
def run_all_tests():
|
||||
"""Run all MongoDB tests and report results."""
|
||||
print("Starting MongoDB tests...")
|
||||
|
||||
# Clean up any existing test data before starting
|
||||
cleanup_test_data()
|
||||
|
||||
tests = [
|
||||
test_basic_crud_operations,
|
||||
test_nested_documents,
|
||||
test_array_operations,
|
||||
test_aggregation,
|
||||
test_index_operations,
|
||||
test_complex_queries,
|
||||
]
|
||||
|
||||
passed_list, not_passed_list = [], []
|
||||
passed, failed = 0, 0
|
||||
|
||||
for test in tests:
|
||||
# Clean up test data before each test
|
||||
cleanup_test_data()
|
||||
try:
|
||||
if test():
|
||||
passed += 1
|
||||
passed_list.append(f"Test {test.__name__} passed")
|
||||
else:
|
||||
failed += 1
|
||||
not_passed_list.append(f"Test {test.__name__} failed")
|
||||
except Exception as e:
|
||||
print(f"Test {test.__name__} failed with exception: {e}")
|
||||
failed += 1
|
||||
not_passed_list.append(f"Test {test.__name__} failed")
|
||||
|
||||
print(f"\nTest Results: {passed} passed, {failed} failed")
|
||||
print("Passed Tests:")
|
||||
print("\n".join(passed_list))
|
||||
print("Failed Tests:")
|
||||
print("\n".join(not_passed_list))
|
||||
|
||||
return passed, failed
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
mongo_handler = MongoDBHandler()
|
||||
|
||||
# Run standard tests first
|
||||
passed, failed = run_all_tests()
|
||||
|
||||
# If all tests pass, run the concurrent operation test
|
||||
if failed == 0:
|
||||
run_concurrent_operation_test(10000)
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
"""
|
||||
Test script for MongoDB handler with a local MongoDB instance.
|
||||
"""
|
||||
|
||||
import os
|
||||
from datetime import datetime
|
||||
from .database import MongoDBHandler, CollectionContext
|
||||
|
||||
# Create a custom handler class for local testing
|
||||
class LocalMongoDBHandler(MongoDBHandler):
|
||||
"""A MongoDB handler for local testing without authentication."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize with a direct MongoDB URI."""
|
||||
self._initialized = False
|
||||
self.uri = "mongodb://localhost:27017/test"
|
||||
self.client_options = {
|
||||
"maxPoolSize": 5,
|
||||
"minPoolSize": 2,
|
||||
"maxIdleTimeMS": 30000,
|
||||
"waitQueueTimeoutMS": 2000,
|
||||
"serverSelectionTimeoutMS": 5000,
|
||||
}
|
||||
self._initialized = True
|
||||
|
||||
|
||||
# Create a custom handler for local testing
|
||||
def create_local_handler():
|
||||
"""Create a MongoDB handler for local testing."""
|
||||
# Create a fresh instance with direct MongoDB URI
|
||||
handler = LocalMongoDBHandler()
|
||||
return handler
|
||||
|
||||
|
||||
def test_connection_monitoring():
|
||||
"""Test connection monitoring with the MongoDB handler."""
|
||||
print("\nTesting connection monitoring...")
|
||||
|
||||
# Create a local handler
|
||||
local_handler = create_local_handler()
|
||||
|
||||
# Add connection tracking to the handler
|
||||
local_handler._open_connections = 0
|
||||
|
||||
# Modify the CollectionContext class to track connections
|
||||
original_enter = CollectionContext.__enter__
|
||||
original_exit = CollectionContext.__exit__
|
||||
|
||||
def tracked_enter(self):
|
||||
result = original_enter(self)
|
||||
self.db_handler._open_connections += 1
|
||||
print(f"Connection opened. Total open: {self.db_handler._open_connections}")
|
||||
return result
|
||||
|
||||
def tracked_exit(self, exc_type, exc_val, exc_tb):
|
||||
self.db_handler._open_connections -= 1
|
||||
print(f"Connection closed. Total open: {self.db_handler._open_connections}")
|
||||
return original_exit(self, exc_type, exc_val, exc_tb)
|
||||
|
||||
# Apply the tracking methods
|
||||
CollectionContext.__enter__ = tracked_enter
|
||||
CollectionContext.__exit__ = tracked_exit
|
||||
|
||||
try:
|
||||
# Test with multiple operations
|
||||
for i in range(3):
|
||||
print(f"\nTest iteration {i+1}:")
|
||||
try:
|
||||
with local_handler.collection("test_collection") as collection:
|
||||
# Try a simple operation
|
||||
try:
|
||||
collection.find_one({})
|
||||
print("Operation succeeded")
|
||||
except Exception as e:
|
||||
print(f"Operation failed: {e}")
|
||||
except Exception as e:
|
||||
print(f"Connection failed: {e}")
|
||||
|
||||
# Final connection count
|
||||
print(f"\nFinal open connections: {local_handler._open_connections}")
|
||||
if local_handler._open_connections == 0:
|
||||
print("✅ All connections were properly closed")
|
||||
else:
|
||||
print(f"❌ {local_handler._open_connections} connections remain open")
|
||||
|
||||
finally:
|
||||
# Restore original methods
|
||||
CollectionContext.__enter__ = original_enter
|
||||
CollectionContext.__exit__ = original_exit
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_connection_monitoring()
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class Configs(BaseSettings):
|
||||
"""
|
||||
Postgresql configuration settings.
|
||||
"""
|
||||
|
||||
DB: str = ""
|
||||
USER: str = ""
|
||||
PASSWORD: str = ""
|
||||
HOST: str = ""
|
||||
PORT: int = 0
|
||||
ENGINE: str = "postgresql+psycopg2"
|
||||
POOL_PRE_PING: bool = True
|
||||
POOL_SIZE: int = 20
|
||||
MAX_OVERFLOW: int = 10
|
||||
POOL_RECYCLE: int = 600
|
||||
POOL_TIMEOUT: int = 30
|
||||
ECHO: bool = True
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
"""Generate the database URL."""
|
||||
return f"{self.ENGINE}://{self.USER}:{self.PASSWORD}@{self.HOST}:{self.PORT}/{self.DB}"
|
||||
|
||||
model_config = SettingsConfigDict(env_prefix="POSTGRES_")
|
||||
|
||||
|
||||
# singleton instance of the POSTGRESQL configuration settings
|
||||
postgres_configs = Configs()
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
from contextlib import contextmanager
|
||||
from functools import lru_cache
|
||||
from typing import Generator
|
||||
from api_controllers.postgres.config import postgres_configs
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import declarative_base, sessionmaker, scoped_session, Session
|
||||
|
||||
|
||||
# Configure the database engine with proper pooling
|
||||
engine = create_engine(
|
||||
postgres_configs.url,
|
||||
pool_pre_ping=True,
|
||||
pool_size=10, # Reduced from 20 to better match your CPU cores
|
||||
max_overflow=5, # Reduced from 10 to prevent too many connections
|
||||
pool_recycle=600, # Keep as is
|
||||
pool_timeout=30, # Keep as is
|
||||
echo=False, # Consider setting to False in production
|
||||
)
|
||||
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
# Create a cached session factory
|
||||
@lru_cache()
|
||||
def get_session_factory() -> scoped_session:
|
||||
"""Create a thread-safe session factory."""
|
||||
session_local = sessionmaker(
|
||||
bind=engine,
|
||||
autocommit=False,
|
||||
autoflush=False,
|
||||
expire_on_commit=True, # Prevent expired object issues
|
||||
)
|
||||
return scoped_session(session_local)
|
||||
|
||||
|
||||
# Get database session with proper connection management
|
||||
@contextmanager
|
||||
def get_db() -> Generator[Session, None, None]:
|
||||
"""Get database session with proper connection management.
|
||||
|
||||
This context manager ensures:
|
||||
- Proper connection pooling
|
||||
- Session cleanup
|
||||
- Connection return to pool
|
||||
- Thread safety
|
||||
|
||||
Yields:
|
||||
Session: SQLAlchemy session object
|
||||
"""
|
||||
|
||||
session_factory = get_session_factory()
|
||||
session = session_factory()
|
||||
try:
|
||||
yield session
|
||||
session.commit()
|
||||
except Exception:
|
||||
session.rollback()
|
||||
raise
|
||||
finally:
|
||||
session.close()
|
||||
session_factory.remove() # Clean up the session from the registry
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
import arrow
|
||||
|
||||
from sqlalchemy import Column, Integer, String, Float, ForeignKey, UUID, TIMESTAMP, Boolean, SmallInteger, Numeric, func, text
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from sqlalchemy_mixins.serialize import SerializeMixin
|
||||
from sqlalchemy_mixins.repr import ReprMixin
|
||||
from sqlalchemy_mixins.smartquery import SmartQueryMixin
|
||||
from sqlalchemy_mixins.activerecord import ActiveRecordMixin
|
||||
from api_controllers.postgres.engine import get_db, Base
|
||||
|
||||
|
||||
class BasicMixin(
|
||||
Base,
|
||||
ActiveRecordMixin,
|
||||
SerializeMixin,
|
||||
ReprMixin,
|
||||
SmartQueryMixin,
|
||||
):
|
||||
|
||||
__abstract__ = True
|
||||
__repr__ = ReprMixin.__repr__
|
||||
|
||||
@classmethod
|
||||
def new_session(cls):
|
||||
"""Get database session."""
|
||||
return get_db()
|
||||
|
||||
|
||||
class CrudMixin(BasicMixin):
|
||||
"""
|
||||
Base mixin providing CRUD operations and common fields for PostgreSQL models.
|
||||
|
||||
Features:
|
||||
- Automatic timestamps (created_at, updated_at)
|
||||
- Soft delete capability
|
||||
- User tracking (created_by, updated_by)
|
||||
- Data serialization
|
||||
- Multi-language support
|
||||
"""
|
||||
|
||||
__abstract__ = True
|
||||
|
||||
# Primary and reference fields
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
uu_id: Mapped[str] = mapped_column(
|
||||
UUID,
|
||||
server_default=text("gen_random_uuid()"),
|
||||
index=True,
|
||||
unique=True,
|
||||
comment="Unique identifier UUID",
|
||||
)
|
||||
|
||||
# Common timestamp fields for all models
|
||||
expiry_starts: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True),
|
||||
server_default=func.now(),
|
||||
comment="Record validity start timestamp",
|
||||
)
|
||||
expiry_ends: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True),
|
||||
default=str(arrow.get("2099-12-31")),
|
||||
server_default=func.now(),
|
||||
comment="Record validity end timestamp",
|
||||
)
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True),
|
||||
server_default=func.now(),
|
||||
nullable=False,
|
||||
index=True,
|
||||
comment="Record creation timestamp",
|
||||
)
|
||||
updated_at: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True),
|
||||
server_default=func.now(),
|
||||
onupdate=func.now(),
|
||||
nullable=False,
|
||||
index=True,
|
||||
comment="Last update timestamp",
|
||||
)
|
||||
|
||||
|
||||
class CrudCollection(CrudMixin):
|
||||
"""
|
||||
Full-featured model class with all common fields.
|
||||
|
||||
Includes:
|
||||
- UUID and reference ID
|
||||
- Timestamps
|
||||
- User tracking
|
||||
- Confirmation status
|
||||
- Soft delete
|
||||
- Notification flags
|
||||
"""
|
||||
|
||||
__abstract__ = True
|
||||
__repr__ = ReprMixin.__repr__
|
||||
|
||||
# Outer reference fields
|
||||
ref_id: Mapped[str] = mapped_column(
|
||||
String(100), nullable=True, index=True, comment="External reference ID"
|
||||
)
|
||||
replication_id: Mapped[int] = mapped_column(
|
||||
SmallInteger, server_default="0", comment="Replication identifier"
|
||||
)
|
||||
|
||||
# Cryptographic and user tracking
|
||||
cryp_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, index=True, comment="Cryptographic UUID"
|
||||
)
|
||||
|
||||
# Token fields of modification
|
||||
created_credentials_token: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Created Credentials token"
|
||||
)
|
||||
updated_credentials_token: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Updated Credentials token"
|
||||
)
|
||||
confirmed_credentials_token: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Confirmed Credentials token"
|
||||
)
|
||||
|
||||
# Status flags
|
||||
is_confirmed: Mapped[bool] = mapped_column(
|
||||
Boolean, server_default="0", comment="Record confirmation status"
|
||||
)
|
||||
deleted: Mapped[bool] = mapped_column(
|
||||
Boolean, server_default="0", comment="Soft delete flag"
|
||||
)
|
||||
active: Mapped[bool] = mapped_column(
|
||||
Boolean, server_default="1", comment="Record active status"
|
||||
)
|
||||
is_notification_send: Mapped[bool] = mapped_column(
|
||||
Boolean, server_default="0", comment="Notification sent flag"
|
||||
)
|
||||
is_email_send: Mapped[bool] = mapped_column(
|
||||
Boolean, server_default="0", comment="Email sent flag"
|
||||
)
|
||||
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
# Redis Pub/Sub Chain Implementation
|
||||
|
||||
This module implements a chain of services communicating through Redis Pub/Sub channels. Each service in the chain subscribes to the previous service's channel and publishes to its own channel, creating a processing pipeline.
|
||||
|
||||
## Architecture
|
||||
|
||||
The implementation follows a simple chain pattern:
|
||||
|
||||
```
|
||||
READER → PROCESSOR → WRITER
|
||||
```
|
||||
|
||||
- **READER**: Generates mock data with a "red" stage and publishes to `chain:reader`
|
||||
- **PROCESSOR**: Subscribes to `chain:reader`, processes messages with "red" stage, updates stage to "processed", and publishes to `chain:processor`
|
||||
- **WRITER**: Subscribes to `chain:processor`, processes messages with "processed" stage, updates stage to "completed", and publishes to `chain:writer`
|
||||
|
||||
## Message Flow
|
||||
|
||||
Each message flows through the chain with a stage attribute that determines how it's processed:
|
||||
|
||||
1. READER generates a message with `stage="red"`
|
||||
2. PROCESSOR receives the message, checks if `stage="red"`, processes it, and sets `stage="processed"`
|
||||
3. WRITER receives the message, checks if `stage="processed"`, processes it, and sets `stage="completed"`
|
||||
|
||||
## Performance
|
||||
|
||||
The implementation includes timing information to track how long messages take to flow through the entire chain. Sample output:
|
||||
|
||||
```
|
||||
[READER] 1745176466.132082 | Published UUID: 74cf2312-25ec-4da8-bc0a-521b6ccd5206
|
||||
[PROCESSOR] 1745176466.132918 | Received UUID: 74cf2312-25ec-4da8-bc0a-521b6ccd5206 | Published UUID: 74cf2312-25ec-4da8-bc0a-521b6ccd5206
|
||||
[WRITER] 1745176466.133097 | Received UUID: 74cf2312-25ec-4da8-bc0a-521b6ccd5206 | Published UUID: 74cf2312-25ec-4da8-bc0a-521b6ccd5206 | Elapsed: 1.83ms
|
||||
[READER] 1745176468.133018 | Published UUID: 2ffd217f-650f-4e10-bc16-317adcf7a59a
|
||||
[PROCESSOR] 1745176468.133792 | Received UUID: 2ffd217f-650f-4e10-bc16-317adcf7a59a | Published UUID: 2ffd217f-650f-4e10-bc16-317adcf7a59a
|
||||
[WRITER] 1745176468.134001 | Received UUID: 2ffd217f-650f-4e10-bc16-317adcf7a59a | Published UUID: 2ffd217f-650f-4e10-bc16-317adcf7a59a | Elapsed: 1.76ms
|
||||
[READER] 1745176470.133841 | Published UUID: 87e1f3af-c6c2-4fa5-9a65-57e7327d3989
|
||||
[PROCESSOR] 1745176470.134623 | Received UUID: 87e1f3af-c6c2-4fa5-9a65-57e7327d3989 | Published UUID: 87e1f3af-c6c2-4fa5-9a65-57e7327d3989
|
||||
[WRITER] 1745176470.134861 | Received UUID: 87e1f3af-c6c2-4fa5-9a65-57e7327d3989 | Published UUID: 87e1f3af-c6c2-4fa5-9a65-57e7327d3989 | Elapsed: 1.68ms
|
||||
```
|
||||
|
||||
The elapsed time shows the total time from when the READER publishes a message until the WRITER completes processing it. In the samples above, the end-to-end processing time ranges from 1.68ms to 1.83ms.
|
||||
|
||||
## Usage
|
||||
|
||||
To run the demonstration:
|
||||
|
||||
```bash
|
||||
python -m Controllers.Redis.Broadcast.implementations
|
||||
```
|
||||
|
||||
This will start all three services in the chain and begin processing messages. Press Ctrl+C to stop the demonstration.
|
||||
|
||||
## Implementation Details
|
||||
|
||||
The implementation uses:
|
||||
|
||||
1. A singleton Redis Pub/Sub handler with publisher and subscriber capabilities
|
||||
2. Thread-based message processing
|
||||
3. JSON serialization for message passing
|
||||
4. Stage-based message processing to track progress through the chain
|
||||
5. Timing information to measure performance
|
||||
|
||||
Each service in the chain follows these steps:
|
||||
1. Subscribe to the appropriate channel
|
||||
2. Define a message handler function
|
||||
3. Process incoming messages based on their stage
|
||||
4. Publish processed messages to the next channel in the chain
|
||||
|
|
@ -0,0 +1,248 @@
|
|||
import json
|
||||
from typing import Optional, Dict, Any, List, Callable, Union
|
||||
from threading import Thread
|
||||
|
||||
from Controllers.Redis.connection import redis_cli
|
||||
from Controllers.Redis.response import RedisResponse
|
||||
|
||||
|
||||
class RedisPublisher:
|
||||
"""Redis Publisher class for broadcasting messages to channels."""
|
||||
|
||||
def __init__(self, redis_client=redis_cli):
|
||||
self.redis_client = redis_client
|
||||
|
||||
def publish(self, channel: str, message: Union[Dict, List, str]) -> RedisResponse:
|
||||
"""Publish a message to a Redis channel.
|
||||
|
||||
Args:
|
||||
channel: The channel to publish to
|
||||
message: The message to publish (will be JSON serialized if dict or list)
|
||||
|
||||
Returns:
|
||||
RedisResponse with status and message
|
||||
"""
|
||||
try:
|
||||
# Convert dict/list to JSON string if needed
|
||||
if isinstance(message, (dict, list)):
|
||||
message = json.dumps(message)
|
||||
|
||||
# Publish the message
|
||||
recipient_count = self.redis_client.publish(channel, message)
|
||||
|
||||
return RedisResponse(
|
||||
status=True,
|
||||
message=f"Message published successfully to {channel}.",
|
||||
data={"recipients": recipient_count},
|
||||
)
|
||||
except Exception as e:
|
||||
return RedisResponse(
|
||||
status=False,
|
||||
message=f"Failed to publish message to {channel}.",
|
||||
error=str(e),
|
||||
)
|
||||
|
||||
|
||||
class RedisSubscriber:
|
||||
"""Redis Subscriber class for listening to channels."""
|
||||
|
||||
def __init__(self, redis_client=redis_cli):
|
||||
self.redis_client = redis_client
|
||||
self.pubsub = self.redis_client.pubsub()
|
||||
self.active_threads = {}
|
||||
|
||||
def subscribe(
|
||||
self, channel: str, callback: Callable[[Dict], None]
|
||||
) -> RedisResponse:
|
||||
"""Subscribe to a Redis channel with a callback function.
|
||||
|
||||
Args:
|
||||
channel: The channel to subscribe to
|
||||
callback: Function to call when a message is received
|
||||
|
||||
Returns:
|
||||
RedisResponse with status and message
|
||||
"""
|
||||
try:
|
||||
# Subscribe to the channel
|
||||
self.pubsub.subscribe(**{channel: self._message_handler(callback)})
|
||||
|
||||
return RedisResponse(
|
||||
status=True, message=f"Successfully subscribed to {channel}."
|
||||
)
|
||||
except Exception as e:
|
||||
return RedisResponse(
|
||||
status=False, message=f"Failed to subscribe to {channel}.", error=str(e)
|
||||
)
|
||||
|
||||
def psubscribe(
|
||||
self, pattern: str, callback: Callable[[Dict], None]
|
||||
) -> RedisResponse:
|
||||
"""Subscribe to Redis channels matching a pattern.
|
||||
|
||||
Args:
|
||||
pattern: The pattern to subscribe to (e.g., 'user.*')
|
||||
callback: Function to call when a message is received
|
||||
|
||||
Returns:
|
||||
RedisResponse with status and message
|
||||
"""
|
||||
try:
|
||||
# Subscribe to the pattern
|
||||
self.pubsub.psubscribe(**{pattern: self._message_handler(callback)})
|
||||
|
||||
return RedisResponse(
|
||||
status=True, message=f"Successfully pattern-subscribed to {pattern}."
|
||||
)
|
||||
except Exception as e:
|
||||
return RedisResponse(
|
||||
status=False,
|
||||
message=f"Failed to pattern-subscribe to {pattern}.",
|
||||
error=str(e),
|
||||
)
|
||||
|
||||
def _message_handler(self, callback: Callable[[Dict], None]):
|
||||
"""Create a message handler function for the subscription."""
|
||||
|
||||
def handler(message):
|
||||
# Skip subscription confirmation messages
|
||||
if message["type"] in ("subscribe", "psubscribe"):
|
||||
return
|
||||
|
||||
# Parse JSON if the message is a JSON string
|
||||
data = message["data"]
|
||||
if isinstance(data, bytes):
|
||||
data = data.decode("utf-8")
|
||||
try:
|
||||
data = json.loads(data)
|
||||
except json.JSONDecodeError:
|
||||
# Not JSON, keep as is
|
||||
pass
|
||||
|
||||
# Call the callback with the message data
|
||||
callback(
|
||||
{
|
||||
"channel": (
|
||||
message.get("channel", b"").decode("utf-8")
|
||||
if isinstance(message.get("channel", b""), bytes)
|
||||
else message.get("channel", "")
|
||||
),
|
||||
"pattern": (
|
||||
message.get("pattern", b"").decode("utf-8")
|
||||
if isinstance(message.get("pattern", b""), bytes)
|
||||
else message.get("pattern", "")
|
||||
),
|
||||
"data": data,
|
||||
}
|
||||
)
|
||||
|
||||
return handler
|
||||
|
||||
def start_listening(self, in_thread: bool = True) -> RedisResponse:
|
||||
"""Start listening for messages on subscribed channels.
|
||||
|
||||
Args:
|
||||
in_thread: If True, start listening in a separate thread
|
||||
|
||||
Returns:
|
||||
RedisResponse with status and message
|
||||
"""
|
||||
try:
|
||||
if in_thread:
|
||||
thread = Thread(target=self._listen_thread, daemon=True)
|
||||
thread.start()
|
||||
self.active_threads["listener"] = thread
|
||||
return RedisResponse(
|
||||
status=True, message="Listening thread started successfully."
|
||||
)
|
||||
else:
|
||||
# This will block the current thread
|
||||
self._listen_thread()
|
||||
return RedisResponse(
|
||||
status=True, message="Listening started successfully (blocking)."
|
||||
)
|
||||
except Exception as e:
|
||||
return RedisResponse(
|
||||
status=False, message="Failed to start listening.", error=str(e)
|
||||
)
|
||||
|
||||
def _listen_thread(self):
|
||||
"""Thread function for listening to messages."""
|
||||
self.pubsub.run_in_thread(sleep_time=0.01)
|
||||
|
||||
def stop_listening(self) -> RedisResponse:
|
||||
"""Stop listening for messages."""
|
||||
try:
|
||||
self.pubsub.close()
|
||||
return RedisResponse(status=True, message="Successfully stopped listening.")
|
||||
except Exception as e:
|
||||
return RedisResponse(
|
||||
status=False, message="Failed to stop listening.", error=str(e)
|
||||
)
|
||||
|
||||
def unsubscribe(self, channel: Optional[str] = None) -> RedisResponse:
|
||||
"""Unsubscribe from a channel or all channels.
|
||||
|
||||
Args:
|
||||
channel: The channel to unsubscribe from, or None for all channels
|
||||
|
||||
Returns:
|
||||
RedisResponse with status and message
|
||||
"""
|
||||
try:
|
||||
if channel:
|
||||
self.pubsub.unsubscribe(channel)
|
||||
message = f"Successfully unsubscribed from {channel}."
|
||||
else:
|
||||
self.pubsub.unsubscribe()
|
||||
message = "Successfully unsubscribed from all channels."
|
||||
|
||||
return RedisResponse(status=True, message=message)
|
||||
except Exception as e:
|
||||
return RedisResponse(
|
||||
status=False,
|
||||
message=f"Failed to unsubscribe from {'channel' if channel else 'all channels'}.",
|
||||
error=str(e),
|
||||
)
|
||||
|
||||
def punsubscribe(self, pattern: Optional[str] = None) -> RedisResponse:
|
||||
"""Unsubscribe from a pattern or all patterns.
|
||||
|
||||
Args:
|
||||
pattern: The pattern to unsubscribe from, or None for all patterns
|
||||
|
||||
Returns:
|
||||
RedisResponse with status and message
|
||||
"""
|
||||
try:
|
||||
if pattern:
|
||||
self.pubsub.punsubscribe(pattern)
|
||||
message = f"Successfully unsubscribed from pattern {pattern}."
|
||||
else:
|
||||
self.pubsub.punsubscribe()
|
||||
message = "Successfully unsubscribed from all patterns."
|
||||
|
||||
return RedisResponse(status=True, message=message)
|
||||
except Exception as e:
|
||||
return RedisResponse(
|
||||
status=False,
|
||||
message=f"Failed to unsubscribe from {'pattern' if pattern else 'all patterns'}.",
|
||||
error=str(e),
|
||||
)
|
||||
|
||||
|
||||
class RedisPubSub:
|
||||
"""Singleton class that provides both publisher and subscriber functionality."""
|
||||
|
||||
_instance = None
|
||||
|
||||
def __new__(cls):
|
||||
if cls._instance is None:
|
||||
cls._instance = super(RedisPubSub, cls).__new__(cls)
|
||||
cls._instance.publisher = RedisPublisher()
|
||||
cls._instance.subscriber = RedisSubscriber()
|
||||
return cls._instance
|
||||
|
||||
|
||||
# Create a singleton instance
|
||||
redis_pubsub = RedisPubSub()
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
import json
|
||||
import time
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from threading import Thread
|
||||
|
||||
from Controllers.Redis.Broadcast.actions import redis_pubsub
|
||||
|
||||
|
||||
# Define the channels for our chain
|
||||
CHANNEL_READER = "chain:reader"
|
||||
CHANNEL_PROCESSOR = "chain:processor"
|
||||
CHANNEL_WRITER = "chain:writer"
|
||||
|
||||
# Flag to control the demo
|
||||
running = True
|
||||
|
||||
|
||||
def generate_mock_data():
|
||||
"""Generate a mock message with UUID, timestamp, and sample data."""
|
||||
return {
|
||||
"uuid": str(uuid.uuid4()),
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"stage": "red", # Initial stage is 'red'
|
||||
"data": {
|
||||
"value": f"Sample data {int(time.time())}",
|
||||
"status": "new",
|
||||
"counter": 0,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def reader_function():
|
||||
"""
|
||||
First function in the chain.
|
||||
Generates mock data and publishes to the reader channel.
|
||||
"""
|
||||
print("[READER] Function started")
|
||||
|
||||
while running:
|
||||
# Generate mock data
|
||||
message = generate_mock_data()
|
||||
start_time = time.time()
|
||||
message["start_time"] = start_time
|
||||
|
||||
# Publish to reader channel
|
||||
result = redis_pubsub.publisher.publish(CHANNEL_READER, message)
|
||||
|
||||
if result.status:
|
||||
print(f"[READER] {time.time():.6f} | Published UUID: {message['uuid']}")
|
||||
else:
|
||||
print(f"[READER] Publish error: {result.error}")
|
||||
|
||||
# Wait before generating next message
|
||||
time.sleep(2)
|
||||
|
||||
|
||||
def processor_function():
|
||||
"""
|
||||
Second function in the chain.
|
||||
Subscribes to reader channel, processes messages, and publishes to processor channel.
|
||||
"""
|
||||
print("[PROCESSOR] Function started")
|
||||
|
||||
def on_reader_message(message):
|
||||
# The message structure from the subscriber has 'data' containing our actual message
|
||||
# If data is a string, parse it as JSON
|
||||
data = message["data"]
|
||||
if isinstance(data, str):
|
||||
try:
|
||||
data = json.loads(data)
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"[PROCESSOR] Error parsing message data: {e}")
|
||||
return
|
||||
|
||||
# Check if stage is 'red' before processing
|
||||
if data.get("stage") == "red":
|
||||
# Process the message
|
||||
data["processor_timestamp"] = datetime.now().isoformat()
|
||||
data["data"]["status"] = "processed"
|
||||
data["data"]["counter"] += 1
|
||||
|
||||
# Update stage to 'processed'
|
||||
data["stage"] = "processed"
|
||||
|
||||
# Add some processing metadata
|
||||
data["processing"] = {
|
||||
"duration_ms": 150, # Mock processing time
|
||||
"processor_id": "main-processor",
|
||||
}
|
||||
|
||||
# Publish to processor channel
|
||||
result = redis_pubsub.publisher.publish(CHANNEL_PROCESSOR, data)
|
||||
|
||||
if result.status:
|
||||
print(
|
||||
f"[PROCESSOR] {time.time():.6f} | Received UUID: {data['uuid']} | Published UUID: {data['uuid']}"
|
||||
)
|
||||
else:
|
||||
print(f"[PROCESSOR] Publish error: {result.error}")
|
||||
else:
|
||||
print(f"[PROCESSOR] Skipped message: {data['uuid']} (stage is not 'red')")
|
||||
|
||||
# Subscribe to reader channel
|
||||
result = redis_pubsub.subscriber.subscribe(CHANNEL_READER, on_reader_message)
|
||||
|
||||
if result.status:
|
||||
print(f"[PROCESSOR] Subscribed to channel: {CHANNEL_READER}")
|
||||
else:
|
||||
print(f"[PROCESSOR] Subscribe error: {result.error}")
|
||||
|
||||
|
||||
def writer_function():
|
||||
"""
|
||||
Third function in the chain.
|
||||
Subscribes to processor channel and performs final processing.
|
||||
"""
|
||||
print("[WRITER] Function started")
|
||||
|
||||
def on_processor_message(message):
|
||||
# The message structure from the subscriber has 'data' containing our actual message
|
||||
# If data is a string, parse it as JSON
|
||||
data = message["data"]
|
||||
if isinstance(data, str):
|
||||
try:
|
||||
data = json.loads(data)
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"[WRITER] Error parsing message data: {e}")
|
||||
return
|
||||
|
||||
# Check if stage is 'processed' before processing
|
||||
if data.get("stage") == "processed":
|
||||
# Process the message
|
||||
data["writer_timestamp"] = datetime.now().isoformat()
|
||||
data["data"]["status"] = "completed"
|
||||
data["data"]["counter"] += 1
|
||||
|
||||
# Update stage to 'completed'
|
||||
data["stage"] = "completed"
|
||||
|
||||
# Add some writer metadata
|
||||
data["storage"] = {"location": "main-db", "partition": "events-2025-04"}
|
||||
|
||||
# Calculate elapsed time if start_time is available
|
||||
current_time = time.time()
|
||||
elapsed_ms = ""
|
||||
if "start_time" in data:
|
||||
elapsed_ms = (
|
||||
f" | Elapsed: {(current_time - data['start_time']) * 1000:.2f}ms"
|
||||
)
|
||||
|
||||
# Optionally publish to writer channel for any downstream listeners
|
||||
result = redis_pubsub.publisher.publish(CHANNEL_WRITER, data)
|
||||
|
||||
if result.status:
|
||||
print(
|
||||
f"[WRITER] {current_time:.6f} | Received UUID: {data['uuid']} | Published UUID: {data['uuid']}{elapsed_ms}"
|
||||
)
|
||||
else:
|
||||
print(f"[WRITER] Publish error: {result.error}")
|
||||
else:
|
||||
print(
|
||||
f"[WRITER] Skipped message: {data['uuid']} (stage is not 'processed')"
|
||||
)
|
||||
|
||||
# Subscribe to processor channel
|
||||
result = redis_pubsub.subscriber.subscribe(CHANNEL_PROCESSOR, on_processor_message)
|
||||
|
||||
if result.status:
|
||||
print(f"[WRITER] Subscribed to channel: {CHANNEL_PROCESSOR}")
|
||||
else:
|
||||
print(f"[WRITER] Subscribe error: {result.error}")
|
||||
|
||||
|
||||
def run_demo():
|
||||
"""Run a demonstration of the simple chain of functions."""
|
||||
print("=== Starting Redis Pub/Sub Chain Demonstration ===")
|
||||
print("Chain: READER → PROCESSOR → WRITER")
|
||||
print(f"Channels: {CHANNEL_READER} → {CHANNEL_PROCESSOR} → {CHANNEL_WRITER}")
|
||||
print("Format: [SERVICE] TIMESTAMP | Received/Published UUID | [Elapsed time]")
|
||||
|
||||
# Start the Redis subscriber listening thread
|
||||
redis_pubsub.subscriber.start_listening()
|
||||
|
||||
# Start processor and writer functions (these subscribe to channels)
|
||||
processor_function()
|
||||
writer_function()
|
||||
|
||||
# Create a thread for the reader function (this publishes messages)
|
||||
reader_thread = Thread(target=reader_function, daemon=True)
|
||||
reader_thread.start()
|
||||
|
||||
# Keep the main thread alive
|
||||
try:
|
||||
while True:
|
||||
time.sleep(0.1)
|
||||
except KeyboardInterrupt:
|
||||
print("\nStopping demonstration...")
|
||||
global running
|
||||
running = False
|
||||
redis_pubsub.subscriber.stop_listening()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_demo()
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
# Redis Controller
|
||||
|
||||
## Overview
|
||||
This module provides a robust, thread-safe Redis connection handler with comprehensive concurrent operation testing. The Redis controller is designed for high-performance, resilient database connection management that can handle multiple simultaneous operations efficiently.
|
||||
|
||||
## Features
|
||||
- Singleton pattern for efficient connection management
|
||||
- Connection pooling with configurable settings
|
||||
- Automatic retry capabilities for Redis operations
|
||||
- Thread-safe operations with proper error handling
|
||||
- Comprehensive JSON data handling
|
||||
- TTL management and expiry time resolution
|
||||
- Efficient batch operations using Redis pipelines
|
||||
|
||||
## Configuration
|
||||
The Redis controller is configured with the following default settings:
|
||||
- Host: 10.10.2.15
|
||||
- Port: 6379
|
||||
- DB: 0
|
||||
- Connection pool size: 50 connections
|
||||
- Health check interval: 30 seconds
|
||||
- Socket timeout: 5.0 seconds
|
||||
- Retry on timeout: Enabled
|
||||
- Socket keepalive: Enabled
|
||||
|
||||
## Usage Examples
|
||||
The controller provides several high-level methods for Redis operations:
|
||||
- `set_json`: Store JSON data with optional expiry
|
||||
- `get_json`: Retrieve JSON data with pattern matching
|
||||
- `get_json_iterator`: Memory-efficient iterator for large datasets
|
||||
- `delete`: Remove keys matching a pattern
|
||||
- `refresh_ttl`: Update expiry time for existing keys
|
||||
- `key_exists`: Check if a key exists without retrieving it
|
||||
- `resolve_expires_at`: Get human-readable expiry time
|
||||
|
||||
## Concurrent Performance Testing
|
||||
The Redis controller has been thoroughly tested for concurrent operations with impressive results:
|
||||
|
||||
### Test Configuration
|
||||
- 10,000 concurrent threads
|
||||
- Each thread performs a set, get, and delete operation
|
||||
- Pipeline used for efficient batching
|
||||
- Exponential backoff for connection errors
|
||||
- Comprehensive error tracking and reporting
|
||||
|
||||
### Test Results
|
||||
```
|
||||
Concurrent Redis Test Results:
|
||||
Total threads: 10000
|
||||
Passed: 10000
|
||||
Failed: 0
|
||||
Operations with retries: 0
|
||||
Total retry attempts: 0
|
||||
Success rate: 100.00%
|
||||
|
||||
Performance Metrics:
|
||||
Total execution time: 4.30 seconds
|
||||
Operations per second: 2324.35
|
||||
Average operation time: 1.92 ms
|
||||
Minimum operation time: 0.43 ms
|
||||
Maximum operation time: 40.45 ms
|
||||
95th percentile operation time: 4.14 ms
|
||||
```
|
||||
|
||||
## Thread Safety
|
||||
The Redis controller is designed to be thread-safe with the following mechanisms:
|
||||
- Connection pooling to manage concurrent connections efficiently
|
||||
- Thread-local storage for operation-specific data
|
||||
- Atomic operations using Redis pipelines
|
||||
- Proper error handling and retry logic for connection issues
|
||||
- Exponential backoff for handling connection limits
|
||||
|
||||
## Error Handling
|
||||
The controller implements comprehensive error handling:
|
||||
- Connection errors are automatically retried with exponential backoff
|
||||
- Detailed error reporting with context-specific information
|
||||
- Graceful degradation under high load
|
||||
- Connection health monitoring and automatic reconnection
|
||||
|
||||
## Best Practices
|
||||
- Use pipelines for batching multiple operations
|
||||
- Implement proper key naming conventions
|
||||
- Set appropriate TTL values for cached data
|
||||
- Monitor connection pool usage in production
|
||||
- Use the JSON iterator for large datasets to minimize memory usage
|
||||
|
|
@ -0,0 +1,328 @@
|
|||
"""
|
||||
Redis key-value operations with structured data handling.
|
||||
|
||||
This module provides a class for managing Redis key-value operations with support for:
|
||||
- Structured data storage and retrieval
|
||||
- Key pattern generation for searches
|
||||
- JSON serialization/deserialization
|
||||
- Type-safe value handling
|
||||
"""
|
||||
|
||||
import arrow
|
||||
import json
|
||||
from typing import Union, Dict, List, Optional, Any, TypeVar
|
||||
|
||||
from Controllers.Redis.connection import redis_cli
|
||||
|
||||
|
||||
T = TypeVar("T", Dict[str, Any], List[Any])
|
||||
|
||||
|
||||
class RedisKeyError(Exception):
|
||||
"""Exception raised for Redis key-related errors."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class RedisValueError(Exception):
|
||||
"""Exception raised for Redis value-related errors."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class RedisRow:
|
||||
"""
|
||||
Handles Redis key-value operations with structured data.
|
||||
|
||||
This class provides methods for:
|
||||
- Managing compound keys with delimiters
|
||||
- Converting between bytes and string formats
|
||||
- JSON serialization/deserialization of values
|
||||
- Pattern generation for Redis key searches
|
||||
|
||||
Attributes:
|
||||
key: The Redis key in bytes or string format
|
||||
value: The stored value (will be JSON serialized)
|
||||
delimiter: Character used to separate compound key parts
|
||||
expires_at: Optional expiration timestamp
|
||||
"""
|
||||
|
||||
key: Union[str, bytes]
|
||||
value: Optional[str] = None
|
||||
delimiter: str = ":"
|
||||
expires_at: Optional[dict] = {"seconds": 60 * 60 * 30}
|
||||
expires_at_string: Optional[str]
|
||||
|
||||
def get_expiry_time(self) -> int | None:
|
||||
"""Calculate expiry time in seconds from kwargs."""
|
||||
time_multipliers = {"days": 86400, "hours": 3600, "minutes": 60, "seconds": 1}
|
||||
if self.expires_at:
|
||||
return sum(
|
||||
int(self.expires_at.get(unit, 0)) * multiplier
|
||||
for unit, multiplier in time_multipliers.items()
|
||||
)
|
||||
return None
|
||||
|
||||
def merge(self, set_values: List[Union[str, bytes]]) -> None:
|
||||
"""
|
||||
Merge list of values into a single delimited key.
|
||||
|
||||
Args:
|
||||
set_values: List of values to merge into key
|
||||
|
||||
Example:
|
||||
>>> RedisRow.merge(["users", "123", "profile"])
|
||||
>>> print(RedisRow.key)
|
||||
b'users:123:profile'
|
||||
"""
|
||||
if not set_values:
|
||||
raise RedisKeyError("Cannot merge empty list of values")
|
||||
|
||||
merged = []
|
||||
for value in set_values:
|
||||
if value is None:
|
||||
continue
|
||||
if isinstance(value, bytes):
|
||||
value = value.decode()
|
||||
merged.append(str(value))
|
||||
|
||||
self.key = self.delimiter.join(merged).encode()
|
||||
|
||||
@classmethod
|
||||
def regex(cls, list_keys: List[Union[Optional[str], Optional[bytes]]]) -> str:
|
||||
"""
|
||||
Generate Redis search pattern from list of keys.
|
||||
|
||||
Args:
|
||||
list_keys: List of key parts, can include None for wildcards
|
||||
|
||||
Returns:
|
||||
str: Redis key pattern with wildcards
|
||||
|
||||
Example:
|
||||
>>> RedisRow.regex([None, "users", "active"])
|
||||
'*:users:active'
|
||||
"""
|
||||
if not list_keys:
|
||||
return ""
|
||||
|
||||
# Filter and convert valid keys
|
||||
valid_keys = []
|
||||
for key in list_keys:
|
||||
if key is None or str(key) == "None":
|
||||
continue
|
||||
if isinstance(key, bytes):
|
||||
key = key.decode()
|
||||
valid_keys.append(str(key))
|
||||
|
||||
# Build pattern
|
||||
pattern = cls.delimiter.join(valid_keys)
|
||||
if not pattern:
|
||||
return ""
|
||||
|
||||
# Add wildcard if first key was None
|
||||
if list_keys[0] is None:
|
||||
pattern = f"*{cls.delimiter}{pattern}"
|
||||
if "*" not in pattern and any([list_key is None for list_key in list_keys]):
|
||||
pattern = f"{pattern}:*"
|
||||
return pattern
|
||||
|
||||
def parse(self) -> List[str]:
|
||||
"""
|
||||
Parse the key into its component parts.
|
||||
|
||||
Returns:
|
||||
List[str]: Key parts split by delimiter
|
||||
|
||||
Example:
|
||||
>>> RedisRow.key = b'users:123:profile'
|
||||
>>> RedisRow.parse()
|
||||
['users', '123', 'profile']
|
||||
"""
|
||||
if not self.key:
|
||||
return []
|
||||
|
||||
key_str = self.key.decode() if isinstance(self.key, bytes) else self.key
|
||||
return key_str.split(self.delimiter)
|
||||
|
||||
def feed(self, value: Union[bytes, Dict, List, str]) -> None:
|
||||
"""
|
||||
Convert and store value in JSON format.
|
||||
|
||||
Args:
|
||||
value: Value to store (bytes, dict, or list)
|
||||
|
||||
Raises:
|
||||
RedisValueError: If value type is not supported
|
||||
|
||||
Example:
|
||||
>>> RedisRow.feed({"name": "John", "age": 30})
|
||||
>>> print(RedisRow.value)
|
||||
'{"name": "John", "age": 30}'
|
||||
"""
|
||||
try:
|
||||
if isinstance(value, (dict, list)):
|
||||
self.value = json.dumps(value)
|
||||
elif isinstance(value, bytes):
|
||||
self.value = json.dumps(json.loads(value.decode()))
|
||||
elif isinstance(value, str):
|
||||
self.value = value
|
||||
else:
|
||||
raise RedisValueError(f"Unsupported value type: {type(value)}")
|
||||
except json.JSONDecodeError as e:
|
||||
raise RedisValueError(f"Invalid JSON format: {str(e)}")
|
||||
|
||||
def modify(self, add_dict: Dict) -> None:
|
||||
"""
|
||||
Modify existing data by merging with new dictionary.
|
||||
|
||||
Args:
|
||||
add_dict: Dictionary to merge with existing data
|
||||
|
||||
Example:
|
||||
>>> RedisRow.feed({"name": "John"})
|
||||
>>> RedisRow.modify({"age": 30})
|
||||
>>> print(RedisRow.data)
|
||||
{"name": "John", "age": 30}
|
||||
"""
|
||||
if not isinstance(add_dict, dict):
|
||||
raise RedisValueError("modify() requires a dictionary argument")
|
||||
current_data = self.row if self.row else {}
|
||||
if not isinstance(current_data, dict):
|
||||
raise RedisValueError("Cannot modify non-dictionary data")
|
||||
current_data = {
|
||||
**current_data,
|
||||
**add_dict,
|
||||
}
|
||||
self.feed(current_data)
|
||||
self.save()
|
||||
|
||||
def save(self):
|
||||
"""
|
||||
Save the data to Redis with optional expiration.
|
||||
|
||||
Raises:
|
||||
RedisKeyError: If key is not set
|
||||
RedisValueError: If value is not set
|
||||
"""
|
||||
|
||||
if not self.key:
|
||||
raise RedisKeyError("Cannot save data without a key")
|
||||
if not self.value:
|
||||
raise RedisValueError("Cannot save empty data")
|
||||
|
||||
if self.expires_at:
|
||||
redis_cli.setex(
|
||||
name=self.redis_key, time=self.get_expiry_time(), value=self.value
|
||||
)
|
||||
self.expires_at_string = str(
|
||||
arrow.now()
|
||||
.shift(seconds=self.get_expiry_time())
|
||||
.format("YYYY-MM-DD HH:mm:ss")
|
||||
)
|
||||
return self.value
|
||||
redis_cli.set(name=self.redis_key, value=self.value)
|
||||
self.expires_at = None
|
||||
self.expires_at_string = None
|
||||
return self.value
|
||||
|
||||
def remove(self, key: str) -> None:
|
||||
"""
|
||||
Remove a key from the stored dictionary.
|
||||
|
||||
Args:
|
||||
key: Key to remove from stored dictionary
|
||||
|
||||
Raises:
|
||||
KeyError: If key doesn't exist
|
||||
RedisValueError: If stored value is not a dictionary
|
||||
"""
|
||||
current_data = self.row
|
||||
if not isinstance(current_data, dict):
|
||||
raise RedisValueError("Cannot remove key from non-dictionary data")
|
||||
|
||||
try:
|
||||
current_data.pop(key)
|
||||
self.feed(current_data)
|
||||
self.save()
|
||||
except KeyError:
|
||||
raise KeyError(f"Key '{key}' not found in stored data")
|
||||
|
||||
def delete(self) -> None:
|
||||
"""Delete the key from Redis."""
|
||||
try:
|
||||
redis_cli.delete(self.redis_key)
|
||||
except Exception as e:
|
||||
raise RedisKeyError(f"Failed to delete key: {str(e)}")
|
||||
|
||||
@property
|
||||
def keys(self) -> str:
|
||||
"""
|
||||
Get key as string.
|
||||
|
||||
Returns:
|
||||
str: Key in string format
|
||||
"""
|
||||
return self.key.decode() if isinstance(self.key, bytes) else self.key
|
||||
|
||||
def set_key(self, key: Union[str, bytes]) -> None:
|
||||
"""
|
||||
Set key ensuring bytes format.
|
||||
|
||||
Args:
|
||||
key: Key in string or bytes format
|
||||
|
||||
Raises:
|
||||
RedisKeyError: If key is empty or invalid
|
||||
"""
|
||||
if not key:
|
||||
raise RedisKeyError("Cannot set empty key")
|
||||
|
||||
# Convert to string for validation
|
||||
key_str = key.decode() if isinstance(key, bytes) else str(key)
|
||||
|
||||
# Validate key length (Redis has a 512MB limit for keys)
|
||||
if len(key_str) > 512 * 1024 * 1024:
|
||||
raise RedisKeyError("Key exceeds maximum length of 512MB")
|
||||
|
||||
# Validate key format (basic check for invalid characters)
|
||||
if any(c in key_str for c in ["\n", "\r", "\t", "\0"]):
|
||||
raise RedisKeyError("Key contains invalid characters")
|
||||
|
||||
self.key = key if isinstance(key, bytes) else str(key).encode()
|
||||
|
||||
@property
|
||||
def redis_key(self) -> bytes:
|
||||
"""
|
||||
Get key in bytes format for Redis operations.
|
||||
|
||||
Returns:
|
||||
bytes: Key in bytes format
|
||||
"""
|
||||
return self.key if isinstance(self.key, bytes) else str(self.key).encode()
|
||||
|
||||
@property
|
||||
def row(self) -> Union[Dict, List]:
|
||||
"""
|
||||
Get stored value as Python object.
|
||||
|
||||
Returns:
|
||||
Union[Dict, List]: Deserialized JSON data
|
||||
"""
|
||||
try:
|
||||
return json.loads(self.value)
|
||||
except json.JSONDecodeError as e:
|
||||
raise RedisValueError(f"Invalid JSON format in stored value: {str(e)}")
|
||||
|
||||
@property
|
||||
def as_dict(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Get row data as dictionary.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: Dictionary with keys and value
|
||||
"""
|
||||
return {
|
||||
"keys": self.keys,
|
||||
"value": self.row,
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class Configs(BaseSettings):
|
||||
"""
|
||||
Redis configuration settings.
|
||||
"""
|
||||
|
||||
HOST: str = "10.10.2.15"
|
||||
PASSWORD: str = "your_strong_password_here"
|
||||
PORT: int = 6379
|
||||
DB: int = 0
|
||||
|
||||
def as_dict(self):
|
||||
return dict(
|
||||
host=self.HOST,
|
||||
password=self.PASSWORD,
|
||||
port=int(self.PORT),
|
||||
db=self.DB,
|
||||
)
|
||||
|
||||
model_config = SettingsConfigDict(env_prefix="REDIS_")
|
||||
|
||||
|
||||
redis_configs = Configs() # singleton instance of the REDIS configuration settings
|
||||
|
|
@ -0,0 +1,215 @@
|
|||
import time
|
||||
|
||||
from typing import Dict, Any
|
||||
from redis import Redis, ConnectionError, TimeoutError, ConnectionPool
|
||||
from Controllers.Redis.config import redis_configs
|
||||
|
||||
|
||||
class RedisConn:
|
||||
"""
|
||||
Redis connection manager with connection pooling, retry logic,
|
||||
and health check capabilities.
|
||||
"""
|
||||
|
||||
CONNECTION_RETRIES = 3 # Number of connection retries before failing
|
||||
RETRY_DELAY = 0.5 # Delay between retries in seconds
|
||||
DEFAULT_TIMEOUT = 5.0 # Default connection timeout in seconds
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
max_retries: int = CONNECTION_RETRIES,
|
||||
):
|
||||
"""
|
||||
Initialize Redis connection with configuration.
|
||||
|
||||
Args:
|
||||
max_retries: Maximum number of connection attempts.
|
||||
"""
|
||||
self.max_retries = max_retries
|
||||
self.config = redis_configs.as_dict()
|
||||
self._redis = None
|
||||
self._pool = None
|
||||
|
||||
# Add default parameters if not provided
|
||||
if "socket_timeout" not in self.config:
|
||||
self.config["socket_timeout"] = self.DEFAULT_TIMEOUT
|
||||
if "socket_connect_timeout" not in self.config:
|
||||
self.config["socket_connect_timeout"] = self.DEFAULT_TIMEOUT
|
||||
if "decode_responses" not in self.config:
|
||||
self.config["decode_responses"] = True
|
||||
|
||||
# Add connection pooling settings if not provided
|
||||
if "max_connections" not in self.config:
|
||||
self.config["max_connections"] = 50 # Increased for better concurrency
|
||||
|
||||
# Add connection timeout settings
|
||||
if "health_check_interval" not in self.config:
|
||||
self.config["health_check_interval"] = 30 # Health check every 30 seconds
|
||||
|
||||
# Add retry settings for operations
|
||||
if "retry_on_timeout" not in self.config:
|
||||
self.config["retry_on_timeout"] = True
|
||||
|
||||
# Add connection pool settings for better performance
|
||||
if "socket_keepalive" not in self.config:
|
||||
self.config["socket_keepalive"] = True
|
||||
|
||||
# Initialize the connection with retry logic
|
||||
self._connect_with_retry()
|
||||
|
||||
def __del__(self):
|
||||
"""Cleanup Redis connection and pool on object destruction."""
|
||||
self.close()
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close Redis connection and connection pool."""
|
||||
try:
|
||||
if self._redis:
|
||||
self._redis.close()
|
||||
self._redis = None
|
||||
if self._pool:
|
||||
self._pool.disconnect()
|
||||
self._pool = None
|
||||
except Exception as e:
|
||||
print(f"Error closing Redis connection: {str(e)}")
|
||||
|
||||
def _connect_with_retry(self) -> None:
|
||||
"""
|
||||
Attempt to establish a Redis connection with retry logic.
|
||||
|
||||
Raises:
|
||||
Exception: If all connection attempts fail.
|
||||
"""
|
||||
for attempt in range(1, self.max_retries + 1):
|
||||
try:
|
||||
if self._pool is None:
|
||||
self._pool = ConnectionPool(**self.config)
|
||||
self._redis = Redis(connection_pool=self._pool)
|
||||
if self.check_connection():
|
||||
return
|
||||
except (ConnectionError, TimeoutError) as e:
|
||||
if attempt < self.max_retries:
|
||||
time.sleep(self.RETRY_DELAY)
|
||||
else:
|
||||
raise Exception(
|
||||
f"Redis connection error after {self.max_retries} attempts: {str(e)}"
|
||||
)
|
||||
except Exception as e:
|
||||
raise
|
||||
|
||||
def check_connection(self) -> bool:
|
||||
"""
|
||||
Check if the Redis connection is alive with a PING command.
|
||||
|
||||
Returns:
|
||||
bool: True if connection is healthy, False otherwise.
|
||||
"""
|
||||
try:
|
||||
return self._redis.ping()
|
||||
except Exception as e:
|
||||
err = e
|
||||
return False
|
||||
|
||||
def set_connection(self, **kwargs) -> Redis:
|
||||
"""
|
||||
Recreate Redis connection with new parameters.
|
||||
|
||||
Args:
|
||||
host: Redis server hostname or IP
|
||||
password: Redis authentication password
|
||||
port: Redis server port
|
||||
db: Redis database number
|
||||
**kwargs: Additional Redis connection parameters
|
||||
|
||||
Returns:
|
||||
Redis: The new Redis connection object
|
||||
"""
|
||||
try:
|
||||
# Update configuration
|
||||
self.config = {
|
||||
"host": redis_configs.HOST,
|
||||
"password": redis_configs.PASSWORD,
|
||||
"port": redis_configs.PORT,
|
||||
"db": redis_configs.PORT,
|
||||
"socket_timeout": kwargs.get("socket_timeout", self.DEFAULT_TIMEOUT),
|
||||
"socket_connect_timeout": kwargs.get(
|
||||
"socket_connect_timeout", self.DEFAULT_TIMEOUT
|
||||
),
|
||||
"decode_responses": kwargs.get("decode_responses", True),
|
||||
"max_connections": kwargs.get("max_connections", 50),
|
||||
"health_check_interval": kwargs.get("health_check_interval", 30),
|
||||
"retry_on_timeout": kwargs.get("retry_on_timeout", True),
|
||||
"socket_keepalive": kwargs.get("socket_keepalive", True),
|
||||
}
|
||||
|
||||
# Add any additional parameters
|
||||
for key, value in kwargs.items():
|
||||
if key not in self.config:
|
||||
self.config[key] = value
|
||||
|
||||
# Create new connection
|
||||
self._redis = Redis(**self.config)
|
||||
if not self.check_connection():
|
||||
raise ConnectionError(
|
||||
"Failed to establish connection with new parameters"
|
||||
)
|
||||
|
||||
return self._redis
|
||||
except Exception as e:
|
||||
raise
|
||||
|
||||
def get_connection_info(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Get current connection configuration details.
|
||||
|
||||
Returns:
|
||||
Dict: Current connection configuration
|
||||
"""
|
||||
# Create a copy without password for security
|
||||
info = self.config.copy()
|
||||
if "password" in info:
|
||||
info["password"] = "********" if info["password"] else None
|
||||
return info
|
||||
|
||||
def get_stats(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Get Redis server statistics.
|
||||
|
||||
Returns:
|
||||
Dict: Redis server info
|
||||
"""
|
||||
try:
|
||||
return self._redis.info()
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
@property
|
||||
def redis(self) -> Redis:
|
||||
"""
|
||||
Property to access the Redis client.
|
||||
|
||||
Returns:
|
||||
Redis: The Redis client instance
|
||||
|
||||
Raises:
|
||||
Exception: If Redis connection is not available
|
||||
"""
|
||||
if not self._redis:
|
||||
raise Exception("Redis client is not initialized")
|
||||
|
||||
# Check connection health and reconnect if necessary
|
||||
if not self.check_connection():
|
||||
self._connect_with_retry()
|
||||
|
||||
return self._redis
|
||||
|
||||
|
||||
# Create singleton instance with error handling
|
||||
try:
|
||||
redis_conn = RedisConn()
|
||||
redis_cli = redis_conn.redis
|
||||
except Exception as t:
|
||||
# Optionally set a dummy/mock Redis client for testing or fallback behavior
|
||||
# redis_cli = MockRedis() # If you have a mock implementation
|
||||
# Or raise the exception to fail fast
|
||||
raise
|
||||
|
|
@ -0,0 +1,355 @@
|
|||
import arrow
|
||||
|
||||
from typing import Optional, List, Dict, Union, Iterator
|
||||
|
||||
from Controllers.Redis.response import RedisResponse
|
||||
from Controllers.Redis.connection import redis_cli
|
||||
from Controllers.Redis.base import RedisRow
|
||||
|
||||
|
||||
class MainConfig:
|
||||
DATETIME_FORMAT: str = "YYYY-MM-DD HH:mm:ss"
|
||||
|
||||
|
||||
class RedisActions:
|
||||
"""Class for handling Redis operations with JSON data."""
|
||||
|
||||
@classmethod
|
||||
def get_expiry_time(cls, expiry_kwargs: Dict[str, int]) -> int:
|
||||
"""
|
||||
Calculate expiry time in seconds from kwargs.
|
||||
|
||||
Args:
|
||||
expiry_kwargs: Dictionary with time units as keys (days, hours, minutes, seconds)
|
||||
and their respective values.
|
||||
|
||||
Returns:
|
||||
Total expiry time in seconds.
|
||||
"""
|
||||
time_multipliers = {"days": 86400, "hours": 3600, "minutes": 60, "seconds": 1}
|
||||
return sum(
|
||||
int(expiry_kwargs.get(unit, 0)) * multiplier
|
||||
for unit, multiplier in time_multipliers.items()
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def set_expiry_time(cls, expiry_seconds: int) -> Dict[str, int]:
|
||||
"""
|
||||
Convert total seconds back into a dictionary of time units.
|
||||
|
||||
Args:
|
||||
expiry_seconds: Total expiry time in seconds.
|
||||
|
||||
Returns:
|
||||
Dictionary with time units and their values.
|
||||
"""
|
||||
time_multipliers = {"days": 86400, "hours": 3600, "minutes": 60, "seconds": 1}
|
||||
result = {}
|
||||
remaining_seconds = expiry_seconds
|
||||
|
||||
if expiry_seconds < 0:
|
||||
return {}
|
||||
|
||||
for unit, multiplier in time_multipliers.items():
|
||||
if remaining_seconds >= multiplier:
|
||||
result[unit], remaining_seconds = divmod(remaining_seconds, multiplier)
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def resolve_expires_at(cls, redis_row: RedisRow) -> str:
|
||||
"""
|
||||
Resolve expiry time for Redis key.
|
||||
|
||||
Args:
|
||||
redis_row: RedisRow object containing the redis_key.
|
||||
|
||||
Returns:
|
||||
Formatted expiry time string or message indicating no expiry.
|
||||
"""
|
||||
expiry_time = redis_cli.ttl(redis_row.redis_key)
|
||||
if expiry_time == -1:
|
||||
return "Key has no expiry time."
|
||||
if expiry_time == -2:
|
||||
return "Key does not exist."
|
||||
return arrow.now().shift(seconds=expiry_time).format(MainConfig.DATETIME_FORMAT)
|
||||
|
||||
@classmethod
|
||||
def key_exists(cls, key: Union[str, bytes]) -> bool:
|
||||
"""
|
||||
Check if a key exists in Redis without retrieving its value.
|
||||
|
||||
Args:
|
||||
key: Redis key to check.
|
||||
|
||||
Returns:
|
||||
Boolean indicating if key exists.
|
||||
"""
|
||||
return bool(redis_cli.exists(key))
|
||||
|
||||
@classmethod
|
||||
def refresh_ttl(
|
||||
cls, key: Union[str, bytes], expires: Dict[str, int]
|
||||
) -> RedisResponse:
|
||||
"""
|
||||
Refresh TTL for an existing key.
|
||||
|
||||
Args:
|
||||
key: Redis key to refresh TTL.
|
||||
expires: Dictionary with time units to set new expiry.
|
||||
|
||||
Returns:
|
||||
RedisResponse with operation result.
|
||||
"""
|
||||
try:
|
||||
if not cls.key_exists(key):
|
||||
return RedisResponse(
|
||||
status=False,
|
||||
message="Cannot refresh TTL: Key does not exist.",
|
||||
)
|
||||
|
||||
expiry_time = cls.get_expiry_time(expiry_kwargs=expires)
|
||||
redis_cli.expire(name=key, time=expiry_time)
|
||||
|
||||
expires_at_string = (
|
||||
arrow.now()
|
||||
.shift(seconds=expiry_time)
|
||||
.format(MainConfig.DATETIME_FORMAT)
|
||||
)
|
||||
|
||||
return RedisResponse(
|
||||
status=True,
|
||||
message="TTL refreshed successfully.",
|
||||
data={"key": key, "expires_at": expires_at_string},
|
||||
)
|
||||
except Exception as e:
|
||||
return RedisResponse(
|
||||
status=False,
|
||||
message="Failed to refresh TTL.",
|
||||
error=str(e),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def delete_key(cls, key: Union[Optional[str], Optional[bytes]]) -> RedisResponse:
|
||||
"""
|
||||
Delete a specific key from Redis.
|
||||
|
||||
Args:
|
||||
key: Redis key to delete.
|
||||
|
||||
Returns:
|
||||
RedisResponse with operation result.
|
||||
"""
|
||||
try:
|
||||
deleted_count = redis_cli.delete(key)
|
||||
if deleted_count > 0:
|
||||
return RedisResponse(
|
||||
status=True,
|
||||
message="Key deleted successfully.",
|
||||
data={"deleted_count": deleted_count},
|
||||
)
|
||||
return RedisResponse(
|
||||
status=False,
|
||||
message="Key not found or already deleted.",
|
||||
data={"deleted_count": 0},
|
||||
)
|
||||
except Exception as e:
|
||||
return RedisResponse(
|
||||
status=False,
|
||||
message="Failed to delete key.",
|
||||
error=str(e),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def delete(
|
||||
cls, list_keys: List[Union[Optional[str], Optional[bytes]]]
|
||||
) -> RedisResponse:
|
||||
"""
|
||||
Delete multiple keys matching a pattern.
|
||||
|
||||
Args:
|
||||
list_keys: List of key components to form pattern for deletion.
|
||||
|
||||
Returns:
|
||||
RedisResponse with operation result.
|
||||
"""
|
||||
try:
|
||||
regex = RedisRow().regex(list_keys=list_keys)
|
||||
json_get = redis_cli.scan_iter(match=regex)
|
||||
|
||||
deleted_keys, deleted_count = [], 0
|
||||
|
||||
# Use pipeline for batch deletion
|
||||
with redis_cli.pipeline() as pipe:
|
||||
for row in json_get:
|
||||
pipe.delete(row)
|
||||
deleted_keys.append(row)
|
||||
results = pipe.execute()
|
||||
deleted_count = sum(results)
|
||||
|
||||
return RedisResponse(
|
||||
status=True,
|
||||
message="Keys deleted successfully.",
|
||||
data={"deleted_count": deleted_count, "deleted_keys": deleted_keys},
|
||||
)
|
||||
except Exception as e:
|
||||
return RedisResponse(
|
||||
status=False,
|
||||
message="Failed to delete keys.",
|
||||
error=str(e),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def set_json(
|
||||
cls,
|
||||
list_keys: List[Union[str, bytes]],
|
||||
value: Optional[Union[Dict, List]],
|
||||
expires: Optional[Dict[str, int]] = None,
|
||||
) -> RedisResponse:
|
||||
"""
|
||||
Set JSON value in Redis with optional expiry.
|
||||
|
||||
Args:
|
||||
list_keys: List of key components to form Redis key.
|
||||
value: JSON-serializable data to store.
|
||||
expires: Optional dictionary with time units for expiry.
|
||||
|
||||
Returns:
|
||||
RedisResponse with operation result.
|
||||
"""
|
||||
redis_row = RedisRow()
|
||||
redis_row.merge(set_values=list_keys)
|
||||
redis_row.feed(value)
|
||||
redis_row.expires_at_string = None
|
||||
redis_row.expires_at = None
|
||||
|
||||
try:
|
||||
if expires:
|
||||
redis_row.expires_at = expires
|
||||
expiry_time = cls.get_expiry_time(expiry_kwargs=expires)
|
||||
redis_cli.setex(
|
||||
name=redis_row.redis_key,
|
||||
time=expiry_time,
|
||||
value=redis_row.value,
|
||||
)
|
||||
redis_row.expires_at_string = str(
|
||||
arrow.now()
|
||||
.shift(seconds=expiry_time)
|
||||
.format(MainConfig.DATETIME_FORMAT)
|
||||
)
|
||||
else:
|
||||
redis_cli.set(name=redis_row.redis_key, value=redis_row.value)
|
||||
|
||||
return RedisResponse(
|
||||
status=True,
|
||||
message="Value set successfully.",
|
||||
data=redis_row,
|
||||
)
|
||||
except Exception as e:
|
||||
return RedisResponse(
|
||||
status=False,
|
||||
message="Failed to set value.",
|
||||
error=str(e),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_json(
|
||||
cls,
|
||||
list_keys: List[Union[Optional[str], Optional[bytes]]],
|
||||
limit: Optional[int] = None,
|
||||
) -> RedisResponse:
|
||||
"""
|
||||
Get JSON values from Redis using pattern matching.
|
||||
|
||||
Args:
|
||||
list_keys: List of key components to form pattern for retrieval.
|
||||
limit: Optional limit on number of results to return.
|
||||
|
||||
Returns:
|
||||
RedisResponse with operation result.
|
||||
"""
|
||||
try:
|
||||
list_of_rows, count = [], 0
|
||||
regex = RedisRow.regex(list_keys=list_keys)
|
||||
json_get = redis_cli.scan_iter(match=regex)
|
||||
|
||||
for row in json_get:
|
||||
if limit is not None and count >= limit:
|
||||
break
|
||||
|
||||
redis_row = RedisRow()
|
||||
redis_row.set_key(key=row)
|
||||
|
||||
# Use pipeline for batch retrieval
|
||||
with redis_cli.pipeline() as pipe:
|
||||
pipe.get(row)
|
||||
pipe.ttl(row)
|
||||
redis_value, redis_value_expire = pipe.execute()
|
||||
redis_row.expires_at = cls.set_expiry_time(
|
||||
expiry_seconds=int(redis_value_expire)
|
||||
)
|
||||
redis_row.expires_at_string = cls.resolve_expires_at(
|
||||
redis_row=redis_row
|
||||
)
|
||||
redis_row.feed(redis_value)
|
||||
list_of_rows.append(redis_row)
|
||||
count += 1
|
||||
|
||||
if list_of_rows:
|
||||
return RedisResponse(
|
||||
status=True,
|
||||
message="Values retrieved successfully.",
|
||||
data=list_of_rows,
|
||||
)
|
||||
return RedisResponse(
|
||||
status=False,
|
||||
message="No matching keys found.",
|
||||
data=list_of_rows,
|
||||
)
|
||||
except Exception as e:
|
||||
return RedisResponse(
|
||||
status=False,
|
||||
message="Failed to retrieve values.",
|
||||
error=str(e),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_json_iterator(
|
||||
cls, list_keys: List[Union[Optional[str], Optional[bytes]]]
|
||||
) -> Iterator[RedisRow]:
|
||||
"""
|
||||
Get JSON values from Redis as an iterator for memory-efficient processing of large datasets.
|
||||
|
||||
Args:
|
||||
list_keys: List of key components to form pattern for retrieval.
|
||||
|
||||
Returns:
|
||||
Iterator yielding RedisRow objects.
|
||||
|
||||
Raises:
|
||||
RedisValueError: If there's an error processing a row
|
||||
"""
|
||||
regex = RedisRow.regex(list_keys=list_keys)
|
||||
json_get = redis_cli.scan_iter(match=regex)
|
||||
|
||||
for row in json_get:
|
||||
try:
|
||||
redis_row = RedisRow()
|
||||
redis_row.set_key(key=row)
|
||||
|
||||
# Use pipeline for batch retrieval
|
||||
with redis_cli.pipeline() as pipe:
|
||||
pipe.get(row)
|
||||
pipe.ttl(row)
|
||||
redis_value, redis_value_expire = pipe.execute()
|
||||
redis_row.expires_at = cls.set_expiry_time(
|
||||
expiry_seconds=int(redis_value_expire)
|
||||
)
|
||||
redis_row.expires_at_string = cls.resolve_expires_at(
|
||||
redis_row=redis_row
|
||||
)
|
||||
redis_row.feed(redis_value)
|
||||
yield redis_row
|
||||
except Exception as e:
|
||||
# Log the error and continue with next row
|
||||
print(f"Error processing row {row}: {str(e)}")
|
||||
continue
|
||||
|
|
@ -0,0 +1,280 @@
|
|||
from Controllers.Redis.database import RedisActions
|
||||
import threading
|
||||
import time
|
||||
import random
|
||||
import uuid
|
||||
import concurrent.futures
|
||||
|
||||
|
||||
def example_set_json() -> None:
|
||||
"""Example of setting JSON data in Redis with and without expiry."""
|
||||
# Example 1: Set JSON without expiry
|
||||
data = {"name": "John", "age": 30, "city": "New York"}
|
||||
keys = ["user", "profile", "123"]
|
||||
result = RedisActions.set_json(list_keys=keys, value=data)
|
||||
print("Set JSON without expiry:", result.as_dict())
|
||||
|
||||
# Example 2: Set JSON with expiry
|
||||
expiry = {"hours": 1, "minutes": 30}
|
||||
result = RedisActions.set_json(list_keys=keys, value=data, expires=expiry)
|
||||
print("Set JSON with expiry:", result.as_dict())
|
||||
|
||||
|
||||
def example_get_json() -> None:
|
||||
"""Example of retrieving JSON data from Redis."""
|
||||
# Example 1: Get all matching keys
|
||||
keys = ["user", "profile", "*"]
|
||||
result = RedisActions.get_json(list_keys=keys)
|
||||
print("Get all matching JSON:", result.as_dict())
|
||||
|
||||
# Example 2: Get with limit
|
||||
result = RedisActions.get_json(list_keys=keys, limit=5)
|
||||
print("Get JSON with limit:", result.as_dict())
|
||||
|
||||
|
||||
def example_get_json_iterator() -> None:
|
||||
"""Example of using the JSON iterator for large datasets."""
|
||||
keys = ["user", "profile", "*"]
|
||||
for row in RedisActions.get_json_iterator(list_keys=keys):
|
||||
print(
|
||||
"Iterating over JSON row:",
|
||||
row.as_dict if isinstance(row.as_dict, dict) else row.as_dict,
|
||||
)
|
||||
|
||||
|
||||
def example_delete_key() -> None:
|
||||
"""Example of deleting a specific key."""
|
||||
key = "user:profile:123"
|
||||
result = RedisActions.delete_key(key)
|
||||
print("Delete specific key:", result)
|
||||
|
||||
|
||||
def example_delete() -> None:
|
||||
"""Example of deleting multiple keys matching a pattern."""
|
||||
keys = ["user", "profile", "*"]
|
||||
result = RedisActions.delete(list_keys=keys)
|
||||
print("Delete multiple keys:", result)
|
||||
|
||||
|
||||
def example_refresh_ttl() -> None:
|
||||
"""Example of refreshing TTL for a key."""
|
||||
key = "user:profile:123"
|
||||
new_expiry = {"hours": 2, "minutes": 0}
|
||||
result = RedisActions.refresh_ttl(key=key, expires=new_expiry)
|
||||
print("Refresh TTL:", result.as_dict())
|
||||
|
||||
|
||||
def example_key_exists() -> None:
|
||||
"""Example of checking if a key exists."""
|
||||
key = "user:profile:123"
|
||||
exists = RedisActions.key_exists(key)
|
||||
print(f"Key {key} exists:", exists)
|
||||
|
||||
|
||||
def example_resolve_expires_at() -> None:
|
||||
"""Example of resolving expiry time for a key."""
|
||||
from Controllers.Redis.base import RedisRow
|
||||
|
||||
redis_row = RedisRow()
|
||||
redis_row.set_key("user:profile:123")
|
||||
print(redis_row.keys)
|
||||
expires_at = RedisActions.resolve_expires_at(redis_row)
|
||||
print("Resolve expires at:", expires_at)
|
||||
|
||||
|
||||
def run_all_examples() -> None:
|
||||
"""Run all example functions to demonstrate RedisActions functionality."""
|
||||
print("\n=== Redis Actions Examples ===\n")
|
||||
|
||||
print("1. Setting JSON data:")
|
||||
example_set_json()
|
||||
|
||||
print("\n2. Getting JSON data:")
|
||||
example_get_json()
|
||||
|
||||
print("\n3. Using JSON iterator:")
|
||||
example_get_json_iterator()
|
||||
|
||||
# print("\n4. Deleting specific key:")
|
||||
# example_delete_key()
|
||||
#
|
||||
# print("\n5. Deleting multiple keys:")
|
||||
# example_delete()
|
||||
|
||||
print("\n6. Refreshing TTL:")
|
||||
example_refresh_ttl()
|
||||
|
||||
print("\n7. Checking key existence:")
|
||||
example_key_exists()
|
||||
|
||||
print("\n8. Resolving expiry time:")
|
||||
example_resolve_expires_at()
|
||||
|
||||
|
||||
def run_concurrent_test(num_threads=100):
|
||||
"""Run a comprehensive concurrent test with multiple threads to verify Redis connection handling."""
|
||||
print(
|
||||
f"\nStarting comprehensive Redis concurrent test with {num_threads} threads..."
|
||||
)
|
||||
|
||||
# Results tracking with detailed metrics
|
||||
results = {
|
||||
"passed": 0,
|
||||
"failed": 0,
|
||||
"retried": 0,
|
||||
"errors": [],
|
||||
"operation_times": [],
|
||||
"retry_count": 0,
|
||||
"max_retries": 3,
|
||||
"retry_delay": 0.1,
|
||||
}
|
||||
results_lock = threading.Lock()
|
||||
|
||||
def worker(thread_id):
|
||||
# Track operation timing
|
||||
start_time = time.time()
|
||||
retry_count = 0
|
||||
success = False
|
||||
error_message = None
|
||||
|
||||
while retry_count <= results["max_retries"] and not success:
|
||||
try:
|
||||
# Generate unique key for this thread
|
||||
unique_id = str(uuid.uuid4())[:8]
|
||||
full_key = f"test:concurrent:{thread_id}:{unique_id}"
|
||||
|
||||
# Simple string operations instead of JSON
|
||||
test_value = f"test-value-{thread_id}-{time.time()}"
|
||||
|
||||
# Set data in Redis with pipeline for efficiency
|
||||
from Controllers.Redis.database import redis_cli
|
||||
|
||||
# Use pipeline to reduce network overhead
|
||||
with redis_cli.pipeline() as pipe:
|
||||
pipe.set(full_key, test_value)
|
||||
pipe.get(full_key)
|
||||
pipe.delete(full_key)
|
||||
results_list = pipe.execute()
|
||||
|
||||
# Check results
|
||||
set_ok = results_list[0]
|
||||
retrieved_value = results_list[1]
|
||||
if isinstance(retrieved_value, bytes):
|
||||
retrieved_value = retrieved_value.decode("utf-8")
|
||||
|
||||
# Verify data
|
||||
success = set_ok and retrieved_value == test_value
|
||||
|
||||
if success:
|
||||
break
|
||||
else:
|
||||
error_message = f"Data verification failed: set_ok={set_ok}, value_match={retrieved_value == test_value}"
|
||||
retry_count += 1
|
||||
with results_lock:
|
||||
results["retry_count"] += 1
|
||||
time.sleep(
|
||||
results["retry_delay"] * (2**retry_count)
|
||||
) # Exponential backoff
|
||||
|
||||
except Exception as e:
|
||||
error_message = str(e)
|
||||
retry_count += 1
|
||||
with results_lock:
|
||||
results["retry_count"] += 1
|
||||
|
||||
# Check if it's a connection error and retry
|
||||
if "Too many connections" in str(e) or "Connection" in str(e):
|
||||
# Exponential backoff for connection issues
|
||||
backoff_time = results["retry_delay"] * (2**retry_count)
|
||||
time.sleep(backoff_time)
|
||||
else:
|
||||
# For other errors, use a smaller delay
|
||||
time.sleep(results["retry_delay"])
|
||||
|
||||
# Record operation time
|
||||
operation_time = time.time() - start_time
|
||||
|
||||
# Update results
|
||||
with results_lock:
|
||||
if success:
|
||||
results["passed"] += 1
|
||||
results["operation_times"].append(operation_time)
|
||||
if retry_count > 0:
|
||||
results["retried"] += 1
|
||||
else:
|
||||
results["failed"] += 1
|
||||
if error_message:
|
||||
results["errors"].append(
|
||||
f"Thread {thread_id} failed after {retry_count} retries: {error_message}"
|
||||
)
|
||||
else:
|
||||
results["errors"].append(
|
||||
f"Thread {thread_id} failed after {retry_count} retries with unknown error"
|
||||
)
|
||||
|
||||
# Create and start threads using a thread pool
|
||||
start_time = time.time()
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=num_threads) as executor:
|
||||
futures = [executor.submit(worker, i) for i in range(num_threads)]
|
||||
concurrent.futures.wait(futures)
|
||||
|
||||
# Calculate execution time and performance metrics
|
||||
execution_time = time.time() - start_time
|
||||
ops_per_second = num_threads / execution_time if execution_time > 0 else 0
|
||||
|
||||
# Calculate additional metrics if we have successful operations
|
||||
avg_op_time = 0
|
||||
min_op_time = 0
|
||||
max_op_time = 0
|
||||
p95_op_time = 0
|
||||
|
||||
if results["operation_times"]:
|
||||
avg_op_time = sum(results["operation_times"]) / len(results["operation_times"])
|
||||
min_op_time = min(results["operation_times"])
|
||||
max_op_time = max(results["operation_times"])
|
||||
# Calculate 95th percentile
|
||||
sorted_times = sorted(results["operation_times"])
|
||||
p95_index = int(len(sorted_times) * 0.95)
|
||||
p95_op_time = (
|
||||
sorted_times[p95_index]
|
||||
if p95_index < len(sorted_times)
|
||||
else sorted_times[-1]
|
||||
)
|
||||
|
||||
# Print detailed results
|
||||
print("\nConcurrent Redis Test Results:")
|
||||
print(f"Total threads: {num_threads}")
|
||||
print(f"Passed: {results['passed']}")
|
||||
print(f"Failed: {results['failed']}")
|
||||
print(f"Operations with retries: {results['retried']}")
|
||||
print(f"Total retry attempts: {results['retry_count']}")
|
||||
print(f"Success rate: {(results['passed'] / num_threads) * 100:.2f}%")
|
||||
|
||||
print("\nPerformance Metrics:")
|
||||
print(f"Total execution time: {execution_time:.2f} seconds")
|
||||
print(f"Operations per second: {ops_per_second:.2f}")
|
||||
|
||||
if results["operation_times"]:
|
||||
print(f"Average operation time: {avg_op_time * 1000:.2f} ms")
|
||||
print(f"Minimum operation time: {min_op_time * 1000:.2f} ms")
|
||||
print(f"Maximum operation time: {max_op_time * 1000:.2f} ms")
|
||||
print(f"95th percentile operation time: {p95_op_time * 1000:.2f} ms")
|
||||
|
||||
# Print errors (limited to 10 for readability)
|
||||
if results["errors"]:
|
||||
print("\nErrors:")
|
||||
for i, error in enumerate(results["errors"][:10]):
|
||||
print(f"- {error}")
|
||||
if len(results["errors"]) > 10:
|
||||
print(f"- ... and {len(results['errors']) - 10} more errors")
|
||||
|
||||
# Return results for potential further analysis
|
||||
return results
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run basic examples
|
||||
run_all_examples()
|
||||
|
||||
# Run enhanced concurrent test
|
||||
run_concurrent_test(10000)
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
from typing import Union, Dict, Optional, Any
|
||||
from Controllers.Redis.base import RedisRow
|
||||
|
||||
|
||||
class RedisResponse:
|
||||
"""
|
||||
Base class for Redis response handling.
|
||||
|
||||
Provides a standardized way to return and process Redis operation results,
|
||||
with tools to convert between different data representations.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
status: bool,
|
||||
message: str,
|
||||
data: Any = None,
|
||||
error: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
Initialize a Redis response.
|
||||
|
||||
Args:
|
||||
status: Operation success status
|
||||
message: Human-readable message about the operation
|
||||
data: Response data (can be None, RedisRow, list, or dict)
|
||||
error: Optional error message if operation failed
|
||||
"""
|
||||
self.status = status
|
||||
self.message = message
|
||||
self.data = data
|
||||
self.error = error
|
||||
|
||||
# Determine the data type
|
||||
if isinstance(data, dict):
|
||||
self.data_type = "dict"
|
||||
elif isinstance(data, list):
|
||||
self.data_type = "list"
|
||||
elif isinstance(data, RedisRow):
|
||||
self.data_type = "row"
|
||||
elif isinstance(data, (int, float, str, bool)):
|
||||
self.data_type = "primitive"
|
||||
else:
|
||||
self.data_type = None
|
||||
|
||||
def as_dict(self) -> Dict:
|
||||
"""
|
||||
Convert the response to a dictionary format suitable for serialization.
|
||||
|
||||
Returns:
|
||||
Dictionary representation of the response
|
||||
"""
|
||||
# Base response fields
|
||||
main_dict = {
|
||||
"status": self.status,
|
||||
"message": self.message,
|
||||
"count": self.count,
|
||||
"dataType": self.data_type,
|
||||
}
|
||||
|
||||
# Add error if present
|
||||
if self.error:
|
||||
main_dict["error"] = self.error
|
||||
|
||||
data = self.all
|
||||
|
||||
# Process single RedisRow
|
||||
if isinstance(data, RedisRow):
|
||||
result = {**main_dict}
|
||||
if hasattr(data, "keys") and hasattr(data, "row"):
|
||||
if not isinstance(data.keys, str):
|
||||
raise ValueError("RedisRow keys must be string type")
|
||||
result[data.keys] = data.row
|
||||
return result
|
||||
|
||||
# Process list of RedisRows
|
||||
elif isinstance(data, list):
|
||||
result = {**main_dict}
|
||||
|
||||
# Handle list of RedisRow objects
|
||||
rows_dict = {}
|
||||
for row in data:
|
||||
if (
|
||||
isinstance(row, RedisRow)
|
||||
and hasattr(row, "keys")
|
||||
and hasattr(row, "row")
|
||||
):
|
||||
if not isinstance(row.keys, str):
|
||||
raise ValueError("RedisRow keys must be string type")
|
||||
rows_dict[row.keys] = row.row
|
||||
|
||||
if rows_dict:
|
||||
result["data"] = rows_dict
|
||||
elif data: # If it's just a regular list with items
|
||||
result["data"] = data
|
||||
|
||||
return result
|
||||
|
||||
# Process dictionary
|
||||
elif isinstance(data, dict):
|
||||
return {**main_dict, "data": data}
|
||||
|
||||
return main_dict
|
||||
|
||||
@property
|
||||
def all(self) -> Any:
|
||||
"""
|
||||
Get all data from the response.
|
||||
|
||||
Returns:
|
||||
All data or empty list if None
|
||||
"""
|
||||
return self.data if self.data is not None else []
|
||||
|
||||
@property
|
||||
def count(self) -> int:
|
||||
"""
|
||||
Count the number of items in the response data.
|
||||
|
||||
Returns:
|
||||
Number of items (0 if no data)
|
||||
"""
|
||||
data = self.all
|
||||
|
||||
if isinstance(data, list):
|
||||
return len(data)
|
||||
elif isinstance(data, (RedisRow, dict)):
|
||||
return 1
|
||||
return 0
|
||||
|
||||
@property
|
||||
def first(self) -> Union[Dict, None]:
|
||||
"""
|
||||
Get the first item from the response data.
|
||||
|
||||
Returns:
|
||||
First item as a dictionary or None if no data
|
||||
"""
|
||||
if not self.data:
|
||||
return None
|
||||
|
||||
if isinstance(self.data, list) and self.data:
|
||||
item = self.data[0]
|
||||
if isinstance(item, RedisRow) and hasattr(item, "row"):
|
||||
return item.row
|
||||
return item
|
||||
elif isinstance(self.data, RedisRow) and hasattr(self.data, "row"):
|
||||
return self.data.row
|
||||
elif isinstance(self.data, dict):
|
||||
return self.data
|
||||
|
||||
return None
|
||||
|
||||
def is_successful(self) -> bool:
|
||||
"""
|
||||
Check if the operation was successful.
|
||||
|
||||
Returns:
|
||||
Boolean indicating success status
|
||||
"""
|
||||
return self.status
|
||||
|
||||
def to_api_response(self) -> Dict:
|
||||
"""
|
||||
Format the response for API consumption.
|
||||
|
||||
Returns:
|
||||
API-friendly response dictionary
|
||||
"""
|
||||
try:
|
||||
response = {
|
||||
"success": self.status,
|
||||
"message": self.message,
|
||||
}
|
||||
|
||||
if self.error:
|
||||
response["error"] = self.error
|
||||
|
||||
if self.data is not None:
|
||||
if self.data_type == "row" and hasattr(self.data, "to_dict"):
|
||||
response["data"] = self.data.to_dict()
|
||||
elif self.data_type == "list":
|
||||
try:
|
||||
if all(hasattr(item, "to_dict") for item in self.data):
|
||||
response["data"] = [item.to_dict() for item in self.data]
|
||||
else:
|
||||
response["data"] = self.data
|
||||
except Exception as e:
|
||||
response["error"] = f"Error converting list items: {str(e)}"
|
||||
else:
|
||||
response["data"] = self.data
|
||||
|
||||
response["count"] = self.count
|
||||
return response
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "Error formatting response",
|
||||
"error": str(e),
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import uvicorn
|
||||
|
||||
from api_initializer.config import api_config
|
||||
from api_initializer.create_app import create_app
|
||||
|
||||
# from prometheus_fastapi_instrumentator import Instrumentator
|
||||
|
||||
|
||||
app = create_app() # Create FastAPI application
|
||||
# Instrumentator().instrument(app=app).expose(app=app) # Setup Prometheus metrics
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run the application with Uvicorn Server
|
||||
uvicorn_config = uvicorn.Config(**api_config.app_as_dict)
|
||||
uvicorn.Server(uvicorn_config).run()
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
|
||||
class Configs(BaseSettings):
|
||||
"""
|
||||
ApiTemplate configuration settings.
|
||||
"""
|
||||
|
||||
PATH: str = ""
|
||||
HOST: str = ""
|
||||
PORT: int = 0
|
||||
LOG_LEVEL: str = "info"
|
||||
RELOAD: int = 0
|
||||
ACCESS_TOKEN_TAG: str = ""
|
||||
|
||||
ACCESS_EMAIL_EXT: str = ""
|
||||
TITLE: str = ""
|
||||
ALGORITHM: str = ""
|
||||
ACCESS_TOKEN_LENGTH: int = 90
|
||||
REFRESHER_TOKEN_LENGTH: int = 144
|
||||
EMAIL_HOST: str = ""
|
||||
DATETIME_FORMAT: str = ""
|
||||
FORGOT_LINK: str = ""
|
||||
ALLOW_ORIGINS: list = ["http://localhost:3000", "http://localhost:3001"]
|
||||
VERSION: str = "0.1.001"
|
||||
DESCRIPTION: str = ""
|
||||
|
||||
@property
|
||||
def app_as_dict(self) -> dict:
|
||||
"""
|
||||
Convert the settings to a dictionary.
|
||||
"""
|
||||
return {
|
||||
"app": self.PATH,
|
||||
"host": self.HOST,
|
||||
"port": int(self.PORT),
|
||||
"log_level": self.LOG_LEVEL,
|
||||
"reload": bool(self.RELOAD),
|
||||
}
|
||||
|
||||
@property
|
||||
def api_info(self):
|
||||
"""
|
||||
Returns a dictionary with application information.
|
||||
"""
|
||||
return {
|
||||
"title": self.TITLE,
|
||||
"description": self.DESCRIPTION,
|
||||
"default_response_class": JSONResponse,
|
||||
"version": self.VERSION,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def forgot_link(cls, forgot_key):
|
||||
"""
|
||||
Generate a forgot password link.
|
||||
"""
|
||||
return cls.FORGOT_LINK + forgot_key
|
||||
|
||||
model_config = SettingsConfigDict(env_prefix="API_")
|
||||
|
||||
|
||||
api_config = Configs()
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
from fastapi import FastAPI, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import RedirectResponse
|
||||
from event_clusters import RouterCluster, EventCluster
|
||||
from config import api_config
|
||||
|
||||
cluster_is_set = False
|
||||
|
||||
|
||||
def create_events_if_any_cluster_set():
|
||||
import events
|
||||
|
||||
global cluster_is_set
|
||||
if not events.__all__ or cluster_is_set:
|
||||
return
|
||||
|
||||
router_cluster_stack: list[RouterCluster] = [getattr(events, e, None) for e in events.__all__]
|
||||
for router_cluster in router_cluster_stack:
|
||||
event_cluster_stack: list[EventCluster] = list(router_cluster.event_clusters.values())
|
||||
for event_cluster in event_cluster_stack:
|
||||
try:
|
||||
event_cluster.set_events_to_database()
|
||||
except Exception as e:
|
||||
print(f"Error creating event cluster: {e}")
|
||||
|
||||
cluster_is_set = True
|
||||
|
||||
|
||||
def create_app():
|
||||
from open_api_creator import create_openapi_schema
|
||||
from middlewares.token_middleware import token_middleware
|
||||
from create_route import RouteRegisterController
|
||||
from endpoints.routes import get_routes
|
||||
|
||||
application = FastAPI(**api_config.api_info)
|
||||
application.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=api_config.ALLOW_ORIGINS,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
@application.middleware("http")
|
||||
async def add_token_middleware(request: Request, call_next):
|
||||
return await token_middleware(request, call_next)
|
||||
|
||||
@application.get("/", description="Redirect Route", include_in_schema=False)
|
||||
async def redirect_to_docs():
|
||||
return RedirectResponse(url="/docs")
|
||||
|
||||
route_register = RouteRegisterController(app=application, router_list=get_routes())
|
||||
application = route_register.register_routes()
|
||||
|
||||
create_events_if_any_cluster_set()
|
||||
application.openapi = lambda _=application: create_openapi_schema(_)
|
||||
return application
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
from typing import List
|
||||
from fastapi import APIRouter, FastAPI
|
||||
|
||||
|
||||
class RouteRegisterController:
|
||||
|
||||
def __init__(self, app: FastAPI, router_list: List[APIRouter]):
|
||||
self.router_list = router_list
|
||||
self.app = app
|
||||
|
||||
@staticmethod
|
||||
def add_router_with_event_to_database(router: APIRouter):
|
||||
from schemas import EndpointRestriction
|
||||
|
||||
# Endpoint operation_id is static now if record exits update() record else create()
|
||||
with EndpointRestriction.new_session() as db_session:
|
||||
for route in router.routes:
|
||||
route_path = str(getattr(route, "path"))
|
||||
route_summary = str(getattr(route, "name"))
|
||||
operation_id = getattr(route, "operation_id", None)
|
||||
if not operation_id:
|
||||
raise ValueError(f"Route {route_path} operation_id is not found")
|
||||
|
||||
for route_method in [
|
||||
method.lower() for method in getattr(route, "methods")
|
||||
]:
|
||||
methods = [method.lower() for method in getattr(route, "methods")]
|
||||
print('methods count : ', len(methods))
|
||||
print(dict(
|
||||
route_method=route_method,
|
||||
operation_uu_id=operation_id,
|
||||
route_path=route_path,
|
||||
route_summary=route_summary,
|
||||
))
|
||||
# add_or_update_dict = dict(
|
||||
# endpoint_method=route_method,
|
||||
# endpoint_name=route_path,
|
||||
# endpoint_desc=route_summary.replace("_", " "),
|
||||
# endpoint_function=route_summary,
|
||||
# operation_uu_id=operation_id,
|
||||
# is_confirmed=True,
|
||||
# )
|
||||
# endpoint_restriction_found = EndpointRestriction.filter_one_system(
|
||||
# EndpointRestriction.operation_uu_id == operation_id, db=db_session,
|
||||
# ).data
|
||||
# if endpoint_restriction_found:
|
||||
# endpoint_restriction_found.update(**add_or_update_dict, db=db_session)
|
||||
# endpoint_restriction_found.save(db=db_session)
|
||||
# else:
|
||||
# restriction = EndpointRestriction.find_or_create(**add_or_update_dict, db=db_session)
|
||||
# if restriction.meta_data.created:
|
||||
# restriction.save(db=db_session)
|
||||
|
||||
def register_routes(self):
|
||||
for router in self.router_list:
|
||||
self.app.include_router(router)
|
||||
self.add_router_with_event_to_database(router)
|
||||
return self.app
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
from typing import Optional, Type
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class EventCluster:
|
||||
"""
|
||||
EventCluster
|
||||
"""
|
||||
def __repr__(self):
|
||||
return f"EventCluster(name={self.name})"
|
||||
|
||||
def __init__(self, endpoint_uu_id: str, name: str):
|
||||
self.endpoint_uu_id = endpoint_uu_id
|
||||
self.name = name
|
||||
self.events: list["Event"] = []
|
||||
|
||||
def add_event(self, event: "Event"):
|
||||
"""
|
||||
Add an event to the cluster
|
||||
"""
|
||||
if event.key not in [e.key for e in self.events]:
|
||||
self.events.append(event)
|
||||
|
||||
def get_event(self, event_key: str):
|
||||
"""
|
||||
Get an event by its key
|
||||
"""
|
||||
|
||||
for event in self.events:
|
||||
if event.key == event_key:
|
||||
return event
|
||||
return None
|
||||
|
||||
def set_events_to_database(self):
|
||||
from schemas import Events, EndpointRestriction
|
||||
|
||||
with Events.new_session() as db_session:
|
||||
# if to_save_endpoint := EndpointRestriction.filter_one(
|
||||
# EndpointRestriction.operation_uu_id == self.endpoint_uu_id,
|
||||
# db=db_session,
|
||||
# ).data:
|
||||
for event in self.events:
|
||||
event_dict_to_save = dict(
|
||||
function_code=event.key,
|
||||
function_class=event.name,
|
||||
description=event.description,
|
||||
endpoint_code=self.endpoint_uu_id,
|
||||
endpoint_id=to_save_endpoint.id,
|
||||
endpoint_uu_id=str(to_save_endpoint.uu_id),
|
||||
is_confirmed=True,
|
||||
db=db_session,
|
||||
)
|
||||
print('event_dict_to_save', event_dict_to_save)
|
||||
# event_found = Events.filter_one(
|
||||
# Events.function_code == event_dict_to_save["function_code"],
|
||||
# db=db_session,
|
||||
# ).data
|
||||
# if event_found:
|
||||
# event_found.update(**event_dict_to_save)
|
||||
# event_found.save(db=db_session)
|
||||
# else:
|
||||
# event_to_save_database = Events.find_or_create(
|
||||
# **event_dict_to_save,
|
||||
# include_args=[
|
||||
# Events.function_code,
|
||||
# Events.function_class,
|
||||
# Events.endpoint_code,
|
||||
# Events.endpoint_uu_id,
|
||||
# ]
|
||||
# )
|
||||
# if event_to_save_database.meta_data.created:
|
||||
# print(f"UUID: {event_to_save_database.uu_id} event is saved to {to_save_endpoint.uu_id}")
|
||||
# event_to_save_database.save(db=db_session)
|
||||
|
||||
def match_event(self, event_key: str) -> "Event":
|
||||
"""
|
||||
Match an event by its key
|
||||
"""
|
||||
if event := self.get_event(event_key=event_key):
|
||||
return event
|
||||
raise ValueError("Event key not found")
|
||||
|
||||
|
||||
class Event:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
key: str,
|
||||
request_validator: Optional[Type[BaseModel]] = None,
|
||||
response_validator: Optional[Type[BaseModel]] = None,
|
||||
description: str = "",
|
||||
):
|
||||
self.name = name
|
||||
self.key = key
|
||||
self.request_validator = request_validator
|
||||
self.response_validator = response_validator
|
||||
self.description = description
|
||||
|
||||
def event_callable(self):
|
||||
"""
|
||||
Example callable method
|
||||
"""
|
||||
print(self.name)
|
||||
return {}
|
||||
|
||||
|
||||
class RouterCluster:
|
||||
"""
|
||||
RouterCluster
|
||||
"""
|
||||
|
||||
def __repr__(self):
|
||||
return f"RouterCluster(name={self.name})"
|
||||
|
||||
def __init__(self, name: str):
|
||||
self.name = name
|
||||
self.event_clusters: dict[str, EventCluster] = {}
|
||||
|
||||
def set_event_cluster(self, event_cluster: EventCluster):
|
||||
"""
|
||||
Add an event cluster to the set
|
||||
"""
|
||||
print("Setting event cluster:", event_cluster.name)
|
||||
if event_cluster.name not in self.event_clusters:
|
||||
self.event_clusters[event_cluster.name] = event_cluster
|
||||
|
||||
def get_event_cluster(self, event_cluster_name: str) -> EventCluster:
|
||||
"""
|
||||
Get an event cluster by its name
|
||||
"""
|
||||
if event_cluster_name not in self.event_clusters:
|
||||
raise ValueError("Event cluster not found")
|
||||
return self.event_clusters[event_cluster_name]
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
from typing import Any, Dict
|
||||
from fastapi import FastAPI
|
||||
from fastapi.routing import APIRoute
|
||||
from fastapi.openapi.utils import get_openapi
|
||||
|
||||
from endpoints.routes import get_safe_endpoint_urls
|
||||
from config import api_config
|
||||
|
||||
|
||||
class OpenAPISchemaCreator:
|
||||
"""
|
||||
OpenAPI schema creator and customizer for FastAPI applications.
|
||||
"""
|
||||
|
||||
def __init__(self, app: FastAPI):
|
||||
"""
|
||||
Initialize the OpenAPI schema creator.
|
||||
|
||||
Args:
|
||||
app: FastAPI application instance
|
||||
"""
|
||||
self.app = app
|
||||
self.safe_endpoint_list: list[tuple[str, str]] = get_safe_endpoint_urls()
|
||||
self.routers_list = self.app.routes
|
||||
|
||||
@staticmethod
|
||||
def create_security_schemes() -> Dict[str, Any]:
|
||||
"""
|
||||
Create security scheme definitions.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: Security scheme configurations
|
||||
"""
|
||||
|
||||
return {
|
||||
"BearerAuth": {
|
||||
"type": "apiKey",
|
||||
"in": "header",
|
||||
"name": api_config.ACCESS_TOKEN_TAG,
|
||||
"description": "Enter: **'Bearer <JWT>'**, where JWT is the access token",
|
||||
}
|
||||
}
|
||||
|
||||
def configure_route_security(
|
||||
self, path: str, method: str, schema: Dict[str, Any]
|
||||
) -> None:
|
||||
"""
|
||||
Configure security requirements for a specific route.
|
||||
|
||||
Args:
|
||||
path: Route path
|
||||
method: HTTP method
|
||||
schema: OpenAPI schema to modify
|
||||
"""
|
||||
if not schema.get("paths", {}).get(path, {}).get(method):
|
||||
return
|
||||
|
||||
# Check if endpoint is in safe list
|
||||
endpoint_path = f"{path}:{method}"
|
||||
list_of_safe_endpoints = [
|
||||
f"{e[0]}:{str(e[1]).lower()}" for e in self.safe_endpoint_list
|
||||
]
|
||||
if endpoint_path not in list_of_safe_endpoints:
|
||||
if "security" not in schema["paths"][path][method]:
|
||||
schema["paths"][path][method]["security"] = []
|
||||
schema["paths"][path][method]["security"].append({"BearerAuth": []})
|
||||
|
||||
def create_schema(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Create the complete OpenAPI schema.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: Complete OpenAPI schema
|
||||
"""
|
||||
openapi_schema = get_openapi(
|
||||
title=api_config.TITLE,
|
||||
description=api_config.DESCRIPTION,
|
||||
version=api_config.VERSION,
|
||||
routes=self.app.routes,
|
||||
)
|
||||
|
||||
# Add security schemes
|
||||
if "components" not in openapi_schema:
|
||||
openapi_schema["components"] = {}
|
||||
|
||||
openapi_schema["components"]["securitySchemes"] = self.create_security_schemes()
|
||||
|
||||
# Configure route security and responses
|
||||
for route in self.app.routes:
|
||||
if isinstance(route, APIRoute) and route.include_in_schema:
|
||||
path = str(route.path)
|
||||
methods = [method.lower() for method in route.methods]
|
||||
for method in methods:
|
||||
self.configure_route_security(path, method, openapi_schema)
|
||||
|
||||
# Add custom documentation extensions
|
||||
openapi_schema["x-documentation"] = {
|
||||
"postman_collection": "/docs/postman",
|
||||
"swagger_ui": "/docs",
|
||||
"redoc": "/redoc",
|
||||
}
|
||||
return openapi_schema
|
||||
|
||||
|
||||
def create_openapi_schema(app: FastAPI) -> Dict[str, Any]:
|
||||
"""
|
||||
Create OpenAPI schema for a FastAPI application.
|
||||
|
||||
Args:
|
||||
app: FastAPI application instance
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: Complete OpenAPI schema
|
||||
"""
|
||||
creator = OpenAPISchemaCreator(app)
|
||||
return creator.create_schema()
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import time
|
||||
|
||||
while True:
|
||||
|
||||
time.sleep(10)
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
from fastapi import Request, status
|
||||
from fastapi.responses import JSONResponse
|
||||
from config import api_config
|
||||
|
||||
from endpoints.routes import get_safe_endpoint_urls
|
||||
|
||||
|
||||
async def token_middleware(request: Request, call_next):
|
||||
base_url = request.url.path
|
||||
safe_endpoints = [_[0] for _ in get_safe_endpoint_urls()]
|
||||
if base_url in safe_endpoints:
|
||||
return await call_next(request)
|
||||
|
||||
token = request.headers.get(api_config.ACCESS_TOKEN_TAG, None)
|
||||
if not token:
|
||||
return JSONResponse(
|
||||
content={
|
||||
"error": "EYS_0002",
|
||||
},
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
)
|
||||
|
||||
response = await call_next(request)
|
||||
return response
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
from fastapi import Header, Request, Response
|
||||
from pydantic import BaseModel
|
||||
|
||||
from api_services.api_initializer.config import api_config
|
||||
|
||||
|
||||
class CommonHeaders(BaseModel):
|
||||
language: str | None = None
|
||||
domain: str | None = None
|
||||
timezone: str | None = None
|
||||
token: str | None = None
|
||||
request: Request | None = None
|
||||
response: Response | None = None
|
||||
operation_id: str | None = None
|
||||
|
||||
model_config = {
|
||||
"arbitrary_types_allowed": True
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def as_dependency(
|
||||
cls,
|
||||
request: Request,
|
||||
response: Response,
|
||||
language: str = Header(None, alias="language"),
|
||||
domain: str = Header(None, alias="domain"),
|
||||
tz: str = Header(None, alias="timezone"),
|
||||
):
|
||||
token = request.headers.get(api_config.ACCESS_TOKEN_TAG, None)
|
||||
|
||||
# Extract operation_id from the route
|
||||
operation_id = None
|
||||
if hasattr(request.scope.get("route"), "operation_id"):
|
||||
operation_id = request.scope.get("route").operation_id
|
||||
|
||||
return cls(
|
||||
language=language,
|
||||
domain=domain,
|
||||
timezone=tz,
|
||||
token=token,
|
||||
request=request,
|
||||
response=response,
|
||||
operation_id=operation_id,
|
||||
)
|
||||
|
||||
def get_headers_dict(self):
|
||||
"""Convert the headers to a dictionary format used in the application"""
|
||||
import uuid
|
||||
|
||||
return {
|
||||
"language": self.language or "",
|
||||
"domain": self.domain or "",
|
||||
"eys-ext": f"{str(uuid.uuid4())}",
|
||||
"tz": self.timezone or "GMT+3",
|
||||
"token": self.token,
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
|
||||
class BaseModelCore(BaseModel):
|
||||
|
||||
"""
|
||||
BaseModelCore
|
||||
model_dump override for alias support Users.name -> Table[Users] Field(alias="name")
|
||||
"""
|
||||
__abstract__ = True
|
||||
|
||||
class Config:
|
||||
validate_by_name = True
|
||||
use_enum_values = True
|
||||
|
||||
def model_dump(self, *args, **kwargs):
|
||||
data = super().model_dump(*args, **kwargs)
|
||||
return {self.__class__.model_fields[field].alias: value for field, value in data.items()}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
from .result import PaginationResult
|
||||
from .base import PostgresResponseSingle
|
||||
from pydantic import BaseModel
|
||||
from typing import Any
|
||||
|
||||
class EndpointResponse(BaseModel):
|
||||
"""Endpoint response model."""
|
||||
|
||||
completed: bool = True
|
||||
message: str = "Success"
|
||||
pagination_result: PaginationResult
|
||||
|
||||
@property
|
||||
def response(self):
|
||||
"""Convert response to dictionary format."""
|
||||
result_data = getattr(self.pagination_result, "data", None)
|
||||
if not result_data:
|
||||
return {
|
||||
"completed": False,
|
||||
"message": "MSG0004-NODATA",
|
||||
"data": None,
|
||||
"pagination": None,
|
||||
}
|
||||
result_pagination = getattr(self.pagination_result, "pagination", None)
|
||||
if not result_pagination:
|
||||
raise ValueError("Invalid pagination result pagination.")
|
||||
pagination_dict = getattr(result_pagination, "as_dict", None)
|
||||
if not pagination_dict:
|
||||
raise ValueError("Invalid pagination result as_dict.")
|
||||
return {
|
||||
"completed": self.completed,
|
||||
"message": self.message,
|
||||
"data": result_data,
|
||||
"pagination": pagination_dict,
|
||||
}
|
||||
|
||||
|
||||
class CreateEndpointResponse(BaseModel):
|
||||
"""Create endpoint response model."""
|
||||
|
||||
completed: bool = True
|
||||
message: str = "Success"
|
||||
data: PostgresResponseSingle
|
||||
|
||||
@property
|
||||
def response(self):
|
||||
"""Convert response to dictionary format."""
|
||||
return {
|
||||
"completed": self.completed,
|
||||
"message": self.message,
|
||||
"data": self.data.data,
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
"""
|
||||
Response handler for PostgreSQL query results.
|
||||
|
||||
This module provides a wrapper class for SQLAlchemy query results,
|
||||
adding convenience methods for accessing data and managing query state.
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, Optional, TypeVar, Generic, Union
|
||||
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.orm import Query
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class PostgresResponse(Generic[T]):
|
||||
"""
|
||||
Wrapper for PostgreSQL/SQLAlchemy query results.
|
||||
|
||||
Properties:
|
||||
count: Total count of results
|
||||
query: Get query object
|
||||
as_dict: Convert response to dictionary format
|
||||
"""
|
||||
|
||||
def __init__(self, query: Query, base_model: Optional[BaseModel] = None):
|
||||
self._query = query
|
||||
self._count: Optional[int] = None
|
||||
self._base_model: Optional[BaseModel] = base_model
|
||||
self.single = False
|
||||
|
||||
@property
|
||||
def query(self) -> Query:
|
||||
"""Get query object."""
|
||||
return self._query
|
||||
|
||||
@property
|
||||
def data(self) -> Union[list[T], T]:
|
||||
"""Get query object."""
|
||||
return self._query.all()
|
||||
|
||||
@property
|
||||
def count(self) -> int:
|
||||
"""Get query object."""
|
||||
return self._query.count()
|
||||
|
||||
@property
|
||||
def to_dict(self, **kwargs) -> list[dict]:
|
||||
"""Get query object."""
|
||||
if self._base_model:
|
||||
return [self._base_model(**item.to_dict()).model_dump(**kwargs) for item in self.data]
|
||||
return [item.to_dict() for item in self.data]
|
||||
|
||||
@property
|
||||
def as_dict(self) -> Dict[str, Any]:
|
||||
"""Convert response to dictionary format."""
|
||||
return {
|
||||
"query": str(self.query),
|
||||
"count": self.count,
|
||||
"data": self.to_dict,
|
||||
}
|
||||
|
||||
|
||||
class PostgresResponseSingle(Generic[T]):
|
||||
"""
|
||||
Wrapper for PostgreSQL/SQLAlchemy query results.
|
||||
|
||||
Properties:
|
||||
count: Total count of results
|
||||
query: Get query object
|
||||
as_dict: Convert response to dictionary format
|
||||
data: Get query object
|
||||
"""
|
||||
|
||||
def __init__(self, query: Query, base_model: Optional[BaseModel] = None):
|
||||
self._query = query
|
||||
self._count: Optional[int] = None
|
||||
self._base_model: Optional[BaseModel] = base_model
|
||||
self.single = True
|
||||
|
||||
@property
|
||||
def query(self) -> Query:
|
||||
"""Get query object."""
|
||||
return self._query
|
||||
|
||||
@property
|
||||
def to_dict(self, **kwargs) -> dict:
|
||||
"""Get query object."""
|
||||
if self._base_model:
|
||||
return self._base_model(**self._query.first().to_dict()).model_dump(**kwargs)
|
||||
return self._query.first().to_dict()
|
||||
|
||||
@property
|
||||
def data(self) -> T:
|
||||
"""Get query object."""
|
||||
return self._query.first()
|
||||
|
||||
@property
|
||||
def count(self) -> int:
|
||||
"""Get query object."""
|
||||
return self._query.count()
|
||||
|
||||
@property
|
||||
def as_dict(self) -> Dict[str, Any]:
|
||||
"""Convert response to dictionary format."""
|
||||
return {"query": str(self.query),"data": self.to_dict, "count": self.count}
|
||||
|
||||
|
||||
class ResultQueryJoin:
|
||||
"""
|
||||
ResultQueryJoin
|
||||
params:
|
||||
list_of_instrumented_attributes: list of instrumented attributes
|
||||
query: query object
|
||||
"""
|
||||
|
||||
def __init__(self, list_of_instrumented_attributes, query):
|
||||
"""Initialize ResultQueryJoin"""
|
||||
self.list_of_instrumented_attributes = list_of_instrumented_attributes
|
||||
self._query = query
|
||||
|
||||
@property
|
||||
def query(self):
|
||||
"""Get query object."""
|
||||
return self._query
|
||||
|
||||
@property
|
||||
def to_dict(self):
|
||||
"""Convert response to dictionary format."""
|
||||
list_of_dictionaries, result = [], dict()
|
||||
for user_orders_shipping_iter in self.query.all():
|
||||
for index, instrumented_attribute_iter in enumerate(self.list_of_instrumented_attributes):
|
||||
result[str(instrumented_attribute_iter)] = user_orders_shipping_iter[index]
|
||||
list_of_dictionaries.append(result)
|
||||
return list_of_dictionaries
|
||||
|
||||
@property
|
||||
def count(self):
|
||||
"""Get count of query."""
|
||||
return self.query.count()
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
"""Get query object."""
|
||||
return self.query.all()
|
||||
|
||||
@property
|
||||
def as_dict(self):
|
||||
"""Convert response to dictionary format."""
|
||||
return {"query": str(self.query), "data": self.data, "count": self.count}
|
||||
|
||||
|
||||
class ResultQueryJoinSingle:
|
||||
"""
|
||||
ResultQueryJoinSingle
|
||||
params:
|
||||
list_of_instrumented_attributes: list of instrumented attributes
|
||||
query: query object
|
||||
"""
|
||||
|
||||
def __init__(self, list_of_instrumented_attributes, query):
|
||||
"""Initialize ResultQueryJoinSingle"""
|
||||
self.list_of_instrumented_attributes = list_of_instrumented_attributes
|
||||
self._query = query
|
||||
|
||||
@property
|
||||
def query(self):
|
||||
"""Get query object."""
|
||||
return self._query
|
||||
|
||||
@property
|
||||
def to_dict(self):
|
||||
"""Convert response to dictionary format."""
|
||||
data, result = self.query.first(), dict()
|
||||
for index, instrumented_attribute_iter in enumerate(self.list_of_instrumented_attributes):
|
||||
result[str(instrumented_attribute_iter)] = data[index]
|
||||
return result
|
||||
|
||||
@property
|
||||
def count(self):
|
||||
"""Get count of query."""
|
||||
return self.query.count()
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
"""Get query object."""
|
||||
return self._query.first()
|
||||
|
||||
@property
|
||||
def as_dict(self):
|
||||
"""Convert response to dictionary format."""
|
||||
return {"query": str(self.query), "data": self.data, "count": self.count}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
|
||||
|
||||
class UserPydantic(BaseModel):
|
||||
|
||||
username: str = Field(..., alias='user.username')
|
||||
account_balance: float = Field(..., alias='user.account_balance')
|
||||
preferred_category_id: Optional[int] = Field(None, alias='user.preferred_category_id')
|
||||
last_ordered_product_id: Optional[int] = Field(None, alias='user.last_ordered_product_id')
|
||||
supplier_rating_id: Optional[int] = Field(None, alias='user.supplier_rating_id')
|
||||
other_rating_id: Optional[int] = Field(None, alias='product.supplier_rating_id')
|
||||
id: int = Field(..., alias='user.id')
|
||||
|
||||
class Config:
|
||||
validate_by_name = True
|
||||
use_enum_values = True
|
||||
|
||||
def model_dump(self, *args, **kwargs):
|
||||
data = super().model_dump(*args, **kwargs)
|
||||
return {self.__class__.model_fields[field].alias: value for field, value in data.items()}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
from typing import Any, Dict, Optional, Union, TypeVar, Type
|
||||
from sqlalchemy import desc, asc
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .base import PostgresResponse
|
||||
|
||||
# Type variable for class methods returning self
|
||||
T = TypeVar("T", bound="BaseModel")
|
||||
|
||||
|
||||
class PaginateConfig:
|
||||
"""
|
||||
Configuration for pagination settings.
|
||||
|
||||
Attributes:
|
||||
DEFAULT_SIZE: Default number of items per page (10)
|
||||
MIN_SIZE: Minimum allowed page size (10)
|
||||
MAX_SIZE: Maximum allowed page size (40)
|
||||
"""
|
||||
|
||||
DEFAULT_SIZE = 10
|
||||
MIN_SIZE = 5
|
||||
MAX_SIZE = 100
|
||||
|
||||
|
||||
class ListOptions(BaseModel):
|
||||
"""
|
||||
Query for list option abilities
|
||||
"""
|
||||
|
||||
page: Optional[int] = 1
|
||||
size: Optional[int] = 10
|
||||
orderField: Optional[Union[tuple[str], list[str]]] = ["uu_id"]
|
||||
orderType: Optional[Union[tuple[str], list[str]]] = ["asc"]
|
||||
# include_joins: Optional[list] = None
|
||||
|
||||
|
||||
class PaginateOnly(ListOptions):
|
||||
"""
|
||||
Query for list option abilities
|
||||
"""
|
||||
|
||||
query: Optional[dict] = None
|
||||
|
||||
|
||||
class PaginationConfig(BaseModel):
|
||||
"""
|
||||
Configuration for pagination settings.
|
||||
|
||||
Attributes:
|
||||
page: Current page number (default: 1)
|
||||
size: Items per page (default: 10)
|
||||
orderField: Field to order by (default: "created_at")
|
||||
orderType: Order direction (default: "desc")
|
||||
"""
|
||||
|
||||
page: int = 1
|
||||
size: int = 10
|
||||
orderField: Optional[Union[tuple[str], list[str]]] = ["created_at"]
|
||||
orderType: Optional[Union[tuple[str], list[str]]] = ["desc"]
|
||||
|
||||
def __init__(self, **data):
|
||||
super().__init__(**data)
|
||||
if self.orderField is None:
|
||||
self.orderField = ["created_at"]
|
||||
if self.orderType is None:
|
||||
self.orderType = ["desc"]
|
||||
|
||||
|
||||
default_paginate_config = PaginateConfig()
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
from .pagination import default_paginate_config
|
||||
from .base import PostgresResponse
|
||||
from typing import Optional, Union
|
||||
from sqlalchemy.orm import Query
|
||||
from pydantic import BaseModel
|
||||
|
||||
class Pagination:
|
||||
"""
|
||||
Handles pagination logic for query results.
|
||||
|
||||
Manages page size, current page, ordering, and calculates total pages
|
||||
and items based on the data source.
|
||||
|
||||
Attributes:
|
||||
DEFAULT_SIZE: Default number of items per page (10)
|
||||
MIN_SIZE: Minimum allowed page size (10)
|
||||
MAX_SIZE: Maximum allowed page size (40)
|
||||
"""
|
||||
|
||||
DEFAULT_SIZE = default_paginate_config.DEFAULT_SIZE
|
||||
MIN_SIZE = default_paginate_config.MIN_SIZE
|
||||
MAX_SIZE = default_paginate_config.MAX_SIZE
|
||||
|
||||
def __init__(self, data: PostgresResponse):
|
||||
self.data = data
|
||||
self.size: int = self.DEFAULT_SIZE
|
||||
self.page: int = 1
|
||||
self.orderField: Optional[Union[tuple[str], list[str]]] = ["uu_id"]
|
||||
self.orderType: Optional[Union[tuple[str], list[str]]] = ["asc"]
|
||||
self.page_count: int = 1
|
||||
self.total_count: int = 0
|
||||
self.all_count: int = 0
|
||||
self.total_pages: int = 1
|
||||
self._update_page_counts()
|
||||
|
||||
def change(self, **kwargs) -> None:
|
||||
"""Update pagination settings from config."""
|
||||
config = PaginationConfig(**kwargs)
|
||||
self.size = (
|
||||
config.size
|
||||
if self.MIN_SIZE <= config.size <= self.MAX_SIZE
|
||||
else self.DEFAULT_SIZE
|
||||
)
|
||||
self.page = config.page
|
||||
self.orderField = config.order_field
|
||||
self.orderType = config.order_type
|
||||
self._update_page_counts()
|
||||
|
||||
def feed(self, data: PostgresResponse) -> None:
|
||||
"""Calculate pagination based on data source."""
|
||||
self.data = data
|
||||
self._update_page_counts()
|
||||
|
||||
def _update_page_counts(self) -> None:
|
||||
"""Update page counts and validate current page."""
|
||||
if self.data:
|
||||
self.total_count = self.data.count
|
||||
self.all_count = self.data.total_count
|
||||
|
||||
self.size = (
|
||||
self.size
|
||||
if self.MIN_SIZE <= self.size <= self.MAX_SIZE
|
||||
else self.DEFAULT_SIZE
|
||||
)
|
||||
self.total_pages = max(1, (self.total_count + self.size - 1) // self.size)
|
||||
self.page = max(1, min(self.page, self.total_pages))
|
||||
self.page_count = (
|
||||
self.total_count % self.size
|
||||
if self.page == self.total_pages and self.total_count % self.size
|
||||
else self.size
|
||||
)
|
||||
|
||||
def refresh(self) -> None:
|
||||
"""Reset pagination state to defaults."""
|
||||
self._update_page_counts()
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Reset pagination state to defaults."""
|
||||
self.size = self.DEFAULT_SIZE
|
||||
self.page = 1
|
||||
self.orderField = "uu_id"
|
||||
self.orderType = "asc"
|
||||
|
||||
@property
|
||||
def next_available(self) -> bool:
|
||||
if self.page < self.total_pages:
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def back_available(self) -> bool:
|
||||
if self.page > 1:
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def as_dict(self) -> Dict[str, Any]:
|
||||
"""Convert pagination state to dictionary format."""
|
||||
self.refresh()
|
||||
return {
|
||||
"size": self.size,
|
||||
"page": self.page,
|
||||
"allCount": self.all_count,
|
||||
"totalCount": self.total_count,
|
||||
"totalPages": self.total_pages,
|
||||
"pageCount": self.page_count,
|
||||
"orderField": self.orderField,
|
||||
"orderType": self.orderType,
|
||||
"next": self.next_available,
|
||||
"back": self.back_available,
|
||||
}
|
||||
|
||||
|
||||
class PaginationResult:
|
||||
"""
|
||||
Result of a paginated query.
|
||||
|
||||
Contains the query result and pagination state.
|
||||
data: PostgresResponse of query results
|
||||
pagination: Pagination state
|
||||
|
||||
Attributes:
|
||||
_query: Original query object
|
||||
pagination: Pagination state
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
data: PostgresResponse,
|
||||
pagination: Pagination,
|
||||
response_model: Type[T] = None,
|
||||
):
|
||||
self._query = data.query
|
||||
self.pagination = pagination
|
||||
self.response_type = data.is_list
|
||||
self.limit = self.pagination.size
|
||||
self.offset = self.pagination.size * (self.pagination.page - 1)
|
||||
self.order_by = self.pagination.orderField
|
||||
self.response_model = response_model
|
||||
|
||||
def dynamic_order_by(self):
|
||||
"""
|
||||
Dynamically order a query by multiple fields.
|
||||
Returns:
|
||||
Ordered query object.
|
||||
"""
|
||||
if not len(self.order_by) == len(self.pagination.orderType):
|
||||
raise ValueError(
|
||||
"Order by fields and order types must have the same length."
|
||||
)
|
||||
order_criteria = zip(self.order_by, self.pagination.orderType)
|
||||
for field, direction in order_criteria:
|
||||
if hasattr(self._query.column_descriptions[0]["entity"], field):
|
||||
if direction.lower().startswith("d"):
|
||||
self._query = self._query.order_by(
|
||||
desc(
|
||||
getattr(self._query.column_descriptions[0]["entity"], field)
|
||||
)
|
||||
)
|
||||
else:
|
||||
self._query = self._query.order_by(
|
||||
asc(
|
||||
getattr(self._query.column_descriptions[0]["entity"], field)
|
||||
)
|
||||
)
|
||||
return self._query
|
||||
|
||||
@property
|
||||
def data(self) -> Union[list | dict]:
|
||||
"""Get query object."""
|
||||
query_ordered = self.dynamic_order_by()
|
||||
query_paginated = query_ordered.limit(self.limit).offset(self.offset)
|
||||
queried_data = (
|
||||
query_paginated.all() if self.response_type else query_paginated.first()
|
||||
)
|
||||
data = (
|
||||
[result.get_dict() for result in queried_data]
|
||||
if self.response_type
|
||||
else queried_data.get_dict()
|
||||
)
|
||||
if self.response_model:
|
||||
return [self.response_model(**item).model_dump() for item in data]
|
||||
return data
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
[project]
|
||||
name = "api-services"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"alembic>=1.15.2",
|
||||
"arrow>=1.3.0",
|
||||
"cryptography>=44.0.2",
|
||||
"faker>=37.1.0",
|
||||
"fastapi>=0.115.12",
|
||||
"pandas>=2.2.3",
|
||||
"prometheus-fastapi-instrumentator>=7.1.0",
|
||||
"psycopg2-binary>=2.9.10",
|
||||
"pydantic-settings>=2.8.1",
|
||||
"pymongo>=4.11.3",
|
||||
"pytest>=8.3.5",
|
||||
"redbox>=0.2.1",
|
||||
"redis>=5.2.1",
|
||||
"redmail>=0.6.0",
|
||||
"requests>=2.32.3",
|
||||
"sqlalchemy-mixins>=2.0.5",
|
||||
"textdistance>=4.6.3",
|
||||
"unidecode>=1.3.8",
|
||||
"uvicorn>=0.34.0",
|
||||
]
|
||||
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
from schemas.account.account import (
|
||||
AccountBooks,
|
||||
AccountCodes,
|
||||
AccountCodeParser,
|
||||
AccountMaster,
|
||||
AccountDetail,
|
||||
AccountRecordExchanges,
|
||||
AccountRecords,
|
||||
)
|
||||
from schemas.account.iban import (
|
||||
BuildIbans,
|
||||
BuildIbanDescription,
|
||||
)
|
||||
from schemas.address.address import (
|
||||
RelationshipEmployee2PostCode,
|
||||
AddressPostcode,
|
||||
Addresses,
|
||||
AddressGeographicLocations,
|
||||
AddressCountry,
|
||||
AddressState,
|
||||
AddressCity,
|
||||
AddressDistrict,
|
||||
AddressLocality,
|
||||
AddressNeighborhood,
|
||||
AddressStreet,
|
||||
)
|
||||
from schemas.building.build import (
|
||||
BuildTypes,
|
||||
Part2Employee,
|
||||
RelationshipEmployee2Build,
|
||||
Build,
|
||||
BuildParts,
|
||||
BuildLivingSpace,
|
||||
BuildManagement,
|
||||
BuildArea,
|
||||
BuildSites,
|
||||
BuildCompaniesProviding,
|
||||
BuildPersonProviding,
|
||||
)
|
||||
from schemas.building.decision_book import (
|
||||
BuildDecisionBook,
|
||||
BuildDecisionBookInvitations,
|
||||
BuildDecisionBookPerson,
|
||||
BuildDecisionBookPersonOccupants,
|
||||
BuildDecisionBookItems,
|
||||
BuildDecisionBookItemsUnapproved,
|
||||
BuildDecisionBookPayments,
|
||||
BuildDecisionBookLegal,
|
||||
BuildDecisionBookProjects,
|
||||
BuildDecisionBookProjectPerson,
|
||||
BuildDecisionBookProjectItems,
|
||||
)
|
||||
from schemas.building.budget import (
|
||||
DecisionBookBudgetBooks,
|
||||
DecisionBookBudgetCodes,
|
||||
DecisionBookBudgetMaster,
|
||||
DecisionBookBudgets,
|
||||
)
|
||||
from schemas.company.company import (
|
||||
Companies,
|
||||
RelationshipDutyCompany,
|
||||
)
|
||||
from schemas.company.employee import (
|
||||
Employees,
|
||||
EmployeesSalaries,
|
||||
EmployeeHistory,
|
||||
Staff,
|
||||
)
|
||||
from schemas.company.department import (
|
||||
Duty,
|
||||
Duties,
|
||||
Departments,
|
||||
)
|
||||
from schemas.event.event import (
|
||||
Modules,
|
||||
Services,
|
||||
Service2Events,
|
||||
Service2Application,
|
||||
Events,
|
||||
Event2Occupant,
|
||||
Event2Employee,
|
||||
Event2OccupantExtra,
|
||||
Event2EmployeeExtra,
|
||||
Applications,
|
||||
Application2Employee,
|
||||
Application2Occupant,
|
||||
Application2EmployeeExtra,
|
||||
Application2OccupantExtra,
|
||||
)
|
||||
from schemas.identity.identity import (
|
||||
UsersTokens,
|
||||
OccupantTypes,
|
||||
People,
|
||||
Users,
|
||||
Credentials,
|
||||
RelationshipDutyPeople,
|
||||
Contracts,
|
||||
)
|
||||
from schemas.address.address import (
|
||||
Addresses,
|
||||
AddressCity,
|
||||
AddressStreet,
|
||||
AddressLocality,
|
||||
AddressDistrict,
|
||||
AddressNeighborhood,
|
||||
AddressState,
|
||||
AddressCountry,
|
||||
AddressPostcode,
|
||||
AddressGeographicLocations,
|
||||
RelationshipEmployee2PostCode,
|
||||
)
|
||||
from schemas.others.enums import (
|
||||
ApiEnumDropdown,
|
||||
)
|
||||
from schemas.rules.rules import (
|
||||
EndpointRestriction,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"AccountBooks",
|
||||
"AccountCodes",
|
||||
"AccountCodeParser",
|
||||
"AccountMaster",
|
||||
"AccountDetail",
|
||||
"AccountRecordExchanges",
|
||||
"AccountRecords",
|
||||
"BuildIbans",
|
||||
"BuildIbanDescription",
|
||||
"RelationshipEmployee2PostCode",
|
||||
"AddressPostcode",
|
||||
"Addresses",
|
||||
"AddressGeographicLocations",
|
||||
"AddressCountry",
|
||||
"AddressState",
|
||||
"AddressCity",
|
||||
"AddressDistrict",
|
||||
"AddressLocality",
|
||||
"AddressNeighborhood",
|
||||
"AddressStreet",
|
||||
"BuildTypes",
|
||||
"Part2Employee",
|
||||
"RelationshipEmployee2Build",
|
||||
"Build",
|
||||
"BuildParts",
|
||||
"BuildLivingSpace",
|
||||
"BuildManagement",
|
||||
"BuildArea",
|
||||
"BuildSites",
|
||||
"BuildCompaniesProviding",
|
||||
"BuildPersonProviding",
|
||||
"BuildDecisionBook",
|
||||
"BuildDecisionBookInvitations",
|
||||
"BuildDecisionBookPerson",
|
||||
"BuildDecisionBookPersonOccupants",
|
||||
"BuildDecisionBookItems",
|
||||
"BuildDecisionBookItemsUnapproved",
|
||||
"BuildDecisionBookPayments",
|
||||
"BuildDecisionBookLegal",
|
||||
"BuildDecisionBookProjects",
|
||||
"BuildDecisionBookProjectPerson",
|
||||
"BuildDecisionBookPersonOccupants",
|
||||
"BuildDecisionBookProjectItems",
|
||||
"DecisionBookBudgetBooks",
|
||||
"DecisionBookBudgetCodes",
|
||||
"DecisionBookBudgetMaster",
|
||||
"DecisionBookBudgets",
|
||||
"Companies",
|
||||
"RelationshipDutyCompany",
|
||||
"Employees",
|
||||
"EmployeesSalaries",
|
||||
"EmployeeHistory",
|
||||
"Staff",
|
||||
"Duty",
|
||||
"Duties",
|
||||
"Departments",
|
||||
"Modules",
|
||||
"Services",
|
||||
"Service2Events",
|
||||
"Events",
|
||||
"Event2Occupant",
|
||||
"Event2Employee",
|
||||
"Event2OccupantExtra",
|
||||
"Event2EmployeeExtra",
|
||||
"Applications",
|
||||
"Application2Employee",
|
||||
"Application2Occupant",
|
||||
"Addresses",
|
||||
"AddressCity",
|
||||
"AddressStreet",
|
||||
"AddressLocality",
|
||||
"AddressDistrict",
|
||||
"AddressNeighborhood",
|
||||
"AddressState",
|
||||
"AddressCountry",
|
||||
"AddressPostcode",
|
||||
"AddressGeographicLocations",
|
||||
"UsersTokens",
|
||||
"OccupantTypes",
|
||||
"People",
|
||||
"Users",
|
||||
"Credentials",
|
||||
"RelationshipDutyPeople",
|
||||
"RelationshipEmployee2PostCode",
|
||||
"Contracts",
|
||||
"ApiEnumDropdown",
|
||||
"EndpointRestriction",
|
||||
"RelationshipEmployee2Build",
|
||||
# ------------------------------------------------
|
||||
]
|
||||
|
|
@ -0,0 +1,575 @@
|
|||
from schemas.base_imports import (
|
||||
CrudCollection,
|
||||
String,
|
||||
Integer,
|
||||
Boolean,
|
||||
ForeignKey,
|
||||
Index,
|
||||
TIMESTAMP,
|
||||
Numeric,
|
||||
SmallInteger,
|
||||
mapped_column,
|
||||
Mapped,
|
||||
)
|
||||
|
||||
|
||||
class AccountBooks(CrudCollection):
|
||||
|
||||
__tablename__ = "account_books"
|
||||
__exclude__fields__ = []
|
||||
|
||||
country: Mapped[str] = mapped_column(String, nullable=False)
|
||||
branch_type: Mapped[int] = mapped_column(SmallInteger, server_default="0")
|
||||
|
||||
company_id: Mapped[int] = mapped_column(ForeignKey("companies.id"), nullable=False)
|
||||
company_uu_id: Mapped[str] = mapped_column(String, nullable=False)
|
||||
branch_id: Mapped[int] = mapped_column(ForeignKey("companies.id"))
|
||||
branch_uu_id: Mapped[str] = mapped_column(String, comment="Branch UU ID")
|
||||
|
||||
__table_args__ = (
|
||||
Index("account_companies_book_ndx_00", company_id, "expiry_starts"),
|
||||
{"comment": "Account Book Information"},
|
||||
)
|
||||
|
||||
|
||||
class AccountCodes(CrudCollection):
|
||||
|
||||
__tablename__ = "account_codes"
|
||||
__exclude__fields__ = []
|
||||
|
||||
account_code: Mapped[str] = mapped_column(
|
||||
String(48), nullable=False, comment="Account Code"
|
||||
)
|
||||
comment_line: Mapped[str] = mapped_column(
|
||||
String(128), nullable=False, comment="Comment Line"
|
||||
)
|
||||
|
||||
is_receive_or_debit: Mapped[bool] = mapped_column(Boolean)
|
||||
product_id: Mapped[int] = mapped_column(Integer, server_default="0")
|
||||
nvi_id: Mapped[str] = mapped_column(String(48), server_default="")
|
||||
status_id: Mapped[int] = mapped_column(SmallInteger, server_default="0")
|
||||
account_code_seperator: Mapped[str] = mapped_column(String(1), server_default=".")
|
||||
|
||||
system_id: Mapped[int] = mapped_column(SmallInteger, server_default="0")
|
||||
locked: Mapped[int] = mapped_column(SmallInteger, server_default="0")
|
||||
|
||||
company_id: Mapped[int] = mapped_column(ForeignKey("companies.id"))
|
||||
company_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Company UU ID"
|
||||
)
|
||||
customer_id: Mapped[int] = mapped_column(ForeignKey("companies.id"))
|
||||
customer_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Customer UU ID"
|
||||
)
|
||||
person_id: Mapped[int] = mapped_column(ForeignKey("people.id"))
|
||||
person_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Person UU ID"
|
||||
)
|
||||
|
||||
__table_args__ = ({"comment": "Account Code Information"},)
|
||||
|
||||
|
||||
class AccountCodeParser(CrudCollection):
|
||||
|
||||
__tablename__ = "account_code_parser"
|
||||
__exclude__fields__ = []
|
||||
|
||||
account_code_1: Mapped[str] = mapped_column(String, nullable=False, comment="Order")
|
||||
account_code_2: Mapped[str] = mapped_column(String, nullable=False, comment="Order")
|
||||
account_code_3: Mapped[str] = mapped_column(String, nullable=False, comment="Order")
|
||||
account_code_4: Mapped[str] = mapped_column(String, server_default="")
|
||||
account_code_5: Mapped[str] = mapped_column(String, server_default="")
|
||||
account_code_6: Mapped[str] = mapped_column(String, server_default="")
|
||||
|
||||
account_code_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("account_codes.id"), nullable=False
|
||||
)
|
||||
account_code_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Account Code UU ID"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("_account_code_parser_ndx_00", account_code_id),
|
||||
{"comment": "Account Code Parser Information"},
|
||||
)
|
||||
|
||||
@property
|
||||
def get_account_code(self):
|
||||
return f"{self.account_codes.account_code_seperator}".join(
|
||||
[
|
||||
getattr(self, f"account_code_{i}")
|
||||
for i in range(1, 7)
|
||||
if getattr(self, f"account_code_{i}")
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class AccountMaster(CrudCollection):
|
||||
"""
|
||||
AccountCodes class based on declarative_base and CrudCollection via session
|
||||
"""
|
||||
|
||||
__tablename__ = "account_master"
|
||||
__exclude__fields__ = []
|
||||
|
||||
doc_date: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True), nullable=False, comment="Document Date"
|
||||
)
|
||||
plug_type: Mapped[str] = mapped_column(String, nullable=False, comment="Plug Type")
|
||||
plug_number: Mapped[int] = mapped_column(
|
||||
Integer, nullable=False, comment="Plug Number"
|
||||
)
|
||||
|
||||
special_code: Mapped[str] = mapped_column(String(12), server_default="")
|
||||
authorization_code: Mapped[str] = mapped_column(String(12), server_default="")
|
||||
|
||||
doc_code: Mapped[str] = mapped_column(String(12), server_default="")
|
||||
doc_type: Mapped[int] = mapped_column(SmallInteger, server_default="0")
|
||||
|
||||
comment_line1: Mapped[str] = mapped_column(String, server_default="")
|
||||
comment_line2: Mapped[str] = mapped_column(String, server_default="")
|
||||
comment_line3: Mapped[str] = mapped_column(String, server_default="")
|
||||
comment_line4: Mapped[str] = mapped_column(String, server_default="")
|
||||
comment_line5: Mapped[str] = mapped_column(String, server_default="")
|
||||
comment_line6: Mapped[str] = mapped_column(String, server_default="")
|
||||
project_code: Mapped[str] = mapped_column(String(12), server_default="")
|
||||
module_no: Mapped[str] = mapped_column(String, server_default="")
|
||||
journal_no: Mapped[int] = mapped_column(Integer, server_default="0")
|
||||
|
||||
status_id: Mapped[int] = mapped_column(SmallInteger, server_default="0")
|
||||
canceled: Mapped[bool] = mapped_column(Boolean, server_default="0")
|
||||
print_count: Mapped[int] = mapped_column(SmallInteger, server_default="0")
|
||||
total_active: Mapped[float] = mapped_column(Numeric(20, 6), server_default="0")
|
||||
total_passive: Mapped[float] = mapped_column(Numeric(20, 6), server_default="0")
|
||||
total_active_1: Mapped[float] = mapped_column(Numeric(20, 6), server_default="0")
|
||||
total_passive_1: Mapped[float] = mapped_column(Numeric(20, 6), server_default="0")
|
||||
total_active_2: Mapped[float] = mapped_column(Numeric(20, 6), server_default="0")
|
||||
total_passive_2: Mapped[float] = mapped_column(Numeric(20, 6), server_default="0")
|
||||
total_active_3: Mapped[float] = mapped_column(Numeric(20, 6), server_default="0")
|
||||
total_passive_3: Mapped[float] = mapped_column(Numeric(20, 6), server_default="0")
|
||||
total_active_4: Mapped[float] = mapped_column(Numeric(20, 6), server_default="0")
|
||||
total_passive_4: Mapped[float] = mapped_column(Numeric(20, 6), server_default="0")
|
||||
cross_ref: Mapped[int] = mapped_column(Integer, server_default="0")
|
||||
data_center_id: Mapped[str] = mapped_column(String, server_default="")
|
||||
data_center_rec_num: Mapped[int] = mapped_column(Integer, server_default="0")
|
||||
|
||||
account_header_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("account_books.id"), nullable=False
|
||||
)
|
||||
account_header_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Account Header UU ID"
|
||||
)
|
||||
project_item_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_decision_book_projects.id")
|
||||
)
|
||||
project_item_uu_id: Mapped[str] = mapped_column(
|
||||
String, comment="Project Item UU ID"
|
||||
)
|
||||
department_id: Mapped[int] = mapped_column(ForeignKey("departments.id"))
|
||||
department_uu_id: Mapped[str] = mapped_column(String, comment="Department UU ID")
|
||||
|
||||
__table_args__ = (
|
||||
Index("_account_master_ndx_00", doc_date, account_header_id),
|
||||
{"comment": "Account Master Information"},
|
||||
)
|
||||
|
||||
|
||||
class AccountDetail(CrudCollection):
|
||||
"""
|
||||
AccountCodes class based on declarative_base and CrudCollection via session
|
||||
"""
|
||||
|
||||
__tablename__ = "account_detail"
|
||||
__exclude__fields__ = []
|
||||
__enum_list__ = [("plug_type", "AccountingReceiptTypes", "M")]
|
||||
|
||||
doc_date: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True), nullable=False, comment="Document Date"
|
||||
)
|
||||
line_no: Mapped[int] = mapped_column(
|
||||
SmallInteger, nullable=False, comment="Line Number"
|
||||
)
|
||||
receive_debit: Mapped[str] = mapped_column(
|
||||
String(1), nullable=False, comment="Receive Debit"
|
||||
)
|
||||
debit: Mapped[float] = mapped_column(
|
||||
Numeric(20, 6), nullable=False, comment="Debit"
|
||||
)
|
||||
|
||||
department: Mapped[str] = mapped_column(String(24), server_default="")
|
||||
special_code: Mapped[str] = mapped_column(String(12), server_default="")
|
||||
account_ref: Mapped[int] = mapped_column(Integer, server_default="0")
|
||||
account_fiche_ref: Mapped[int] = mapped_column(Integer, server_default="0")
|
||||
center_ref: Mapped[int] = mapped_column(Integer, server_default="0")
|
||||
general_code: Mapped[str] = mapped_column(String(32), server_default="")
|
||||
credit: Mapped[float] = mapped_column(Numeric(20, 6), server_default="0")
|
||||
currency_type: Mapped[str] = mapped_column(String(4), server_default="TL")
|
||||
exchange_rate: Mapped[float] = mapped_column(Numeric(20, 6), server_default="0")
|
||||
debit_cur: Mapped[float] = mapped_column(Numeric(20, 6), server_default="0")
|
||||
credit_cur: Mapped[float] = mapped_column(Numeric(20, 6), server_default="0")
|
||||
discount_cur: Mapped[float] = mapped_column(Numeric(20, 6), server_default="0")
|
||||
amount: Mapped[float] = mapped_column(Numeric(20, 6), server_default="0")
|
||||
cross_account_code: Mapped[float] = mapped_column(String(32), server_default="")
|
||||
inf_index: Mapped[float] = mapped_column(Numeric(20, 6), server_default="0")
|
||||
not_inflated: Mapped[int] = mapped_column(SmallInteger, server_default="0")
|
||||
not_calculated: Mapped[int] = mapped_column(SmallInteger, server_default="0")
|
||||
comment_line1: Mapped[str] = mapped_column(String(64), server_default="")
|
||||
comment_line2: Mapped[str] = mapped_column(String(64), server_default="")
|
||||
comment_line3: Mapped[str] = mapped_column(String(64), server_default="")
|
||||
comment_line4: Mapped[str] = mapped_column(String(64), server_default="")
|
||||
comment_line5: Mapped[str] = mapped_column(String(64), server_default="")
|
||||
comment_line6: Mapped[str] = mapped_column(String(64), server_default="")
|
||||
owner_acc_ref: Mapped[int] = mapped_column(Integer, server_default="0")
|
||||
from_where: Mapped[int] = mapped_column(Integer, server_default="0")
|
||||
orj_eid: Mapped[int] = mapped_column(Integer, server_default="0")
|
||||
canceled: Mapped[int] = mapped_column(SmallInteger, server_default="0")
|
||||
cross_ref: Mapped[int] = mapped_column(Integer, server_default="0")
|
||||
data_center_id: Mapped[str] = mapped_column(String, server_default="")
|
||||
data_center_rec_num: Mapped[int] = mapped_column(Integer, server_default="0")
|
||||
status_id: Mapped[int] = mapped_column(SmallInteger, server_default="0")
|
||||
|
||||
plug_type_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("api_enum_dropdown.id"), nullable=True
|
||||
)
|
||||
plug_type_uu_id = mapped_column(String, nullable=False, comment="Plug Type UU ID")
|
||||
account_header_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("account_books.id"), nullable=False
|
||||
)
|
||||
account_header_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Account Header UU ID"
|
||||
)
|
||||
account_code_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("account_codes.id"), nullable=False
|
||||
)
|
||||
account_code_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Account Code UU ID"
|
||||
)
|
||||
account_master_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("account_master.id"), nullable=False
|
||||
)
|
||||
account_master_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Account Master UU ID"
|
||||
)
|
||||
project_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_decision_book_projects.id")
|
||||
)
|
||||
project_uu_id: Mapped[str] = mapped_column(String, comment="Project UU ID")
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"_account_detail_ndx_00",
|
||||
account_master_id,
|
||||
doc_date,
|
||||
line_no,
|
||||
account_header_id,
|
||||
unique=True,
|
||||
),
|
||||
{"comment": "Account Detail Information"},
|
||||
)
|
||||
|
||||
|
||||
class AccountRecordExchanges(CrudCollection):
|
||||
|
||||
__tablename__ = "account_record_exchanges"
|
||||
__exclude__fields__ = []
|
||||
|
||||
are_currency: Mapped[str] = mapped_column(
|
||||
String(5), nullable=False, comment="Unit of Currency"
|
||||
)
|
||||
are_exchange_rate: Mapped[float] = mapped_column(
|
||||
Numeric(18, 6), nullable=False, server_default="1"
|
||||
)
|
||||
usd_exchange_rate_value: Mapped[float] = mapped_column(
|
||||
Numeric(18, 6),
|
||||
nullable=True,
|
||||
server_default="0",
|
||||
comment="It will be written by multiplying the usd exchange rate with the current value result.",
|
||||
)
|
||||
eur_exchange_rate_value: Mapped[float] = mapped_column(
|
||||
Numeric(18, 6),
|
||||
nullable=True,
|
||||
server_default="0",
|
||||
comment="It will be written by multiplying the eur exchange rate with the current value result.",
|
||||
)
|
||||
gbp_exchange_rate_value: Mapped[float] = mapped_column(
|
||||
Numeric(18, 6),
|
||||
nullable=True,
|
||||
server_default="0",
|
||||
comment="It will be written by multiplying the gpd exchange rate with the current value result.",
|
||||
)
|
||||
cny_exchange_rate_value: Mapped[float] = mapped_column(
|
||||
Numeric(18, 6),
|
||||
nullable=True,
|
||||
server_default="0",
|
||||
comment="It will be written by multiplying the cny exchange rate with the current value result.",
|
||||
)
|
||||
|
||||
account_records_id: Mapped[int] = mapped_column(ForeignKey("account_records.id"))
|
||||
account_records_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Account Record UU ID"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("_account_record_exchanges_ndx_00", account_records_id),
|
||||
{"comment": "Account Record Exchanges Information"},
|
||||
)
|
||||
|
||||
|
||||
class AccountRecords(CrudCollection):
|
||||
"""
|
||||
build_decision_book_id = kaydın sorumlu olduğu karar defteri
|
||||
send_company_id = kaydı gönderen firma, send_person_id = gönderen kişi
|
||||
customer_id = sorumlu kullanıcı bilgisi, company_id = sorumlu firma
|
||||
"""
|
||||
|
||||
__tablename__ = "account_records"
|
||||
__exclude__fields__ = []
|
||||
__enum_list__ = [
|
||||
("receive_debit", "DebitTypes", "D"),
|
||||
("budget_type", "BudgetType", "B"),
|
||||
]
|
||||
|
||||
iban: Mapped[str] = mapped_column(
|
||||
String(64), nullable=False, comment="IBAN Number of Bank"
|
||||
)
|
||||
bank_date: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True), nullable=False, comment="Bank Transaction Date"
|
||||
)
|
||||
|
||||
currency_value: Mapped[float] = mapped_column(
|
||||
Numeric(20, 6), nullable=False, comment="Currency Value"
|
||||
)
|
||||
bank_balance: Mapped[float] = mapped_column(
|
||||
Numeric(20, 6), nullable=False, comment="Bank Balance"
|
||||
)
|
||||
currency: Mapped[str] = mapped_column(
|
||||
String(5), nullable=False, comment="Unit of Currency"
|
||||
)
|
||||
additional_balance: Mapped[float] = mapped_column(
|
||||
Numeric(20, 6), nullable=False, comment="Additional Balance"
|
||||
)
|
||||
channel_branch: Mapped[str] = mapped_column(
|
||||
String(120), nullable=False, comment="Branch Bank"
|
||||
)
|
||||
process_name: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Bank Process Type Name"
|
||||
)
|
||||
process_type: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Bank Process Type"
|
||||
)
|
||||
process_comment: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Transaction Record Comment"
|
||||
)
|
||||
process_garbage: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Transaction Record Garbage"
|
||||
)
|
||||
bank_reference_code: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Bank Reference Code"
|
||||
)
|
||||
|
||||
add_comment_note: Mapped[str] = mapped_column(String, server_default="")
|
||||
is_receipt_mail_send: Mapped[bool] = mapped_column(Boolean, server_default="0")
|
||||
found_from = mapped_column(String, server_default="")
|
||||
similarity: Mapped[float] = mapped_column(Numeric(20, 6), server_default="0")
|
||||
remainder_balance: Mapped[float] = mapped_column(Numeric(20, 6), server_default="0")
|
||||
|
||||
bank_date_y: Mapped[int] = mapped_column(Integer)
|
||||
bank_date_m: Mapped[int] = mapped_column(SmallInteger)
|
||||
bank_date_w: Mapped[int] = mapped_column(SmallInteger)
|
||||
bank_date_d: Mapped[int] = mapped_column(SmallInteger)
|
||||
|
||||
approving_accounting_record: Mapped[bool] = mapped_column(
|
||||
Boolean, server_default="0"
|
||||
)
|
||||
accounting_receipt_date: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True), server_default="1900-01-01 00:00:00"
|
||||
)
|
||||
accounting_receipt_number: Mapped[int] = mapped_column(Integer, server_default="0")
|
||||
status_id: Mapped[int] = mapped_column(SmallInteger, server_default="0")
|
||||
|
||||
approved_record: Mapped[bool] = mapped_column(Boolean, server_default="0")
|
||||
import_file_name: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="XLS Key"
|
||||
)
|
||||
|
||||
receive_debit: Mapped[int] = mapped_column(
|
||||
ForeignKey("api_enum_dropdown.id"), nullable=True
|
||||
)
|
||||
receive_debit_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Debit UU ID"
|
||||
)
|
||||
budget_type: Mapped[int] = mapped_column(
|
||||
ForeignKey("api_enum_dropdown.id"), nullable=True
|
||||
)
|
||||
budget_type_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Budget Type UU ID"
|
||||
)
|
||||
company_id: Mapped[int] = mapped_column(ForeignKey("companies.id"), nullable=True)
|
||||
company_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Company UU ID"
|
||||
)
|
||||
send_company_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("companies.id"), nullable=True
|
||||
)
|
||||
send_company_uu_id = mapped_column(
|
||||
String, nullable=True, comment="Send Company UU ID"
|
||||
)
|
||||
|
||||
send_person_id: Mapped[int] = mapped_column(ForeignKey("people.id"), nullable=True)
|
||||
send_person_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Send Person UU ID"
|
||||
)
|
||||
approving_accounting_person: Mapped[int] = mapped_column(
|
||||
ForeignKey("people.id"), nullable=True
|
||||
)
|
||||
approving_accounting_person_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Approving Accounting Person UU ID"
|
||||
)
|
||||
|
||||
living_space_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_living_space.id"), nullable=True
|
||||
)
|
||||
living_space_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Living Space UU ID"
|
||||
)
|
||||
customer_id: Mapped[int] = mapped_column(ForeignKey("people.id"), nullable=True)
|
||||
customer_uu_id = mapped_column(String, nullable=True, comment="Customer UU ID")
|
||||
|
||||
build_id: Mapped[int] = mapped_column(ForeignKey("build.id"), nullable=True)
|
||||
build_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Build UU ID"
|
||||
)
|
||||
build_parts_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_parts.id"), nullable=True
|
||||
)
|
||||
build_parts_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Build Parts UU ID"
|
||||
)
|
||||
build_decision_book_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_decision_book.id"), nullable=True
|
||||
)
|
||||
build_decision_book_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Build Decision Book UU ID"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("_budget_records_ndx_00", is_receipt_mail_send, bank_date),
|
||||
Index(
|
||||
"_budget_records_ndx_01",
|
||||
iban,
|
||||
bank_date,
|
||||
bank_reference_code,
|
||||
bank_balance,
|
||||
unique=True,
|
||||
),
|
||||
Index("_budget_records_ndx_02", status_id, bank_date),
|
||||
{
|
||||
"comment": "Bank Records that are related to building and financial transactions"
|
||||
},
|
||||
)
|
||||
|
||||
# def payment_budget_record_close(self):
|
||||
# from database_sql_models import (
|
||||
# DecisionBookProjectPaymentsMaster,
|
||||
# ApiEnumDropdown,
|
||||
# BuildDecisionBook,
|
||||
# BuildDecisionBookPaymentsMaster,
|
||||
# )
|
||||
#
|
||||
# budget_record = self
|
||||
# if self.receive_debit == ApiEnumDropdown.uuid_of_enum(
|
||||
# enum_class="DebitTypes", key="R"
|
||||
# ):
|
||||
# print(
|
||||
# "This record is not debit. Debit:",
|
||||
# self.receive_debit,
|
||||
# "DebitTypes.R.name",
|
||||
# # str(DebitTypes.R.name),
|
||||
# )
|
||||
# return
|
||||
# if abs(budget_record.currency_value + budget_record.remainder_balance) > 0:
|
||||
# payment_dict = {
|
||||
# "budget_records_id": self.id,
|
||||
# "build_decision_book_id": budget_record.build_decision_book_id,
|
||||
# "build_parts_id": budget_record.build_parts_id,
|
||||
# "start_date": budget_record.bank_date,
|
||||
# "paid_value": budget_record.currency_value
|
||||
# - budget_record.remainder_balance,
|
||||
# "is_all": False,
|
||||
# }
|
||||
# (paid_value, start_paid_value, balance) = (
|
||||
# float(budget_record.currency_value - budget_record.remainder_balance),
|
||||
# float(budget_record.currency_value - budget_record.remainder_balance),
|
||||
# float(budget_record.remainder_balance),
|
||||
# )
|
||||
# print(
|
||||
# "self.id",
|
||||
# self.id,
|
||||
# "paid_value",
|
||||
# paid_value,
|
||||
# "start_paid_value",
|
||||
# start_paid_value,
|
||||
# "balance",
|
||||
# balance,
|
||||
# self.receive_debit,
|
||||
# )
|
||||
#
|
||||
# if not BuildDecisionBook.find_one(
|
||||
# id=payment_dict["build_decision_book_id"]
|
||||
# ):
|
||||
# return paid_value
|
||||
#
|
||||
# if budget_record.replication_id == 55:
|
||||
# if paid_value > 0:
|
||||
# payment_dict["dues_type"] = ApiEnumDropdown.uuid_of_enum(
|
||||
# enum_class="BuildDuesTypes", key="L"
|
||||
# )
|
||||
# paid_value = (
|
||||
# DecisionBookProjectPaymentsMaster.pay_law_and_ren_of_build_part(
|
||||
# **payment_dict
|
||||
# )
|
||||
# )
|
||||
# print("dues_type", payment_dict["dues_type"], paid_value)
|
||||
# if paid_value > 0:
|
||||
# payment_dict.pop("dues_type", None)
|
||||
# paid_value = BuildDecisionBookPaymentsMaster.pay_dues_of_build_part(
|
||||
# **payment_dict
|
||||
# )
|
||||
# print("dues_type", None, paid_value)
|
||||
# if paid_value > 0:
|
||||
# payment_dict["dues_type"] = ApiEnumDropdown.uuid_of_enum(
|
||||
# enum_class="BuildDuesTypes", key="R"
|
||||
# )
|
||||
# paid_value = (
|
||||
# DecisionBookProjectPaymentsMaster.pay_law_and_ren_of_build_part(
|
||||
# **payment_dict
|
||||
# )
|
||||
# )
|
||||
# print("dues_type", payment_dict["dues_type"], paid_value)
|
||||
# payment_dict["is_all"] = True
|
||||
# if paid_value > 0:
|
||||
# payment_dict["dues_type"] = ApiEnumDropdown.uuid_of_enum(
|
||||
# enum_class="BuildDuesTypes", key="L"
|
||||
# )
|
||||
# paid_value = (
|
||||
# DecisionBookProjectPaymentsMaster.pay_law_and_ren_of_build_part(
|
||||
# **payment_dict
|
||||
# )
|
||||
# )
|
||||
# print("is all dues_type", payment_dict["dues_type"], paid_value)
|
||||
# if paid_value > 0:
|
||||
# payment_dict.pop("dues_type", None)
|
||||
# paid_value = BuildDecisionBookPaymentsMaster.pay_dues_of_build_part(
|
||||
# **payment_dict
|
||||
# )
|
||||
# print("is all dues_type", None, paid_value)
|
||||
# if paid_value > 0:
|
||||
# payment_dict["dues_type"] = ApiEnumDropdown.uuid_of_enum(
|
||||
# enum_class="BuildDuesTypes", key="R"
|
||||
# )
|
||||
# paid_value = (
|
||||
# DecisionBookProjectPaymentsMaster.pay_law_and_ren_of_build_part(
|
||||
# **payment_dict
|
||||
# )
|
||||
# )
|
||||
# print("is all dues_type", payment_dict["dues_type"], paid_value)
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
from schemas.base_imports import (
|
||||
CrudCollection,
|
||||
String,
|
||||
Integer,
|
||||
Boolean,
|
||||
ForeignKey,
|
||||
Index,
|
||||
TIMESTAMP,
|
||||
Numeric,
|
||||
SmallInteger,
|
||||
mapped_column,
|
||||
Mapped,
|
||||
)
|
||||
|
||||
|
||||
class BuildIbans(CrudCollection):
|
||||
"""
|
||||
BuildParts class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "build_ibans"
|
||||
__exclude__fields__ = []
|
||||
|
||||
iban: Mapped[str] = mapped_column(
|
||||
String(40), server_default="", nullable=False, comment="IBAN number"
|
||||
)
|
||||
start_date: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True), nullable=False, comment="Bank Transaction Start Date"
|
||||
)
|
||||
stop_date: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True), server_default="2900-01-01 00:00:00"
|
||||
)
|
||||
bank_code: Mapped[str] = mapped_column(String(24), server_default="TR0000000000000")
|
||||
xcomment: Mapped[str] = mapped_column(String(64), server_default="????")
|
||||
build_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build.id"), nullable=True, comment="Building ID"
|
||||
)
|
||||
build_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Building UUID", index=True
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("_build_ibans_ndx_01", iban, start_date, unique=True),
|
||||
{"comment": "IBANs related to money transactions due to building objects"},
|
||||
)
|
||||
|
||||
|
||||
class BuildIbanDescription(CrudCollection):
|
||||
"""
|
||||
SearchComments class based on declarative_base and CrudCollection via session
|
||||
"""
|
||||
|
||||
__tablename__ = "build_iban_description"
|
||||
__exclude__fields__ = []
|
||||
|
||||
iban: Mapped[str] = mapped_column(String, nullable=False, comment="IBAN Number")
|
||||
group_id: Mapped[int] = mapped_column(
|
||||
SmallInteger, nullable=False, comment="Group ID"
|
||||
)
|
||||
search_word: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Search Word", index=True
|
||||
)
|
||||
customer_id: Mapped[int] = mapped_column(ForeignKey("people.id"), nullable=True)
|
||||
customer_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Customer UUID"
|
||||
)
|
||||
company_id: Mapped[int] = mapped_column(ForeignKey("companies.id"), nullable=True)
|
||||
company_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Company UUID"
|
||||
)
|
||||
build_parts_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_parts.id"), nullable=True
|
||||
)
|
||||
build_parts_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Build Parts UUID"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"_search_iban_description_ndx_00", iban, search_word, group_id, unique=True
|
||||
),
|
||||
{"comment": "Search Iban Description Information"},
|
||||
)
|
||||
|
|
@ -0,0 +1,575 @@
|
|||
from schemas.base_imports import (
|
||||
CrudCollection,
|
||||
String,
|
||||
Integer,
|
||||
Boolean,
|
||||
ForeignKey,
|
||||
Index,
|
||||
TIMESTAMP,
|
||||
Numeric,
|
||||
SmallInteger,
|
||||
mapped_column,
|
||||
Mapped,
|
||||
BigInteger,
|
||||
Text,
|
||||
)
|
||||
|
||||
class RelationshipEmployee2PostCode(CrudCollection):
|
||||
"""
|
||||
Build2EmployeeRelationship class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "relationship_employee2postcode"
|
||||
__exclude__fields__ = []
|
||||
__include__fields__ = []
|
||||
|
||||
company_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("companies.id"), nullable=True
|
||||
) # 1, 2, 3
|
||||
employee_id: Mapped[int] = mapped_column(ForeignKey("employees.id"), nullable=False)
|
||||
member_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("address_postcode.id"), nullable=False
|
||||
)
|
||||
|
||||
relationship_type: Mapped[str] = mapped_column(
|
||||
String, nullable=True, server_default="Employee"
|
||||
) # Commercial
|
||||
show_only: Mapped[bool] = mapped_column(Boolean, server_default="0")
|
||||
|
||||
__table_args__ = ({"comment": "Build2Employee Relationship Information"},)
|
||||
|
||||
|
||||
class AddressPostcode(CrudCollection):
|
||||
"""
|
||||
Postcode class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "address_postcode"
|
||||
__exclude__fields__ = []
|
||||
__access_by__ = []
|
||||
__many__table__ = RelationshipEmployee2PostCode
|
||||
|
||||
street_id: Mapped[int] = mapped_column(ForeignKey("address_street.id"))
|
||||
street_uu_id: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="Street UUID"
|
||||
)
|
||||
postcode: Mapped[str] = mapped_column(
|
||||
String(32), nullable=False, comment="Postcode"
|
||||
)
|
||||
|
||||
__table_args__ = ({"comment": "Postcode Information"},)
|
||||
|
||||
|
||||
class Addresses(CrudCollection):
|
||||
"""
|
||||
Address class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "addresses"
|
||||
__exclude__fields__ = []
|
||||
|
||||
build_number: Mapped[str] = mapped_column(
|
||||
String(24), nullable=False, comment="Build Number"
|
||||
)
|
||||
door_number: Mapped[str] = mapped_column(
|
||||
String(24), nullable=True, comment="Door Number"
|
||||
)
|
||||
floor_number: Mapped[str] = mapped_column(
|
||||
String(24), nullable=True, comment="Floor Number"
|
||||
)
|
||||
|
||||
comment_address: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Address"
|
||||
)
|
||||
letter_address: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Address"
|
||||
)
|
||||
short_letter_address: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Address"
|
||||
)
|
||||
|
||||
latitude: Mapped[float] = mapped_column(Numeric(20, 12), server_default="0")
|
||||
longitude: Mapped[float] = mapped_column(Numeric(20, 12), server_default="0")
|
||||
|
||||
street_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("address_street.id"), nullable=False
|
||||
)
|
||||
street_uu_id: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="Street UUID"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
# Index("_address_ndx_00", country_code, b_state, city, district),
|
||||
{"comment": "Address Information"},
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def list_via_employee(cls, token_dict, filter_expr=None):
|
||||
with cls.new_session() as db_session:
|
||||
post_code_list = RelationshipEmployee2PostCode.filter_all(
|
||||
RelationshipEmployee2PostCode.employee_id
|
||||
== token_dict.selected_company.employee_id,
|
||||
db=db_session,
|
||||
).data
|
||||
post_code_id_list = [post_code.member_id for post_code in post_code_list]
|
||||
if not post_code_id_list:
|
||||
raise ValueError(
|
||||
"User has no post code registered. User can not list addresses."
|
||||
)
|
||||
# raise HTTPException(
|
||||
# status_code=404,
|
||||
# detail="User has no post code registered. User can not list addresses.",
|
||||
# )
|
||||
cls.pre_query = cls.filter_all(
|
||||
cls.post_code_id.in_(post_code_id_list), db=db_session
|
||||
).query
|
||||
filter_cls = cls.filter_all(*filter_expr or [], db=db_session)
|
||||
cls.pre_query = None
|
||||
return filter_cls.data
|
||||
|
||||
# buildings: Mapped["Build"] = relationship(
|
||||
# "Build", back_populates="addresses", foreign_keys="Build.address_id"
|
||||
# )
|
||||
# site: Mapped["BuildSites"] = relationship(
|
||||
# "BuildSites", back_populates="addresses", foreign_keys="BuildSites.address_id"
|
||||
# )
|
||||
# official_companies: Mapped["Companies"] = relationship(
|
||||
# "Company",
|
||||
# back_populates="official_address",
|
||||
# foreign_keys="Company.official_address_id",
|
||||
# )
|
||||
|
||||
# @classmethod
|
||||
# def create_action(cls, request, create_address: InsertAddress):
|
||||
# from services.redis.auth_actions.token import parse_token_object_to_dict
|
||||
#
|
||||
# token_dict = parse_token_object_to_dict(request=request)
|
||||
# data_dict = create_address.model_dump()
|
||||
# post_code = AddressPostcode.find_one(uu_id=create_address.post_code_uu_id)
|
||||
# if not post_code:
|
||||
# raise HTTPException(
|
||||
# status_code=404,
|
||||
# detail="Post code not found.",
|
||||
# )
|
||||
# if Employee2AddressRelationship.post_code_id.find_one(
|
||||
# employee_id=token_dict.selected_company.employee_id,
|
||||
# post_code_id=post_code.id,
|
||||
# ):
|
||||
# data_dict["post_code_id"] = post_code.id
|
||||
# del data_dict["post_code_uu_id"]
|
||||
# return cls.find_or_create(**create_address.model_dump())
|
||||
# raise HTTPException(
|
||||
# status_code=401,
|
||||
# detail=f"User is not qualified to create address at this post code {post_code.postcode}",
|
||||
# )
|
||||
|
||||
# __table_args__ = (
|
||||
# Index("_address_ndx_00", country_code, b_state, city, district),
|
||||
# {"comment": "Address Information"},
|
||||
# )
|
||||
|
||||
|
||||
class AddressGeographicLocations(CrudCollection):
|
||||
"""
|
||||
Country class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "address_geographic_locations"
|
||||
__exclude__fields__ = []
|
||||
|
||||
geo_table: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Address Table Name"
|
||||
)
|
||||
geo_id: Mapped[int] = mapped_column(
|
||||
Integer, nullable=False, comment="Address Table ID"
|
||||
)
|
||||
geo_name: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Geographic Location Name"
|
||||
)
|
||||
geo_latitude: Mapped[float] = mapped_column(
|
||||
Numeric(20, 6), server_default="0", comment="Geographic Location Name"
|
||||
)
|
||||
geo_longitude: Mapped[float] = mapped_column(
|
||||
Numeric(20, 6), server_default="0", comment="Geographic Location Latitude"
|
||||
)
|
||||
geo_altitude: Mapped[float] = mapped_column(
|
||||
Numeric(20, 6), server_default="0", comment="Geographic Location Longitude"
|
||||
)
|
||||
geo_description: Mapped[str] = mapped_column(
|
||||
Text, nullable=False, comment="Geographic Location Description"
|
||||
)
|
||||
geo_area_size: Mapped[float] = mapped_column(
|
||||
Numeric(20, 2),
|
||||
nullable=True,
|
||||
server_default="0",
|
||||
comment="Geographic Location Area Size",
|
||||
)
|
||||
geo_population: Mapped[int] = mapped_column(
|
||||
BigInteger, nullable=True, comment="Geographic Location Population"
|
||||
)
|
||||
# geo_geom_point = mapped_column(Geometry('POINT', srid=4326), nullable=True, comment="Geographic Location Points")
|
||||
# geo_geom_polygon = mapped_column(Geometry('POLYGON', srid=4326), nullable=True,
|
||||
# comment="Geographic Location Vector geographic information (polygon)")
|
||||
# geo_centroid = mapped_column( GEOMETRY(POINT, 4326), nullable=True,
|
||||
# comment="Geographic Location center of gravity of the region(points)")
|
||||
|
||||
__table_args__ = (
|
||||
Index("_address_geographic_locations_ndx_00", geo_table, geo_id),
|
||||
Index("_address_geographic_locations_ndx_01", geo_latitude, geo_longitude),
|
||||
{"comment": "Geographic Location Information"},
|
||||
)
|
||||
|
||||
|
||||
class AddressCountry(CrudCollection):
|
||||
"""
|
||||
Country class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "address_country"
|
||||
__exclude__fields__ = []
|
||||
|
||||
country_code: Mapped[str] = mapped_column(
|
||||
String(16), nullable=False, comment="Country Code"
|
||||
)
|
||||
country_name: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Country Name"
|
||||
)
|
||||
money_code: Mapped[str] = mapped_column(
|
||||
String(12), nullable=True, comment="Money Code"
|
||||
)
|
||||
language: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Language Code"
|
||||
)
|
||||
address_geographic_id: Mapped[int] = mapped_column(
|
||||
BigInteger, nullable=True, comment="Address Geographic Id"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("_address_country_ndx_00", money_code),
|
||||
Index("_address_country_ndx_01", country_code, unique=True),
|
||||
{"comment": "Country Information"},
|
||||
)
|
||||
|
||||
|
||||
class AddressState(CrudCollection):
|
||||
"""
|
||||
State class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "address_state"
|
||||
__exclude__fields__ = []
|
||||
|
||||
state_code: Mapped[str] = mapped_column(
|
||||
String(16), nullable=False, comment="State Code"
|
||||
)
|
||||
state_name: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="State Name"
|
||||
)
|
||||
licence_plate: Mapped[str] = mapped_column(
|
||||
String(24), nullable=True, comment="Sign Code"
|
||||
)
|
||||
phone_code: Mapped[str] = mapped_column(
|
||||
String(36), nullable=True, comment="Phone Code"
|
||||
)
|
||||
gov_code: Mapped[str] = mapped_column(
|
||||
String(128), nullable=True, comment="Government Code"
|
||||
)
|
||||
address_geographic_id: Mapped[int] = mapped_column(
|
||||
BigInteger, nullable=True, comment="Address Geographic Id"
|
||||
)
|
||||
|
||||
country_id: Mapped[int] = mapped_column(ForeignKey("address_country.id"))
|
||||
country_uu_id: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="Country UUID"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"_address_state_ndx_01",
|
||||
country_id,
|
||||
state_code,
|
||||
unique=True,
|
||||
),
|
||||
{"comment": "State Information"},
|
||||
)
|
||||
|
||||
|
||||
class AddressCity(CrudCollection):
|
||||
"""
|
||||
City class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "address_city"
|
||||
__exclude__fields__ = []
|
||||
|
||||
city_code: Mapped[str] = mapped_column(
|
||||
String(24), nullable=False, comment="City Code"
|
||||
)
|
||||
city_name: Mapped[str] = mapped_column(String, nullable=False, comment="City Name")
|
||||
licence_plate: Mapped[str] = mapped_column(
|
||||
String(24), nullable=True, comment="Sign Code"
|
||||
)
|
||||
phone_code: Mapped[str] = mapped_column(
|
||||
String(36), nullable=True, comment="Phone Code"
|
||||
)
|
||||
gov_code: Mapped[str] = mapped_column(
|
||||
String(128), nullable=True, comment="Government Code"
|
||||
)
|
||||
address_geographic_id: Mapped[int] = mapped_column(
|
||||
BigInteger, nullable=True, comment="Address Geographic Id"
|
||||
)
|
||||
|
||||
state_id: Mapped[int] = mapped_column(ForeignKey("address_state.id"))
|
||||
state_uu_id: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="State UUID"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"_address_city_ndx_01",
|
||||
state_id,
|
||||
city_code,
|
||||
unique=True,
|
||||
),
|
||||
{"comment": "City Information"},
|
||||
)
|
||||
|
||||
|
||||
class AddressDistrict(CrudCollection):
|
||||
"""
|
||||
District class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "address_district"
|
||||
__exclude__fields__ = []
|
||||
|
||||
district_code: Mapped[str] = mapped_column(
|
||||
String(16), nullable=False, comment="District Code"
|
||||
)
|
||||
district_name: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="District Name"
|
||||
)
|
||||
phone_code: Mapped[str] = mapped_column(
|
||||
String(36), nullable=True, comment="Phone Code"
|
||||
)
|
||||
gov_code: Mapped[str] = mapped_column(
|
||||
String(128), nullable=True, comment="Government Code"
|
||||
)
|
||||
address_geographic_id: Mapped[int] = mapped_column(
|
||||
BigInteger, nullable=True, comment="Address Geographic Id"
|
||||
)
|
||||
|
||||
city_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("address_city.id"), nullable=False, comment="City ID"
|
||||
)
|
||||
city_uu_id: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="City UUID"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"_address_district_ndx_01",
|
||||
city_id,
|
||||
district_code,
|
||||
unique=True,
|
||||
),
|
||||
{"comment": "District Information"},
|
||||
)
|
||||
|
||||
|
||||
class AddressLocality(CrudCollection):
|
||||
"""
|
||||
Locality class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "address_locality"
|
||||
__exclude__fields__ = []
|
||||
|
||||
locality_code: Mapped[str] = mapped_column(
|
||||
String(16), nullable=False, comment="Locality Code"
|
||||
)
|
||||
locality_name: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Locality Name"
|
||||
)
|
||||
type_code: Mapped[str] = mapped_column(String, nullable=True, comment="Type Name")
|
||||
type_description: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Type Name"
|
||||
)
|
||||
gov_code: Mapped[str] = mapped_column(
|
||||
String(128), nullable=True, comment="Government Code"
|
||||
)
|
||||
address_show: Mapped[bool] = mapped_column(Boolean, server_default="1")
|
||||
address_geographic_id: Mapped[int] = mapped_column(
|
||||
BigInteger, nullable=True, comment="Address Geographic Id"
|
||||
)
|
||||
|
||||
district_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("address_district.id"), nullable=False, comment="District ID"
|
||||
)
|
||||
district_uu_id: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="District UUID"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"_address_locality_ndx_01",
|
||||
district_id,
|
||||
locality_code,
|
||||
unique=True,
|
||||
),
|
||||
{"comment": "Locality Information"},
|
||||
)
|
||||
|
||||
|
||||
class AddressNeighborhood(CrudCollection):
|
||||
"""
|
||||
Neighborhood class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "address_neighborhood"
|
||||
__exclude__fields__ = []
|
||||
|
||||
neighborhood_code: Mapped[str] = mapped_column(
|
||||
String(16), nullable=False, comment="Neighborhood Code"
|
||||
)
|
||||
neighborhood_name: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Neighborhood Name"
|
||||
)
|
||||
type_code: Mapped[str] = mapped_column(String, nullable=True, comment="Type Name")
|
||||
type_description: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Type Name"
|
||||
)
|
||||
gov_code: Mapped[str] = mapped_column(
|
||||
String(128), nullable=True, comment="Government Code"
|
||||
)
|
||||
address_show: Mapped[bool] = mapped_column(Boolean, server_default="1")
|
||||
address_geographic_id: Mapped[int] = mapped_column(
|
||||
BigInteger, nullable=True, comment="Address Geographic Id"
|
||||
)
|
||||
|
||||
district_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("address_district.id"), nullable=True, comment="District ID"
|
||||
)
|
||||
district_uu_id: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="District UUID"
|
||||
)
|
||||
locality_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("address_locality.id"), nullable=True, comment="Locality ID"
|
||||
)
|
||||
locality_uu_id: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="Locality UUID"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"_address_neighborhood_ndx_01",
|
||||
locality_id,
|
||||
neighborhood_code,
|
||||
unique=True,
|
||||
),
|
||||
{"comment": "Neighborhood Information"},
|
||||
)
|
||||
|
||||
|
||||
class AddressStreet(CrudCollection):
|
||||
"""
|
||||
Street class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "address_street"
|
||||
__exclude__fields__ = []
|
||||
|
||||
street_code: Mapped[str] = mapped_column(
|
||||
String(16), nullable=False, comment="Street Code"
|
||||
)
|
||||
street_name: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Street Name"
|
||||
)
|
||||
type_code: Mapped[str] = mapped_column(String, nullable=True, comment="Type Name")
|
||||
type_description: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Type Name"
|
||||
)
|
||||
gov_code: Mapped[str] = mapped_column(
|
||||
String(128), nullable=True, comment="Government Code"
|
||||
)
|
||||
|
||||
address_geographic_id: Mapped[int] = mapped_column(
|
||||
BigInteger, nullable=True, comment="Address Geographic Id"
|
||||
)
|
||||
neighborhood_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("address_neighborhood.id"), nullable=False, comment="Neighborhood ID"
|
||||
)
|
||||
neighborhood_uu_id: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="Neighborhood UUID"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("_address_street_ndx_01", neighborhood_id, street_code, unique=True),
|
||||
{"comment": "Street Information"},
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def search_address_text(cls, search_text, token_dict=None):
|
||||
field_dict = {
|
||||
"AddressStreet.uu_id": cls.uu_id,
|
||||
"AddressCountry.uu_id": AddressCountry.uu_id,
|
||||
"AddressState.uu_id": AddressState.uu_id,
|
||||
"AddressCity.uu_id": AddressCity.uu_id,
|
||||
"AddressDistrict.uu_id": AddressDistrict.uu_id,
|
||||
"AddressLocality.uu_id": AddressLocality.uu_id,
|
||||
"AddressNeighborhood.uu_id": AddressNeighborhood.uu_id,
|
||||
"AddressCountry.country_name": AddressCountry.country_name,
|
||||
"AddressState.state_name": AddressState.state_name,
|
||||
"AddressCity.city_name": AddressCity.city_name,
|
||||
"AddressDistrict.district_name": AddressDistrict.district_name,
|
||||
"AddressLocality.locality_name": AddressLocality.locality_name,
|
||||
"AddressNeighborhood.neighborhood_name": AddressNeighborhood.neighborhood_name,
|
||||
"AddressStreet.street_name": cls.street_name,
|
||||
}
|
||||
joined_data = (
|
||||
cls.session.query(*list(field_dict.values()))
|
||||
.select_from(cls)
|
||||
.join(AddressNeighborhood, AddressNeighborhood.id == cls.neighborhood_id)
|
||||
.join(
|
||||
AddressLocality, AddressLocality.id == AddressNeighborhood.locality_id
|
||||
)
|
||||
.join(AddressDistrict, AddressDistrict.id == AddressLocality.district_id)
|
||||
.join(AddressCity, AddressCity.id == AddressDistrict.city_id)
|
||||
.join(AddressState, AddressState.id == AddressCity.state_id)
|
||||
.join(AddressCountry, AddressCountry.id == AddressState.country_id)
|
||||
.filter(
|
||||
or_(
|
||||
AddressNeighborhood.neighborhood_name.ilike(
|
||||
f"%{str(search_text).upper()}%"
|
||||
),
|
||||
AddressLocality.locality_name.ilike(
|
||||
f"%{str(search_text).upper()}%"
|
||||
),
|
||||
AddressDistrict.district_name.ilike(
|
||||
f"%{str(search_text).upper()}%"
|
||||
),
|
||||
# AddressCity.city_name.ilike(f"%{str(search_text).upper()}%"),
|
||||
# AddressState.state_name.ilike(f"%{str(search_text).upper()}%"),
|
||||
# AddressCountry.country_name.ilike(f"%{str(search_text).upper()}%"),
|
||||
cls.street_name.ilike(f"%{str(search_text).upper()}%"),
|
||||
),
|
||||
)
|
||||
)
|
||||
# select([mytable.c.id]).where(
|
||||
# func.to_tsvector('english', mytable.c.title) \
|
||||
# .match('somestring', postgresql_regconfig='english')
|
||||
# )
|
||||
joined_statement = joined_data
|
||||
joined_data = joined_data.first()
|
||||
if not joined_data:
|
||||
raise ValueError(
|
||||
"No address found with the given search text.",
|
||||
)
|
||||
# raise HTTPException(
|
||||
# status_code=404,
|
||||
# detail="No address found with the given search text.",
|
||||
# )
|
||||
return dict(
|
||||
query=joined_statement,
|
||||
schema=list(field_dict.keys()),
|
||||
)
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
from api_controllers.postgres.mixin import CrudCollection
|
||||
from sqlalchemy.orm import mapped_column, Mapped, relationship
|
||||
from sqlalchemy import (
|
||||
String,
|
||||
Integer,
|
||||
Boolean,
|
||||
ForeignKey,
|
||||
Index,
|
||||
TIMESTAMP,
|
||||
Numeric,
|
||||
SmallInteger,
|
||||
text,
|
||||
or_,
|
||||
and_,
|
||||
func,
|
||||
UUID,
|
||||
JSON,
|
||||
BigInteger,
|
||||
Float,
|
||||
Text,
|
||||
LargeBinary,
|
||||
PickleType,
|
||||
DATETIME,
|
||||
)
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
from schemas.base_imports import (
|
||||
CrudCollection,
|
||||
String,
|
||||
ForeignKey,
|
||||
Index,
|
||||
SmallInteger,
|
||||
TIMESTAMP,
|
||||
Text,
|
||||
Numeric,
|
||||
Integer,
|
||||
mapped_column,
|
||||
Mapped,
|
||||
relationship,
|
||||
func,
|
||||
)
|
||||
|
||||
|
||||
class DecisionBookBudgetBooks(CrudCollection):
|
||||
|
||||
__tablename__ = "decision_book_budget_books"
|
||||
__exclude__fields__ = []
|
||||
|
||||
country: Mapped[str] = mapped_column(String, nullable=False)
|
||||
branch_type: Mapped[int] = mapped_column(SmallInteger, server_default="0")
|
||||
|
||||
company_id: Mapped[int] = mapped_column(ForeignKey("companies.id"), nullable=False)
|
||||
company_uu_id: Mapped[str] = mapped_column(String, nullable=False)
|
||||
branch_id: Mapped[int] = mapped_column(ForeignKey("companies.id"), nullable=True)
|
||||
branch_uu_id: Mapped[str] = mapped_column(
|
||||
String, comment="Branch UU ID", nullable=True
|
||||
)
|
||||
build_decision_book_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_decision_book.id"), nullable=False
|
||||
)
|
||||
build_decision_book_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Build Decision Book UU ID"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"_decision_book_budget_companies_book_ndx_00",
|
||||
company_id,
|
||||
"created_at",
|
||||
),
|
||||
{"comment": "budget Book Information"},
|
||||
)
|
||||
|
||||
|
||||
class DecisionBookBudgetCodes(CrudCollection):
|
||||
|
||||
__tablename__ = "decision_book_budget_codes"
|
||||
__exclude__fields__ = []
|
||||
|
||||
budget_code: Mapped[str] = mapped_column(
|
||||
String(48), nullable=False, comment="budget Code"
|
||||
)
|
||||
comment_line: Mapped[str] = mapped_column(
|
||||
Text, nullable=False, comment="Comment Line"
|
||||
)
|
||||
|
||||
build_decision_book_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_decision_book.id"), nullable=True
|
||||
)
|
||||
build_decision_book_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Build Decision Book UU ID"
|
||||
)
|
||||
|
||||
build_parts_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_parts.id"), nullable=True
|
||||
)
|
||||
build_parts_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Build Parts UU ID"
|
||||
)
|
||||
|
||||
company_id: Mapped[int] = mapped_column(ForeignKey("companies.id"), nullable=True)
|
||||
company_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Company UU ID"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("_decision_book_budget_codes_ndx_00", budget_code, "created_at"),
|
||||
Index("_decision_book_budget_codes_ndx_01", company_id, "created_at"),
|
||||
{"comment": "budget Book Information"},
|
||||
)
|
||||
|
||||
|
||||
class DecisionBookBudgetMaster(CrudCollection):
|
||||
|
||||
__tablename__ = "decision_book_budget_master"
|
||||
__exclude__fields__ = []
|
||||
|
||||
budget_type: Mapped[str] = mapped_column(
|
||||
String(50), nullable=False
|
||||
) # Bütçe tipi (örneğin: Operasyonel, Yatırım)
|
||||
currency: Mapped[str] = mapped_column(
|
||||
String(8), server_default="TRY"
|
||||
) # Bütçe para birimi
|
||||
total_budget: Mapped[float] = mapped_column(
|
||||
Numeric(10, 2), nullable=False
|
||||
) # Toplam bütçe
|
||||
|
||||
tracking_period_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("api_enum_dropdown.id"), nullable=True
|
||||
)
|
||||
tracking_period_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Part Direction UUID"
|
||||
)
|
||||
budget_books_id: Mapped[int] = mapped_column(
|
||||
Integer, ForeignKey("decision_book_budget_books.id"), nullable=False
|
||||
)
|
||||
budget_books_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Budget Books UU ID"
|
||||
)
|
||||
department_id: Mapped[int] = mapped_column(
|
||||
Integer, ForeignKey("departments.id"), nullable=False
|
||||
) # Departman ile ilişki
|
||||
department_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Department UU ID"
|
||||
)
|
||||
|
||||
__table_args__ = ({"comment": "budget Book Information"},)
|
||||
|
||||
|
||||
class DecisionBookBudgets(CrudCollection):
|
||||
|
||||
__tablename__ = "decision_book_budgets"
|
||||
__exclude__fields__ = []
|
||||
|
||||
process_date: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True), nullable=False
|
||||
) # Başlangıç tarihi
|
||||
budget_codes_id: Mapped[int] = mapped_column(
|
||||
Integer, ForeignKey("decision_book_budget_codes.id"), nullable=False
|
||||
)
|
||||
total_budget: Mapped[float] = mapped_column(
|
||||
Numeric(10, 2), nullable=False
|
||||
) # Toplam bütçe
|
||||
used_budget: Mapped[float] = mapped_column(
|
||||
Numeric(10, 2), nullable=False, default=0.0
|
||||
) # Kullanılan bütçe
|
||||
remaining_budget: Mapped[float] = mapped_column(
|
||||
Numeric(10, 2), nullable=False, default=0.0
|
||||
) # Kullanılan bütçe
|
||||
|
||||
decision_book_budget_master_id: Mapped[int] = mapped_column(
|
||||
Integer, ForeignKey("decision_book_budget_master.id"), nullable=False
|
||||
)
|
||||
decision_book_budget_master_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Decision Book Budget Master UU ID"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"_decision_book_budgets_ndx_00",
|
||||
decision_book_budget_master_uu_id,
|
||||
process_date,
|
||||
),
|
||||
{"comment": "budget Book Information"},
|
||||
)
|
||||
|
|
@ -0,0 +1,602 @@
|
|||
import arrow
|
||||
from datetime import timedelta
|
||||
from typing import List, Union, Any
|
||||
from schemas.base_imports import (
|
||||
CrudCollection,
|
||||
mapped_column,
|
||||
Mapped,
|
||||
relationship,
|
||||
String,
|
||||
Integer,
|
||||
Boolean,
|
||||
ForeignKey,
|
||||
Index,
|
||||
TIMESTAMP,
|
||||
Text,
|
||||
Numeric,
|
||||
or_,
|
||||
and_,
|
||||
)
|
||||
|
||||
|
||||
class BuildTypes(CrudCollection):
|
||||
"""
|
||||
BuildTypes class based on declarative_base and BaseMixin via session
|
||||
|
||||
"""
|
||||
|
||||
__tablename__ = "build_types"
|
||||
__exclude__fields__ = []
|
||||
__include__fields__ = []
|
||||
|
||||
function_code: Mapped[str] = mapped_column(
|
||||
String(12), server_default="", nullable=False, comment="Function Code"
|
||||
)
|
||||
type_code: Mapped[str] = mapped_column(
|
||||
String(12), server_default="", nullable=False, comment="Structure Type Code"
|
||||
)
|
||||
lang: Mapped[str] = mapped_column(
|
||||
String(4), server_default="TR", nullable=False, comment="Language"
|
||||
)
|
||||
type_name: Mapped[str] = mapped_column(
|
||||
String(48), server_default="", nullable=False, comment="Type Name"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("_build_types_ndx_00", type_code, function_code, lang, unique=True),
|
||||
{"comment": "Function group of building types with their language information"},
|
||||
)
|
||||
|
||||
|
||||
class Part2Employee(CrudCollection):
|
||||
"""
|
||||
Employee2Parts class based on declarative_base and BaseMixin via session
|
||||
In between start and end date, a part can be assigned to only one employee
|
||||
"""
|
||||
|
||||
__tablename__ = "part2employee"
|
||||
__exclude__fields__ = []
|
||||
__include__fields__ = []
|
||||
|
||||
build_id: Mapped[int] = mapped_column(Integer, comment="Building ID")
|
||||
part_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_parts.id"), nullable=False, comment="Part ID"
|
||||
)
|
||||
employee_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("employees.id"), nullable=False, comment="Employee ID"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("_part2employee_ndx_00", employee_id, part_id, unique=True),
|
||||
{"comment": "Employee2Parts Information"},
|
||||
)
|
||||
|
||||
|
||||
class RelationshipEmployee2Build(CrudCollection):
|
||||
"""
|
||||
CompanyRelationship class based on declarative_base and CrudCollection via session
|
||||
Company -> Sub Company -> Sub-Sub Company
|
||||
|
||||
"""
|
||||
|
||||
__tablename__ = "relationship_employee2build"
|
||||
__exclude__fields__ = []
|
||||
|
||||
company_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("companies.id"), nullable=False
|
||||
) # 1, 2, 3
|
||||
employee_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("employees.id"), nullable=False
|
||||
) # employee -> (n)person Evyos LTD
|
||||
member_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build.id"), nullable=False
|
||||
) # 2, 3, 4
|
||||
|
||||
relationship_type: Mapped[str] = mapped_column(
|
||||
String, nullable=True, server_default="Employee"
|
||||
) # Commercial
|
||||
show_only: Mapped[bool] = mapped_column(Boolean, server_default="False")
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"relationship_build_employee_ndx_00",
|
||||
company_id,
|
||||
employee_id,
|
||||
member_id,
|
||||
relationship_type,
|
||||
unique=True,
|
||||
),
|
||||
{"comment": "Build & Employee Relationship Information"},
|
||||
)
|
||||
|
||||
|
||||
class Build(CrudCollection):
|
||||
"""
|
||||
Builds class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "build"
|
||||
__exclude__fields__ = []
|
||||
__include__fields__ = []
|
||||
__access_by__ = []
|
||||
# __many__table__ = RelationshipEmployee2Build
|
||||
|
||||
gov_address_code: Mapped[str] = mapped_column(String, server_default="", unique=True)
|
||||
build_name: Mapped[str] = mapped_column(String, nullable=False, comment="Building Name")
|
||||
build_no: Mapped[str] = mapped_column(String(8), nullable=False, comment="Building Number")
|
||||
|
||||
max_floor: Mapped[int] = mapped_column(Integer, server_default="1", nullable=False, comment="Max Floor")
|
||||
underground_floor: Mapped[int] = mapped_column(Integer, server_default="0", nullable=False, comment="Underground Floor")
|
||||
build_date: Mapped[TIMESTAMP] = mapped_column(TIMESTAMP(timezone=True), server_default="1900-01-01")
|
||||
decision_period_date: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True),
|
||||
server_default="1900-01-01",
|
||||
comment="Building annual ordinary meeting period",
|
||||
)
|
||||
tax_no: Mapped[str] = mapped_column(String(24), server_default="")
|
||||
lift_count: Mapped[int] = mapped_column(Integer, server_default="0")
|
||||
heating_system: Mapped[bool] = mapped_column(Boolean, server_default="True")
|
||||
cooling_system: Mapped[bool] = mapped_column(Boolean, server_default="False")
|
||||
hot_water_system: Mapped[bool] = mapped_column(Boolean, server_default="False")
|
||||
block_service_man_count: Mapped[int] = mapped_column(Integer, server_default="0")
|
||||
security_service_man_count: Mapped[int] = mapped_column(Integer, server_default="0")
|
||||
garage_count: Mapped[int] = mapped_column(Integer, server_default="0", comment="Garage Count")
|
||||
management_room_id: Mapped[int] = mapped_column(Integer, nullable=True, comment="Management Room ID")
|
||||
|
||||
site_id: Mapped[int] = mapped_column(ForeignKey("build_sites.id"), nullable=True)
|
||||
site_uu_id: Mapped[str] = mapped_column(String, comment="Site UUID", nullable=True)
|
||||
address_id: Mapped[int] = mapped_column(ForeignKey("addresses.id"), nullable=False)
|
||||
address_uu_id: Mapped[str] = mapped_column(String, comment="Address UUID", nullable=False)
|
||||
build_types_id: Mapped[int] = mapped_column(ForeignKey("build_types.id"), nullable=False, comment="Building Type")
|
||||
build_types_uu_id: Mapped[str] = mapped_column(String, comment="Building Type UUID")
|
||||
|
||||
parts: Mapped[List["BuildParts"]] = relationship("BuildParts", back_populates="buildings", foreign_keys="BuildParts.build_id")
|
||||
decision_books: Mapped[List["BuildDecisionBook"]] = relationship(
|
||||
"BuildDecisionBook",
|
||||
back_populates="buildings",
|
||||
foreign_keys="BuildDecisionBook.build_id",
|
||||
)
|
||||
|
||||
# build_ibans: Mapped["BuildIbans"] = relationship(
|
||||
# "BuildIbans", back_populates="building", foreign_keys="BuildIbans.build_id"
|
||||
# )
|
||||
# areas: Mapped["BuildArea"] = relationship(
|
||||
# "BuildArea", back_populates="buildings", foreign_keys="BuildArea.build_id"
|
||||
# )
|
||||
# response_companies: Mapped["Companies"] = relationship(
|
||||
# "Companies",
|
||||
# back_populates="response_buildings",
|
||||
# foreign_keys=[response_company_id],
|
||||
# )
|
||||
# addresses: Mapped[List["Address"]] = relationship(
|
||||
# "Address", back_populates="buildings", foreign_keys=[address_id]
|
||||
# )
|
||||
# peoples: Mapped["People"] = relationship(
|
||||
# "People", back_populates="buildings", foreign_keys=[people_id]
|
||||
# )
|
||||
# sites: Mapped["BuildSites"] = relationship(
|
||||
# "BuildSites", back_populates="buildings", foreign_keys=[site_id]
|
||||
# )
|
||||
|
||||
__table_args__ = (
|
||||
Index("_builds_ndx_00", gov_address_code),
|
||||
Index("_builds_ndx_01", build_name, build_no),
|
||||
{
|
||||
"comment": "Build objects are building that are created for living and store purposes"
|
||||
},
|
||||
)
|
||||
|
||||
@property
|
||||
def management_room(self):
|
||||
with self.new_session() as db_session:
|
||||
if management_room := BuildParts.filter_by_one(
|
||||
system=True, id=self.management_room_id, build_id=self.id, db=db_session
|
||||
).data:
|
||||
return management_room
|
||||
return None
|
||||
|
||||
@property
|
||||
def top_flat(self):
|
||||
max_flat_no = 0
|
||||
for part in self.parts:
|
||||
if part.part_no > self.max_floor:
|
||||
max_flat_no = part.part_no
|
||||
return max_flat_no
|
||||
|
||||
@property
|
||||
def bottom_flat(self):
|
||||
min_flat_no = 0
|
||||
for part in self.parts:
|
||||
if part.part_no < self.max_floor:
|
||||
min_flat_no = part.part_no
|
||||
return min_flat_no
|
||||
|
||||
@property
|
||||
def human_livable_parts(self) -> tuple:
|
||||
parts = list(part for part in self.parts if part.human_livable)
|
||||
return parts, len(parts)
|
||||
|
||||
@property
|
||||
def livable_part_count(self):
|
||||
with self.new_session() as db_session:
|
||||
livable_parts = BuildParts.filter_all(
|
||||
BuildParts.build_id == self.id,
|
||||
BuildParts.human_livable == True,
|
||||
db=db_session,
|
||||
)
|
||||
if not livable_parts.data:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="There is no livable part in this building.",
|
||||
)
|
||||
return livable_parts.count
|
||||
|
||||
@property
|
||||
def part_type_count(self):
|
||||
with self.new_session() as db_session:
|
||||
building_types = None
|
||||
for part in self.parts:
|
||||
building_types = {}
|
||||
build_type = BuildTypes.filter_by_one(
|
||||
system=True, id=part.build_part_type_id, db=db_session
|
||||
).data
|
||||
if build_type.type_code in building_types:
|
||||
building_types[build_type.type_code]["list"].append(part.part_no)
|
||||
else:
|
||||
building_types[build_type.type_code] = {"list": [part.part_no]}
|
||||
|
||||
# for key, val in building_types.items():
|
||||
# list_parts = val["list"]
|
||||
# building_types[key] = {
|
||||
# "list": list_parts,
|
||||
# "min": min(list_parts),
|
||||
# "max": max(list_parts),
|
||||
# "count": len(list_parts),
|
||||
# }
|
||||
return building_types
|
||||
|
||||
|
||||
class BuildParts(CrudCollection):
|
||||
"""
|
||||
BuildParts class based on declarative_base and BaseMixin via session
|
||||
Attentions: Part_no is unique for each building and Every building must have a management section.!!! default no 0
|
||||
"""
|
||||
|
||||
__tablename__ = "build_parts"
|
||||
__exclude__fields__ = []
|
||||
__include__fields__ = []
|
||||
__enum_list__ = [("part_direction", "Directions", "NN")]
|
||||
|
||||
# https://adres.nvi.gov.tr/VatandasIslemleri/AdresSorgu
|
||||
address_gov_code: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Goverment Door Code"
|
||||
)
|
||||
# part_name: Mapped[str] = mapped_column(String(24), server_default="", nullable=False, comment="Part Name")
|
||||
part_no: Mapped[int] = mapped_column(
|
||||
Integer, server_default="0", nullable=False, comment="Part Number"
|
||||
)
|
||||
part_level: Mapped[int] = mapped_column(
|
||||
Integer, server_default="0", comment="Building Part Level"
|
||||
)
|
||||
part_code: Mapped[str] = mapped_column(
|
||||
String, server_default="", nullable=False, comment="Part Code"
|
||||
)
|
||||
part_gross_size: Mapped[int] = mapped_column(
|
||||
Integer, server_default="0", comment="Part Gross Size"
|
||||
)
|
||||
part_net_size: Mapped[int] = mapped_column(
|
||||
Integer, server_default="0", comment="Part Net Size"
|
||||
)
|
||||
default_accessory: Mapped[str] = mapped_column(
|
||||
Text, server_default="0", comment="Default Accessory"
|
||||
)
|
||||
human_livable: Mapped[bool] = mapped_column(
|
||||
Boolean, server_default="1", comment="Human Livable"
|
||||
)
|
||||
due_part_key: Mapped[str] = mapped_column(
|
||||
String, server_default="", nullable=False, comment="Constant Payment Group"
|
||||
)
|
||||
|
||||
build_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build.id"), nullable=False, comment="Building ID"
|
||||
)
|
||||
build_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Building UUID"
|
||||
)
|
||||
part_direction_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("api_enum_dropdown.id"), nullable=True
|
||||
)
|
||||
part_direction_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Part Direction UUID"
|
||||
)
|
||||
part_type_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_types.id"), nullable=False, comment="Building Part Type"
|
||||
)
|
||||
part_type_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Building Part Type UUID"
|
||||
)
|
||||
|
||||
buildings: Mapped["Build"] = relationship(
|
||||
"Build", back_populates="parts", foreign_keys=[build_id]
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("build_parts_ndx_1", build_id, part_no, unique=True),
|
||||
{"comment": "Part objects that are belong to building objects"},
|
||||
)
|
||||
|
||||
def part_name(self, db):
|
||||
if build_type := BuildTypes.filter_by_one(
|
||||
system=True, id=self.part_type_id, db=db
|
||||
).data:
|
||||
return f"{str(build_type.type_name).upper()} : {str(self.part_no).upper()}"
|
||||
return f"Undefined:{str(build_type.type_name).upper()}"
|
||||
|
||||
|
||||
class BuildLivingSpace(CrudCollection):
|
||||
"""
|
||||
LivingSpace class based on declarative_base and BaseMixin via session
|
||||
Owner or live person = Occupant of the build part
|
||||
+ Query OR(owner_person_id == person_id, life_person_id == person_id) AND (now(date))
|
||||
"""
|
||||
|
||||
__tablename__ = "build_living_space"
|
||||
__exclude__fields__ = []
|
||||
__include__fields__ = []
|
||||
|
||||
fix_value: Mapped[float] = mapped_column(
|
||||
Numeric(20, 6),
|
||||
server_default="0",
|
||||
comment="Fixed value is deducted from debit.",
|
||||
)
|
||||
fix_percent: Mapped[float] = mapped_column(
|
||||
Numeric(6, 2),
|
||||
server_default="0",
|
||||
comment="Fixed percent is deducted from debit.",
|
||||
)
|
||||
|
||||
agreement_no: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="Agreement No"
|
||||
)
|
||||
marketing_process: Mapped[bool] = mapped_column(Boolean, server_default="False")
|
||||
marketing_layer: Mapped[int] = mapped_column(Integer, server_default="0")
|
||||
|
||||
build_parts_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_parts.id"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
comment="Build Part ID",
|
||||
)
|
||||
build_parts_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Build Part UUID"
|
||||
)
|
||||
person_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("people.id"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
comment="Responsible People ID",
|
||||
)
|
||||
person_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Responsible People UUID"
|
||||
)
|
||||
occupant_type_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("occupant_types.id"),
|
||||
nullable=False,
|
||||
comment="Occupant Type",
|
||||
)
|
||||
occupant_type_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Occupant Type UUID"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
{"comment": "Living Space inside building parts that are related to people"},
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def find_living_from_customer_id(
|
||||
cls, customer_id, process_date, add_days: int = 32
|
||||
):
|
||||
with cls.new_session() as db_session:
|
||||
formatted_date = arrow.get(str(process_date))
|
||||
living_spaces = cls.filter_all(
|
||||
or_(
|
||||
cls.owner_person_id == customer_id,
|
||||
cls.life_person_id == customer_id,
|
||||
),
|
||||
cls.start_date < formatted_date - timedelta(days=add_days),
|
||||
cls.stop_date > formatted_date + timedelta(days=add_days),
|
||||
db=db_session,
|
||||
)
|
||||
return living_spaces.data, living_spaces.count
|
||||
|
||||
|
||||
class BuildManagement(CrudCollection):
|
||||
|
||||
__tablename__ = "build_management"
|
||||
__exclude__fields__ = []
|
||||
|
||||
discounted_percentage: Mapped[float] = mapped_column(
|
||||
Numeric(6, 2), server_default="0.00"
|
||||
) # %22
|
||||
discounted_price: Mapped[float] = mapped_column(
|
||||
Numeric(20, 2), server_default="0.00"
|
||||
) # Normal: 78.00 TL
|
||||
calculated_price: Mapped[float] = mapped_column(
|
||||
Numeric(20, 2), server_default="0.00"
|
||||
) # sana düz 75.00 TL yapar
|
||||
|
||||
occupant_type: Mapped[int] = mapped_column(
|
||||
ForeignKey("occupant_types.id"),
|
||||
nullable=False,
|
||||
comment="Occupant Type",
|
||||
)
|
||||
occupant_type_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Occupant Type UUID"
|
||||
)
|
||||
build_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build.id"), nullable=False, comment="Building ID"
|
||||
)
|
||||
build_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Building UUID"
|
||||
)
|
||||
build_parts_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_parts.id"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
comment="Build Part ID",
|
||||
)
|
||||
build_parts_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Build Part UUID"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"build_management_ndx_00",
|
||||
build_parts_id,
|
||||
occupant_type,
|
||||
"expiry_starts",
|
||||
unique=True,
|
||||
),
|
||||
{"comment": "Management of the building parts that are related to people"},
|
||||
)
|
||||
|
||||
|
||||
class BuildArea(CrudCollection):
|
||||
"""
|
||||
Builds class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "build_area"
|
||||
__exclude__fields__ = []
|
||||
|
||||
area_name: Mapped[str] = mapped_column(String, server_default="")
|
||||
area_code: Mapped[str] = mapped_column(String, server_default="")
|
||||
area_type: Mapped[str] = mapped_column(String, server_default="GREEN")
|
||||
area_direction: Mapped[str] = mapped_column(String(2), server_default="NN")
|
||||
area_gross_size: Mapped[float] = mapped_column(Numeric(20, 6), server_default="0")
|
||||
area_net_size: Mapped[float] = mapped_column(Numeric(20, 6), server_default="0")
|
||||
width = mapped_column(Integer, server_default="0")
|
||||
size = mapped_column(Integer, server_default="0")
|
||||
|
||||
build_id: Mapped[int] = mapped_column(ForeignKey("build.id"))
|
||||
build_uu_id: Mapped[str] = mapped_column(String, comment="Building UUID")
|
||||
part_type_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_types.id"), nullable=True, comment="Building Part Type"
|
||||
)
|
||||
part_type_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Building Part Type UUID"
|
||||
)
|
||||
|
||||
# buildings: Mapped["Build"] = relationship(
|
||||
# "Build", back_populates="areas", foreign_keys=[build_id]
|
||||
# )
|
||||
|
||||
_table_args_ = (
|
||||
Index("_edm_build_parts_area_ndx_00", build_id, area_code, unique=True),
|
||||
)
|
||||
|
||||
|
||||
class BuildSites(CrudCollection):
|
||||
"""
|
||||
Builds class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "build_sites"
|
||||
__exclude__fields__ = []
|
||||
__include__fields__ = []
|
||||
|
||||
site_name: Mapped[str] = mapped_column(String(24), nullable=False)
|
||||
site_no: Mapped[str] = mapped_column(String(8), nullable=False)
|
||||
|
||||
address_id: Mapped[int] = mapped_column(ForeignKey("addresses.id"))
|
||||
address_uu_id: Mapped[str] = mapped_column(String, comment="Address UUID")
|
||||
|
||||
# addresses: Mapped["Address"] = relationship(
|
||||
# "Address", back_populates="site", foreign_keys=[address_id]
|
||||
# )
|
||||
# buildings: Mapped["Build"] = relationship(
|
||||
# "Build", back_populates="sites", foreign_keys="Build.site_id"
|
||||
# )
|
||||
|
||||
__table_args__ = (
|
||||
Index("_sites_ndx_01", site_no, site_name),
|
||||
{"comment": "Sites that groups building objets"},
|
||||
)
|
||||
|
||||
|
||||
class BuildCompaniesProviding(CrudCollection):
|
||||
""" """
|
||||
|
||||
__tablename__ = "build_companies_providing"
|
||||
__exclude__fields__ = []
|
||||
__include__fields__ = []
|
||||
|
||||
build_id = mapped_column(
|
||||
ForeignKey("build.id"), nullable=False, comment="Building ID"
|
||||
)
|
||||
build_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Providing UUID"
|
||||
)
|
||||
company_id: Mapped[int] = mapped_column(ForeignKey("companies.id"))
|
||||
company_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Providing UUID"
|
||||
)
|
||||
provide_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("api_enum_dropdown.id"), nullable=True
|
||||
)
|
||||
provide_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Providing UUID"
|
||||
)
|
||||
contract_id: Mapped[int] = mapped_column(
|
||||
Integer, ForeignKey("companies.id"), nullable=True
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"_build_companies_providing_ndx_00",
|
||||
build_id,
|
||||
company_id,
|
||||
provide_id,
|
||||
unique=True,
|
||||
),
|
||||
{"comment": "Companies providing services for building"},
|
||||
)
|
||||
|
||||
|
||||
class BuildPersonProviding(CrudCollection):
|
||||
""" """
|
||||
|
||||
__tablename__ = "build_person_providing"
|
||||
__exclude__fields__ = []
|
||||
__include__fields__ = []
|
||||
|
||||
build_id = mapped_column(
|
||||
ForeignKey("build.id"), nullable=False, comment="Building ID"
|
||||
)
|
||||
build_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Providing UUID"
|
||||
)
|
||||
people_id: Mapped[int] = mapped_column(ForeignKey("people.id"))
|
||||
people_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="People UUID"
|
||||
)
|
||||
provide_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("api_enum_dropdown.id"), nullable=True
|
||||
)
|
||||
provide_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Providing UUID"
|
||||
)
|
||||
contract_id: Mapped[int] = mapped_column(
|
||||
Integer, ForeignKey("companies.id"), nullable=True
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"_build_person_providing_ndx_00",
|
||||
build_id,
|
||||
people_id,
|
||||
provide_id,
|
||||
unique=True,
|
||||
),
|
||||
{"comment": "People providing services for building"},
|
||||
)
|
||||
|
|
@ -0,0 +1,888 @@
|
|||
import math
|
||||
import arrow
|
||||
from datetime import datetime, timedelta
|
||||
from decimal import Decimal
|
||||
from typing import List, Any
|
||||
from fastapi import HTTPException, status
|
||||
|
||||
from schemas.base_imports import (
|
||||
CrudCollection,
|
||||
String,
|
||||
ForeignKey,
|
||||
Index,
|
||||
SmallInteger,
|
||||
Boolean,
|
||||
TIMESTAMP,
|
||||
Text,
|
||||
Numeric,
|
||||
Integer,
|
||||
mapped_column,
|
||||
Mapped,
|
||||
relationship,
|
||||
)
|
||||
|
||||
|
||||
class BuildDecisionBook(CrudCollection):
|
||||
"""
|
||||
Builds class based on declarative_base and BaseMixin via session
|
||||
The start dates of the decision log periods are determined from the 'decision_period_date' field in the decision log table within the building information.
|
||||
decision_period_date = Her yıl yapılan karar toplantısı + 365 gün her yıl tekrar eden
|
||||
decision_book_pdf_path: Karar defteri pdf dosyasının yolu
|
||||
resp_company_fix_wage: Karar defterinin oluşmasını sağlayan dışardaki danışmanlık ücreti
|
||||
is_out_sourced: Karar defterinin dışardan alınan hizmetle oluşturulup oluşturulmadığı
|
||||
contact_agreement_path: Karar defterinin oluşmasını sağlayan dışardaki danışmanlık anlaşması dosyasının yolu
|
||||
contact_agreement_date: Karar defterinin oluşmasını sağlayan dışardaki danışmanlık anlaşma tarihi
|
||||
meeting_date: Karar defterinin oluşmasını sağlayan toplantı tarihi
|
||||
decision_type: Karar defterinin tipi (Bina Yönetim Toplantısı (BYT), Yıllık Acil Toplantı (YAT)
|
||||
"""
|
||||
|
||||
__tablename__ = "build_decision_book"
|
||||
__exclude__fields__ = []
|
||||
|
||||
decision_book_pdf_path: Mapped[str] = mapped_column(
|
||||
String, server_default="", nullable=True
|
||||
)
|
||||
resp_company_fix_wage: Mapped[float] = mapped_column(
|
||||
Numeric(10, 2), server_default="0"
|
||||
) #
|
||||
is_out_sourced: Mapped[bool] = mapped_column(Boolean, server_default="0")
|
||||
meeting_date: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True), server_default="1900-01-01"
|
||||
)
|
||||
decision_type: Mapped[str] = mapped_column(String(3), server_default="RBM")
|
||||
meeting_is_completed: Mapped[bool] = mapped_column(Boolean, server_default="0")
|
||||
meeting_completed_date: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True), nullable=True, comment="Meeting Completed Date"
|
||||
)
|
||||
|
||||
build_id: Mapped[int] = mapped_column(ForeignKey("build.id"), nullable=False)
|
||||
build_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Build UUID"
|
||||
)
|
||||
resp_company_id: Mapped[int] = mapped_column(ForeignKey("companies.id"))
|
||||
resp_company_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Company UUID"
|
||||
)
|
||||
contact_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("contracts.id"), nullable=True, comment="Contract id"
|
||||
)
|
||||
contact_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Contract UUID"
|
||||
)
|
||||
|
||||
buildings: Mapped["Build"] = relationship(
|
||||
"Build",
|
||||
back_populates="decision_books",
|
||||
foreign_keys=build_id,
|
||||
)
|
||||
decision_book_items: Mapped[List["BuildDecisionBookItems"]] = relationship(
|
||||
"BuildDecisionBookItems",
|
||||
back_populates="decision_books",
|
||||
foreign_keys="BuildDecisionBookItems.build_decision_book_id",
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("build_decision_book_ndx_011", meeting_date, build_id),
|
||||
Index("build_decision_book_ndx_011", build_id, "expiry_starts", "expiry_ends"),
|
||||
{
|
||||
"comment": "Decision Book objects that are related to decision taken at building meetings"
|
||||
},
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def retrieve_active_rbm(cls):
|
||||
from Schemas.building.build import Build
|
||||
|
||||
with cls.new_session() as db_session:
|
||||
related_build = Build.find_one(id=cls.build_id)
|
||||
related_date = arrow.get(related_build.build_date)
|
||||
date_processed = related_date.replace(
|
||||
year=arrow.now().date().year, month=related_date.month, day=1
|
||||
)
|
||||
if arrow.now().date() <= date_processed:
|
||||
book = cls.filter_one(
|
||||
cls.expiry_ends <= date_processed,
|
||||
cls.decision_type == "RBM",
|
||||
cls.build_id == related_build.id,
|
||||
db=db_session,
|
||||
).data
|
||||
if not book:
|
||||
cls.raise_http_exception(
|
||||
status_code="HTTP_404_NOT_FOUND",
|
||||
error_case="NOTFOUND",
|
||||
message=f"Decision Book is not found for {related_build.build_name}-RBM",
|
||||
data=dict(
|
||||
build_id=str(related_build.uu_id),
|
||||
build_name=related_build.build_name,
|
||||
decision_type="RBM",
|
||||
),
|
||||
)
|
||||
return book
|
||||
return
|
||||
|
||||
@property
|
||||
def semester(self):
|
||||
start_format = "".join(
|
||||
[str(self.expiry_starts.year), "-", str(self.expiry_starts.month)]
|
||||
)
|
||||
end_format = "".join(
|
||||
[str(self.expiry_ends.year), "-", str(self.expiry_ends.month)]
|
||||
)
|
||||
return "".join([start_format, " ", end_format])
|
||||
|
||||
def check_book_is_valid(self, bank_date: str):
|
||||
if all(
|
||||
[True if letter in str(bank_date) else False for letter in ["-", " ", ":"]]
|
||||
):
|
||||
bank_date = datetime.strptime(str(bank_date), "%Y-%m-%d %H:%M:%S")
|
||||
date_valid = (
|
||||
arrow.get(self.expiry_starts)
|
||||
< arrow.get(bank_date)
|
||||
< arrow.get(self.expiry_ends)
|
||||
)
|
||||
return date_valid and self.active and not self.deleted
|
||||
|
||||
# @classmethod
|
||||
# def retrieve_valid_book(cls, bank_date, iban):
|
||||
# from Schemas import (
|
||||
# BuildIbans,
|
||||
# )
|
||||
# with cls.new_session() as db_session:
|
||||
# if all(
|
||||
# [True if letter in str(bank_date) else False for letter in ["-", " ", ":"]]
|
||||
# ):
|
||||
# bank_date = datetime.strptime(str(bank_date), "%Y-%m-%d %H:%M:%S")
|
||||
# build_iban = BuildIbans.find_one(iban=iban)
|
||||
# decision_book: cls = cls.filter_one(
|
||||
# cls.build_id == build_iban.build_id,
|
||||
# cls.expiry_starts < bank_date,
|
||||
# cls.expiry_ends > bank_date,
|
||||
# cls.active == True,
|
||||
# cls.deleted == False,
|
||||
# db=db_session
|
||||
# ).data
|
||||
# decision_book.check_book_is_valid(bank_date.__str__())
|
||||
# return decision_book
|
||||
# return
|
||||
|
||||
|
||||
class BuildDecisionBookInvitations(CrudCollection):
|
||||
"""
|
||||
Builds class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "build_decision_book_invitations"
|
||||
__exclude__fields__ = []
|
||||
|
||||
build_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
build_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Build UUID"
|
||||
)
|
||||
decision_book_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_decision_book.id"), nullable=False
|
||||
)
|
||||
decision_book_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Decision Book UUID"
|
||||
)
|
||||
|
||||
invitation_type: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Invite Type"
|
||||
)
|
||||
invitation_attempt: Mapped[int] = mapped_column(SmallInteger, server_default="1")
|
||||
living_part_count: Mapped[int] = mapped_column(SmallInteger, server_default="1")
|
||||
living_part_percentage: Mapped[float] = mapped_column(
|
||||
Numeric(10, 2), server_default="0.51"
|
||||
)
|
||||
|
||||
message: Mapped[str] = mapped_column(
|
||||
Text, nullable=True, comment="Invitation Message"
|
||||
)
|
||||
planned_date: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True), nullable=False, comment="Planned Meeting Date"
|
||||
)
|
||||
planned_date_expires: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True), nullable=False, comment="Planned Meeting Date Expires"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"_build_decision_book_invitations_ndx_01",
|
||||
invitation_type,
|
||||
planned_date,
|
||||
invitation_attempt,
|
||||
unique=True,
|
||||
),
|
||||
{"comment": "People that are invited to building meetings."},
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def check_invites_are_ready_for_meeting(cls, selected_decision_book, token_dict):
|
||||
with cls.new_session() as db_session:
|
||||
first_book_invitation = BuildDecisionBookInvitations.filter_one(
|
||||
BuildDecisionBookInvitations.build_id
|
||||
== token_dict.selected_occupant.build_id,
|
||||
BuildDecisionBookInvitations.decision_book_id
|
||||
== selected_decision_book.id,
|
||||
BuildDecisionBookInvitations.invitation_attempt == 1,
|
||||
db=db_session,
|
||||
).data
|
||||
if not first_book_invitation:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"First Meeting Invitation is not found for Decision Book UUID : {selected_decision_book.uu_id}",
|
||||
)
|
||||
need_attend_count = int(first_book_invitation.living_part_count) * Decimal(
|
||||
first_book_invitation.living_part_percentage
|
||||
)
|
||||
valid_invite_count = (
|
||||
BuildDecisionBookPerson.filter_all_system(
|
||||
BuildDecisionBookPerson.invite_id == first_book_invitation.id,
|
||||
BuildDecisionBookPerson.build_decision_book_id
|
||||
== selected_decision_book.id,
|
||||
BuildDecisionBookPerson.is_attending == True,
|
||||
db=db_session,
|
||||
)
|
||||
.query.distinct(BuildDecisionBookPerson.person_id)
|
||||
.count()
|
||||
)
|
||||
|
||||
second_book_invitation = BuildDecisionBookInvitations.filter_one_system(
|
||||
BuildDecisionBookInvitations.build_id
|
||||
== token_dict.selected_occupant.build_id,
|
||||
BuildDecisionBookInvitations.decision_book_id
|
||||
== selected_decision_book.id,
|
||||
BuildDecisionBookInvitations.invitation_attempt == 2,
|
||||
db=db_session,
|
||||
).data
|
||||
if (
|
||||
not valid_invite_count >= need_attend_count
|
||||
and not second_book_invitation
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"In order meeting to be held, {math.ceil(need_attend_count)} people must attend "
|
||||
f"to the meeting. Only {valid_invite_count} people are attending to the meeting.",
|
||||
)
|
||||
return first_book_invitation or second_book_invitation
|
||||
|
||||
|
||||
class BuildDecisionBookPerson(CrudCollection):
|
||||
"""
|
||||
Builds class based on declarative_base and BaseMixin via session
|
||||
Karar Defteri toplantılarına katılan kişiler veya yetkililer
|
||||
dues_percent_discount: Katılımcının aidat indirim oranı Aidatdan yüzde indirim alır
|
||||
dues_fix_discount: Katılımcının aidat sabit miktarı Aidatdan sabit bir miktar indirim alır
|
||||
dues_discount_approval_date: Bu kişinin indiriminin onayladığı tarih
|
||||
management_typecode: Kişinin toplantı görevi
|
||||
"""
|
||||
|
||||
__tablename__ = "build_decision_book_person"
|
||||
__exclude__fields__ = []
|
||||
__enum_list__ = [("management_typecode", "BuildManagementType", "bm")]
|
||||
|
||||
dues_percent_discount: Mapped[int] = mapped_column(SmallInteger, server_default="0")
|
||||
dues_fix_discount: Mapped[float] = mapped_column(Numeric(10, 2), server_default="0")
|
||||
dues_discount_approval_date: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True), server_default="1900-01-01 00:00:00"
|
||||
)
|
||||
send_date: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True), nullable=False, comment="Confirmation Date"
|
||||
)
|
||||
is_attending: Mapped[bool] = mapped_column(
|
||||
Boolean, server_default="0", comment="Occupant is Attending to invitation"
|
||||
)
|
||||
confirmed_date: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True), nullable=True, comment="Confirmation Date"
|
||||
)
|
||||
token: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="Invitation Token"
|
||||
)
|
||||
|
||||
vicarious_person_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("people.id"), nullable=True, comment="Vicarious Person ID"
|
||||
)
|
||||
vicarious_person_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Vicarious Person UUID"
|
||||
)
|
||||
|
||||
invite_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_decision_book_invitations.id"), nullable=False
|
||||
)
|
||||
invite_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Invite UUID"
|
||||
)
|
||||
|
||||
build_decision_book_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_decision_book.id"), nullable=False
|
||||
)
|
||||
build_decision_book_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Decision Book UUID"
|
||||
)
|
||||
build_living_space_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_living_space.id"), nullable=False
|
||||
)
|
||||
build_living_space_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Living Space UUID"
|
||||
)
|
||||
person_id: Mapped[int] = mapped_column(ForeignKey("people.id"), nullable=False)
|
||||
# person_uu_id: Mapped[str] = mapped_column(String, nullable=False, comment="Person UUID")
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"_build_decision_book_person_ndx_01",
|
||||
build_decision_book_id,
|
||||
invite_id,
|
||||
build_living_space_id,
|
||||
unique=True,
|
||||
),
|
||||
{"comment": "People that are attended to building meetings."},
|
||||
)
|
||||
|
||||
def retrieve_all_occupant_types(self):
|
||||
with self.new_session() as db_session:
|
||||
all_decision_book_people = self.filter_all_system(
|
||||
BuildDecisionBookPersonOccupants.invite_id == self.invite_id,
|
||||
db=db_session,
|
||||
)
|
||||
BuildDecisionBookPersonOccupants.pre_query = all_decision_book_people.query
|
||||
return BuildDecisionBookPersonOccupants.filter_all_system(
|
||||
db=db_session
|
||||
).data
|
||||
|
||||
def get_occupant_types(self):
|
||||
with self.new_session() as db_session:
|
||||
if occupants := BuildDecisionBookPersonOccupants.filter_all(
|
||||
BuildDecisionBookPersonOccupants.build_decision_book_person_id
|
||||
== self.id,
|
||||
db=db_session,
|
||||
).data:
|
||||
return occupants
|
||||
return
|
||||
|
||||
def check_occupant_type(self, occupant_type):
|
||||
with self.new_session() as db_session:
|
||||
book_person_occupant_type = BuildDecisionBookPersonOccupants.filter_one(
|
||||
BuildDecisionBookPersonOccupants.build_decision_book_person_id
|
||||
== self.id,
|
||||
BuildDecisionBookPersonOccupants.occupant_type_id == occupant_type.id,
|
||||
BuildDecisionBookPersonOccupants.active == True,
|
||||
BuildDecisionBookPersonOccupants.is_confirmed == True,
|
||||
db=db_session,
|
||||
).data
|
||||
if not book_person_occupant_type:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Occupant Type : {occupant_type.occupant_code} is not found in "
|
||||
f"Decision Book Person UUID {self.uu_id}",
|
||||
)
|
||||
|
||||
|
||||
class BuildDecisionBookPersonOccupants(CrudCollection):
|
||||
"""
|
||||
Builds class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "build_decision_book_person_occupants"
|
||||
__exclude__fields__ = []
|
||||
|
||||
build_decision_book_person_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_decision_book_person.id"), nullable=False
|
||||
)
|
||||
build_decision_book_person_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Decision Book Person UUID"
|
||||
)
|
||||
invite_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_decision_book_invitations.id"), nullable=True
|
||||
)
|
||||
invite_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Invite UUID"
|
||||
)
|
||||
|
||||
occupant_type_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("occupant_types.id"), nullable=False
|
||||
)
|
||||
occupant_type_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Occupant UUID"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"_build_decision_book_person_occupants_ndx_01",
|
||||
build_decision_book_person_id,
|
||||
occupant_type_id,
|
||||
unique=True,
|
||||
),
|
||||
{"comment": "Occupant Types of People that are attended to building meetings."},
|
||||
)
|
||||
|
||||
|
||||
class BuildDecisionBookItems(CrudCollection):
|
||||
"""
|
||||
Builds class based on declarative_base and BaseMixin via session
|
||||
item_commentary = metine itiraz şerh maddesi için
|
||||
item_order = maddelerin sıralanma numarası
|
||||
item_objection = maddelerin itiraz şerhi Text şeklinde
|
||||
"""
|
||||
|
||||
__tablename__ = "build_decision_book_items"
|
||||
__exclude__fields__ = []
|
||||
|
||||
item_order: Mapped[int] = mapped_column(
|
||||
SmallInteger, nullable=False, comment="Order Number of Item"
|
||||
)
|
||||
item_comment: Mapped[str] = mapped_column(
|
||||
Text, nullable=False, comment="Comment Content"
|
||||
)
|
||||
item_objection: Mapped[str] = mapped_column(
|
||||
Text, nullable=True, comment="Objection Content"
|
||||
)
|
||||
info_is_completed: Mapped[bool] = mapped_column(
|
||||
Boolean, server_default="0", comment="Info process is Completed"
|
||||
)
|
||||
is_payment_created: Mapped[bool] = mapped_column(
|
||||
Boolean, server_default="0", comment="Are payment Records Created"
|
||||
)
|
||||
|
||||
info_type_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("api_enum_dropdown.id"), nullable=True
|
||||
)
|
||||
info_type_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Info Type UUID"
|
||||
)
|
||||
|
||||
build_decision_book_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_decision_book.id"), nullable=False
|
||||
)
|
||||
build_decision_book_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Decision Book UUID"
|
||||
)
|
||||
item_short_comment: Mapped[str] = mapped_column(
|
||||
String(24),
|
||||
nullable=True,
|
||||
comment="This field is reserved for use in grouping data or in the pivot heading.",
|
||||
)
|
||||
|
||||
decision_books: Mapped["BuildDecisionBook"] = relationship(
|
||||
"BuildDecisionBook",
|
||||
back_populates="decision_book_items",
|
||||
foreign_keys=[build_decision_book_id],
|
||||
)
|
||||
decision_book_project: Mapped["BuildDecisionBookProjects"] = relationship(
|
||||
"BuildDecisionBookProjects",
|
||||
back_populates="build_decision_book_item",
|
||||
foreign_keys="BuildDecisionBookProjects.build_decision_book_item_id",
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("_build_decision_book_item_ndx_01", build_decision_book_id),
|
||||
Index(
|
||||
"_build_decision_book_item_ndx_02",
|
||||
build_decision_book_id,
|
||||
item_order,
|
||||
unique=True,
|
||||
),
|
||||
{
|
||||
"comment": "Decision Book Items that are related to decision taken at building meetings"
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class BuildDecisionBookItemsUnapproved(CrudCollection):
|
||||
"""
|
||||
Builds class based on declarative_base and BaseMixin via session unapproved personnel
|
||||
"""
|
||||
|
||||
__tablename__ = "build_decision_book_items_unapproved"
|
||||
__exclude__fields__ = []
|
||||
|
||||
item_objection: Mapped[str] = mapped_column(
|
||||
Text, nullable=False, comment="Objection Content"
|
||||
)
|
||||
item_order: Mapped[int] = mapped_column(
|
||||
SmallInteger, nullable=False, comment="Order Number"
|
||||
)
|
||||
|
||||
decision_book_item_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_decision_book_items.id"), nullable=False
|
||||
)
|
||||
decision_book_item_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Decision Book Item"
|
||||
)
|
||||
person_id: Mapped[int] = mapped_column(ForeignKey("people.id"), nullable=False)
|
||||
person_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Person UUID"
|
||||
)
|
||||
build_decision_book_item: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_decision_book_items.id"), nullable=False
|
||||
)
|
||||
build_decision_book_item_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Decision Book Item UUID"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("_build_decision_book_item_unapproved_ndx_01", build_decision_book_item),
|
||||
{
|
||||
"comment": "People that are unapproved partially or completely in decision book items"
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class BuildDecisionBookPayments(CrudCollection):
|
||||
"""
|
||||
Builds class based on declarative_base and BaseMixin via session
|
||||
period_time = to_char(NEW.process_date, 'YYYY-MM');
|
||||
"""
|
||||
|
||||
__tablename__ = "build_decision_book_payments"
|
||||
__exclude__fields__ = []
|
||||
__enum_list__ = [("receive_debit", "DebitTypes", "D")]
|
||||
|
||||
payment_plan_time_periods: Mapped[str] = mapped_column(
|
||||
String(10), nullable=False, comment="Payment Plan Time Periods"
|
||||
)
|
||||
process_date: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True), nullable=False, comment="Payment Due Date"
|
||||
)
|
||||
payment_amount: Mapped[float] = mapped_column(
|
||||
Numeric(16, 2), nullable=False, comment="Payment Amount"
|
||||
)
|
||||
currency: Mapped[str] = mapped_column(String(8), server_default="TRY")
|
||||
|
||||
payment_types_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("api_enum_dropdown.id"), nullable=True
|
||||
)
|
||||
payment_types_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Dues Type UUID"
|
||||
)
|
||||
|
||||
period_time: Mapped[str] = mapped_column(String(12))
|
||||
process_date_y: Mapped[int] = mapped_column(SmallInteger)
|
||||
process_date_m: Mapped[int] = mapped_column(SmallInteger)
|
||||
|
||||
build_decision_book_item_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_decision_book_items.id"),
|
||||
nullable=False,
|
||||
comment="Build Decision Book Item ID",
|
||||
)
|
||||
build_decision_book_item_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Decision Book Item UUID"
|
||||
)
|
||||
# build_decision_book_id: Mapped[int] = mapped_column(
|
||||
# ForeignKey("build_decision_book.id"), nullable=True
|
||||
# )
|
||||
# build_decision_book_uu_id: Mapped[str] = mapped_column(
|
||||
# String, nullable=True, comment="Decision Book UUID"
|
||||
# )
|
||||
build_parts_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_parts.id"), nullable=False
|
||||
)
|
||||
build_parts_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Build Part UUID"
|
||||
)
|
||||
decision_book_project_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_decision_book_projects.id"),
|
||||
nullable=True,
|
||||
comment="Decision Book Project ID",
|
||||
)
|
||||
decision_book_project_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Decision Book Project UUID"
|
||||
)
|
||||
account_records_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("account_records.id"), nullable=True
|
||||
)
|
||||
account_records_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Account Record UU ID"
|
||||
)
|
||||
|
||||
# budget_records_id: Mapped[int] = mapped_column(ForeignKey("account_records.id"), nullable=True)
|
||||
# budget_records_uu_id: Mapped[str] = mapped_column(
|
||||
# String, nullable=True, comment="Budget UUID"
|
||||
# )
|
||||
# accounting_id: Mapped[int] = mapped_column(ForeignKey("account_detail.id"), nullable=True)
|
||||
# accounting_uu_id: Mapped[str] = mapped_column(
|
||||
# String, nullable=True, comment="Accounting UUID"
|
||||
# )
|
||||
# receive_debit_id: Mapped[int] = mapped_column(ForeignKey("api_enum_dropdown.id"), nullable=True)
|
||||
# receive_debit_uu_id: Mapped[str] = mapped_column(String, nullable=True, comment="Debit UUID")
|
||||
|
||||
# accounting: Mapped["AccountDetail"] = relationship(
|
||||
# "AccountDetail",
|
||||
# back_populates="decision_book_payment_detail",
|
||||
# foreign_keys=[accounting_id],
|
||||
# )
|
||||
#
|
||||
# decision_book_master: Mapped["BuildDecisionBookPaymentsMaster"] = relationship(
|
||||
# "BuildDecisionBookPaymentsMaster",
|
||||
# back_populates="decision_book_payment_detail",
|
||||
# foreign_keys=[build_decision_book_payments_master_id],
|
||||
# )
|
||||
# budget_records: Mapped["CompanyBudgetRecords"] = relationship(
|
||||
# "CompanyBudgetRecords",
|
||||
# back_populates="decision_book_payment_detail",
|
||||
# foreign_keys=[budget_records_id],
|
||||
# )
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"build_decision_book_payments_detail_ndx_00",
|
||||
build_decision_book_item_id,
|
||||
build_parts_id,
|
||||
payment_plan_time_periods,
|
||||
process_date,
|
||||
payment_types_id,
|
||||
account_records_id,
|
||||
unique=True,
|
||||
),
|
||||
Index("build_decision_book_payments_detail_ndx_01", account_records_id),
|
||||
{"comment": "Payment Details of Decision Book Payments"},
|
||||
)
|
||||
|
||||
|
||||
class BuildDecisionBookLegal(CrudCollection):
|
||||
"""
|
||||
Builds class based on declarative_base and BaseMixin via session
|
||||
lawsuits_type C:Court Mehkeme M: mediator arabulucu
|
||||
"""
|
||||
|
||||
__tablename__ = "build_decision_book_legal"
|
||||
__exclude__fields__ = []
|
||||
|
||||
period_start_date: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True), nullable=False, comment="Start Date of Legal Period"
|
||||
)
|
||||
lawsuits_decision_number: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Lawsuits Decision Number"
|
||||
)
|
||||
lawsuits_decision_date: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True), nullable=False, comment="Lawsuits Decision Date"
|
||||
)
|
||||
|
||||
period_stop_date: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True), server_default="2099-12-31 23:59:59"
|
||||
)
|
||||
decision_book_pdf_path: Mapped[str] = mapped_column(
|
||||
String, server_default="", nullable=True
|
||||
)
|
||||
resp_company_total_wage: Mapped[float] = mapped_column(
|
||||
Numeric(10, 2), server_default="0", nullable=True
|
||||
)
|
||||
contact_agreement_path: Mapped[str] = mapped_column(
|
||||
String, server_default="", nullable=True
|
||||
)
|
||||
contact_agreement_date: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True), server_default="1900-01-01 00:00:00", nullable=True
|
||||
)
|
||||
meeting_date: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True), server_default="1900-01-01 00:00:00"
|
||||
)
|
||||
lawsuits_type: Mapped[str] = mapped_column(String(1), server_default="C")
|
||||
lawsuits_name: Mapped[str] = mapped_column(String(128))
|
||||
lawsuits_note: Mapped[str] = mapped_column(String(512))
|
||||
lawyer_cost: Mapped[float] = mapped_column(Numeric(20, 2))
|
||||
mediator_lawyer_cost: Mapped[float] = mapped_column(Numeric(20, 2))
|
||||
other_cost: Mapped[float] = mapped_column(Numeric(20, 2))
|
||||
legal_cost: Mapped[float] = mapped_column(Numeric(20, 2))
|
||||
approved_cost: Mapped[float] = mapped_column(Numeric(20, 2))
|
||||
total_price: Mapped[float] = mapped_column(Numeric(20, 2))
|
||||
|
||||
build_db_item_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_decision_book_items.id"), nullable=False
|
||||
)
|
||||
build_db_item_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Decision Book Item UUID"
|
||||
)
|
||||
resp_attorney_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("people.id"), nullable=False
|
||||
)
|
||||
resp_attorney_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Attorney UUID"
|
||||
)
|
||||
resp_attorney_company_id: Mapped[int] = mapped_column(ForeignKey("companies.id"))
|
||||
resp_attorney_company_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Company UUID"
|
||||
)
|
||||
mediator_lawyer_person_id: Mapped[int] = mapped_column(ForeignKey("people.id"))
|
||||
mediator_lawyer_person_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Mediator Lawyer UUID"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("_build_decision_book_legal_ndx_00", meeting_date),
|
||||
{
|
||||
"comment": "Legal items related to decision book items recoreded at building meetings"
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class BuildDecisionBookProjects(CrudCollection):
|
||||
"""
|
||||
Builds class based on declarative_base and BaseMixin via session
|
||||
project_type = C:Court Mehkeme M: mediator arabulucu
|
||||
"""
|
||||
|
||||
__tablename__ = "build_decision_book_projects"
|
||||
__exclude__fields__ = []
|
||||
|
||||
project_no: Mapped[str] = mapped_column(
|
||||
String(12), nullable=True, comment="Project Number of Decision Book"
|
||||
)
|
||||
project_name: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Project Name"
|
||||
)
|
||||
project_start_date: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True), nullable=False, comment="Project Start Date"
|
||||
)
|
||||
project_stop_date: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True), server_default="2099-12-31 23:59:59"
|
||||
)
|
||||
project_type: Mapped[str] = mapped_column(String, server_default="C")
|
||||
project_note: Mapped[str] = mapped_column(Text)
|
||||
|
||||
decision_book_pdf_path: Mapped[str] = mapped_column(
|
||||
String, server_default="", nullable=True
|
||||
)
|
||||
is_completed: Mapped[bool] = mapped_column(
|
||||
Boolean, server_default="0", comment="Project is Completed"
|
||||
)
|
||||
status_code: Mapped[int] = mapped_column(SmallInteger, nullable=True)
|
||||
resp_company_fix_wage: Mapped[float] = mapped_column(
|
||||
Numeric(10, 2), server_default="0"
|
||||
)
|
||||
is_out_sourced: Mapped[bool] = mapped_column(Boolean, server_default="0")
|
||||
|
||||
meeting_date: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True), server_default="1900-01-01 00:00:00", index=True
|
||||
)
|
||||
currency: Mapped[str] = mapped_column(String(8), server_default="TRY")
|
||||
bid_price: Mapped[float] = mapped_column(Numeric(16, 4), server_default="0")
|
||||
approved_price: Mapped[float] = mapped_column(Numeric(16, 4), server_default="0")
|
||||
final_price: Mapped[float] = mapped_column(Numeric(16, 4), server_default="0")
|
||||
|
||||
contact_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("contracts.id"), nullable=True, comment="Contract id"
|
||||
)
|
||||
contact_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Contract UUID"
|
||||
)
|
||||
build_decision_book_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_decision_book.id"), nullable=False
|
||||
)
|
||||
build_decision_book_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Decision Book UUID"
|
||||
)
|
||||
build_decision_book_item_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_decision_book_items.id"), nullable=False
|
||||
)
|
||||
build_decision_book_item_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Decision Book Item UUID"
|
||||
)
|
||||
project_response_living_space_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_living_space.id"),
|
||||
nullable=True,
|
||||
comment="Project Response Person ID",
|
||||
)
|
||||
project_response_living_space_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Project Response Person UUID"
|
||||
)
|
||||
resp_company_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("companies.id"), nullable=True
|
||||
)
|
||||
resp_company_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Company UUID"
|
||||
)
|
||||
|
||||
build_decision_book_item: Mapped["BuildDecisionBookItems"] = relationship(
|
||||
"BuildDecisionBookItems",
|
||||
back_populates="decision_book_project",
|
||||
foreign_keys=[build_decision_book_item_id],
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"_build_decision_book_project_ndx_00",
|
||||
project_no,
|
||||
project_start_date,
|
||||
unique=True,
|
||||
),
|
||||
{
|
||||
"comment": "Project related to decision taken at building meetings on book items"
|
||||
},
|
||||
)
|
||||
|
||||
@property
|
||||
def get_project_year(self):
|
||||
return self.decision_book_items.decision_books.period_start_date.year
|
||||
|
||||
@property
|
||||
def get_project_no(self):
|
||||
return f"{self.get_project_year}-{str(self.id)[-4:].zfill(4)}"
|
||||
|
||||
|
||||
class BuildDecisionBookProjectPerson(CrudCollection):
|
||||
"""
|
||||
Builds class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "build_decision_book_project_person"
|
||||
__exclude__fields__ = []
|
||||
# __enum_list__ = [("management_typecode", "ProjectTeamTypes", "PTT-EMP")]
|
||||
|
||||
dues_percent_discount: Mapped[int] = mapped_column(SmallInteger, server_default="0")
|
||||
job_fix_wage: Mapped[float] = mapped_column(Numeric(10, 2), server_default="0")
|
||||
bid_price: Mapped[float] = mapped_column(Numeric(10, 2), server_default="0")
|
||||
decision_price: Mapped[float] = mapped_column(Numeric(10, 2), server_default="0")
|
||||
|
||||
build_decision_book_project_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_decision_book_projects.id"), nullable=False
|
||||
)
|
||||
build_decision_book_project_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Decision Book Project UUID"
|
||||
)
|
||||
living_space_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_living_space.id"), nullable=False
|
||||
)
|
||||
living_space_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Living Space UUID"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
{"comment": "People that are attended to building project meetings."},
|
||||
)
|
||||
|
||||
|
||||
class BuildDecisionBookProjectItems(CrudCollection):
|
||||
"""
|
||||
Builds class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "build_decision_book_project_items"
|
||||
__exclude__fields__ = []
|
||||
|
||||
item_header: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Item Header"
|
||||
)
|
||||
item_comment: Mapped[str] = mapped_column(
|
||||
Text, nullable=False, comment="Item Comment"
|
||||
)
|
||||
attachment_pdf_path: Mapped[str] = mapped_column(
|
||||
String, server_default="", nullable=True, comment="Attachment PDF Path"
|
||||
)
|
||||
item_estimated_cost: Mapped[float] = mapped_column(
|
||||
Numeric(16, 2), server_default="0", comment="Estimated Cost"
|
||||
)
|
||||
item_short_comment: Mapped[str] = mapped_column(
|
||||
String(24),
|
||||
nullable=True,
|
||||
comment="This field is reserved for use in grouping data or in the pivot heading.",
|
||||
)
|
||||
|
||||
build_decision_book_project_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_decision_book_projects.id"), nullable=False
|
||||
)
|
||||
build_decision_book_project_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Decision Book Project UUID"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
{"comment": "Project Items related to decision taken at building meetings"},
|
||||
)
|
||||
|
|
@ -0,0 +1,235 @@
|
|||
from typing import Any
|
||||
from schemas.base_imports import (
|
||||
CrudCollection,
|
||||
String,
|
||||
Integer,
|
||||
Boolean,
|
||||
ForeignKey,
|
||||
Index,
|
||||
TIMESTAMP,
|
||||
Numeric,
|
||||
SmallInteger,
|
||||
mapped_column,
|
||||
Mapped,
|
||||
)
|
||||
|
||||
class RelationshipDutyCompany(CrudCollection):
|
||||
"""
|
||||
CompanyRelationship class based on declarative_base and CrudCollection via session
|
||||
Company -> Sub Company -> Sub-Sub Company
|
||||
|
||||
if owner_id == parent_id: can manipulate data of any record
|
||||
else: Read-Only
|
||||
duty_id = if relationship_type == base An organization / not operational / no responsible person
|
||||
|
||||
relationship = company_id filter -> Action filter(company_id) relationship_type = Organization
|
||||
relationship = company_id filter -> Action filter(company_id) relationship_type = Commercial
|
||||
"""
|
||||
|
||||
__tablename__ = "relationship_duty_company"
|
||||
__exclude__fields__ = []
|
||||
|
||||
owner_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("companies.id"), nullable=False
|
||||
) # 1
|
||||
duties_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("duties.id"), nullable=False
|
||||
) # duty -> (n)employee Evyos LTD
|
||||
|
||||
member_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("companies.id"), nullable=False
|
||||
) # 2, 3, 4
|
||||
parent_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("companies.id"), nullable=True
|
||||
) # None
|
||||
|
||||
relationship_type: Mapped[str] = mapped_column(
|
||||
String, nullable=True, server_default="Commercial"
|
||||
) # Commercial, Organization # Bulk
|
||||
child_count: Mapped[int] = mapped_column(Integer) # 0
|
||||
show_only: Mapped[bool] = mapped_column(Boolean, server_default="0")
|
||||
|
||||
# related_company: Mapped[List["Companies"]] = relationship(
|
||||
# "Companies",
|
||||
# back_populates="related_companies",
|
||||
# foreign_keys=[related_company_id],
|
||||
# )
|
||||
|
||||
@classmethod
|
||||
def match_company_to_company_commercial(cls, data: Any, token):
|
||||
from Schemas import Duties
|
||||
|
||||
with cls.new_session() as db_session:
|
||||
token_duties_id, token_company_id = token.get("duty_id"), token.get(
|
||||
"company_id"
|
||||
)
|
||||
list_match_company_id = []
|
||||
send_duties = Duties.filter_one(
|
||||
Duties.uu_id == data.duty_uu_id, db=db_session
|
||||
)
|
||||
send_user_duties = Duties.filter_one(
|
||||
Duties.duties_id == send_duties.id,
|
||||
Duties.company_id == token_duties_id,
|
||||
db=db_session,
|
||||
)
|
||||
if not send_user_duties:
|
||||
raise Exception(
|
||||
"Send Duty is not found in company. Please check duty uuid and try again."
|
||||
)
|
||||
|
||||
for company_uu_id in list(data.match_company_uu_id):
|
||||
company = Companies.filter_one(
|
||||
Companies.uu_id == company_uu_id, db=db_session
|
||||
)
|
||||
bulk_company = RelationshipDutyCompany.filter_one(
|
||||
RelationshipDutyCompany.owner_id == token_company_id,
|
||||
RelationshipDutyCompany.relationship_type == "Bulk",
|
||||
RelationshipDutyCompany.member_id == company.id,
|
||||
db=db_session,
|
||||
)
|
||||
if not bulk_company:
|
||||
raise Exception(
|
||||
f"Bulk Company is not found in company. "
|
||||
f"Please check company uuid {bulk_company.uu_id} and try again."
|
||||
)
|
||||
list_match_company_id.append(bulk_company)
|
||||
|
||||
for match_company_id in list_match_company_id:
|
||||
RelationshipDutyCompany.find_or_create(
|
||||
owner_id=token_company_id,
|
||||
duties_id=send_user_duties.id,
|
||||
member_id=match_company_id.id,
|
||||
parent_id=match_company_id.parent_id,
|
||||
relationship_type="Commercial",
|
||||
show_only=False,
|
||||
db=db_session,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def match_company_to_company_organization(cls, data: Any, token):
|
||||
from Schemas import Duties
|
||||
|
||||
with cls.new_session() as db_session:
|
||||
token_duties_id, token_company_id = token.get("duty_id"), token.get(
|
||||
"company_id"
|
||||
)
|
||||
list_match_company_id = []
|
||||
send_duties = Duties.filter_one(
|
||||
Duties.uu_id == data.duty_uu_id, db=db_session
|
||||
)
|
||||
send_user_duties = Duties.filter_one(
|
||||
Duties.duties_id == send_duties.id,
|
||||
Duties.company_id == token_duties_id,
|
||||
db=db_session,
|
||||
)
|
||||
if not send_user_duties:
|
||||
raise Exception(
|
||||
"Send Duty is not found in company. Please check duty uuid and try again."
|
||||
)
|
||||
|
||||
for company_uu_id in list(data.match_company_uu_id):
|
||||
company = Companies.filter_one(
|
||||
Companies.uu_id == company_uu_id, db=db_session
|
||||
)
|
||||
bulk_company = RelationshipDutyCompany.filter_one(
|
||||
RelationshipDutyCompany.owner_id == token_company_id,
|
||||
RelationshipDutyCompany.relationship_type == "Bulk",
|
||||
RelationshipDutyCompany.member_id == company.id,
|
||||
db=db_session,
|
||||
)
|
||||
if not bulk_company:
|
||||
raise Exception(
|
||||
f"Bulk Company is not found in company. "
|
||||
f"Please check company uuid {bulk_company.uu_id} and try again."
|
||||
)
|
||||
list_match_company_id.append(bulk_company)
|
||||
|
||||
for match_company_id in list_match_company_id:
|
||||
Duties.init_a_company_default_duties(
|
||||
company_id=match_company_id.id,
|
||||
company_uu_id=str(match_company_id.uu_id),
|
||||
db=db_session,
|
||||
)
|
||||
RelationshipDutyCompany.find_or_create(
|
||||
owner_id=token_company_id,
|
||||
duties_id=send_user_duties.id,
|
||||
member_id=match_company_id.id,
|
||||
parent_id=match_company_id.parent_id,
|
||||
relationship_type="Organization",
|
||||
show_only=False,
|
||||
db=db_session,
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"_company_relationship_ndx_01",
|
||||
duties_id,
|
||||
owner_id,
|
||||
member_id,
|
||||
relationship_type,
|
||||
unique=True,
|
||||
),
|
||||
{"comment": "Company Relationship Information"},
|
||||
)
|
||||
|
||||
|
||||
class Companies(CrudCollection):
|
||||
"""
|
||||
Company class based on declarative_base and CrudCollection via session
|
||||
formal_name = Government register name by offical
|
||||
public_name = Public registered name by User
|
||||
nick_name = Search by nickname, commercial_type = Tüzel veya birey
|
||||
"""
|
||||
|
||||
__tablename__ = "companies"
|
||||
|
||||
__exclude__fields__ = ["is_blacklist", "is_commercial"]
|
||||
__access_by__ = []
|
||||
__many__table__ = RelationshipDutyCompany
|
||||
|
||||
formal_name: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Formal Name"
|
||||
)
|
||||
company_type: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Company Type"
|
||||
)
|
||||
commercial_type: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Commercial Type"
|
||||
)
|
||||
tax_no: Mapped[str] = mapped_column(
|
||||
String, index=True, unique=True, nullable=False, comment="Tax No"
|
||||
)
|
||||
|
||||
public_name: Mapped[str] = mapped_column(String, comment="Public Name of a company")
|
||||
company_tag: Mapped[str] = mapped_column(String, comment="Company Tag")
|
||||
default_lang_type: Mapped[str] = mapped_column(String, server_default="TR")
|
||||
default_money_type: Mapped[str] = mapped_column(String, server_default="TL")
|
||||
is_commercial: Mapped[bool] = mapped_column(Boolean, server_default="False")
|
||||
is_blacklist: Mapped[bool] = mapped_column(Boolean, server_default="False")
|
||||
parent_id = mapped_column(Integer, nullable=True)
|
||||
workplace_no: Mapped[str] = mapped_column(String, nullable=True)
|
||||
|
||||
official_address_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("addresses.id"), nullable=True
|
||||
)
|
||||
official_address_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Official Address UUID"
|
||||
)
|
||||
top_responsible_company_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("companies.id"), nullable=True
|
||||
)
|
||||
top_responsible_company_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Top Responsible Company UUID"
|
||||
)
|
||||
|
||||
# buildings: Mapped[List["Build"]] = relationship(
|
||||
# "Build",
|
||||
# back_populates="companies",
|
||||
# foreign_keys="Build.company_id",
|
||||
# )
|
||||
|
||||
__table_args__ = (
|
||||
Index("_company_ndx_01", tax_no, unique=True),
|
||||
Index("_company_ndx_02", formal_name, public_name),
|
||||
{"comment": "Company Information"},
|
||||
)
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
from schemas.base_imports import (
|
||||
CrudCollection,
|
||||
String,
|
||||
Integer,
|
||||
Boolean,
|
||||
ForeignKey,
|
||||
Index,
|
||||
TIMESTAMP,
|
||||
Numeric,
|
||||
SmallInteger,
|
||||
mapped_column,
|
||||
Mapped,
|
||||
)
|
||||
|
||||
|
||||
class Departments(CrudCollection):
|
||||
|
||||
__tablename__ = "departments"
|
||||
__exclude__fields__ = []
|
||||
|
||||
parent_department_id = mapped_column(Integer, server_default="0")
|
||||
department_code = mapped_column(
|
||||
String(16), nullable=False, index=True, comment="Department Code"
|
||||
)
|
||||
department_name: Mapped[str] = mapped_column(
|
||||
String(128), nullable=False, comment="Department Name"
|
||||
)
|
||||
department_description: Mapped[str] = mapped_column(String, server_default="")
|
||||
|
||||
company_id: Mapped[int] = mapped_column(ForeignKey("companies.id"), nullable=False)
|
||||
company_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Company UUID"
|
||||
)
|
||||
|
||||
__table_args__ = {"comment": "Departments Information"}
|
||||
|
||||
|
||||
class Duty(CrudCollection):
|
||||
|
||||
__tablename__ = "duty"
|
||||
__exclude__fields__ = []
|
||||
|
||||
duty_name: Mapped[str] = mapped_column(
|
||||
String, unique=True, nullable=False, comment="Duty Name"
|
||||
)
|
||||
duty_code: Mapped[str] = mapped_column(String, nullable=False, comment="Duty Code")
|
||||
duty_description: Mapped[str] = mapped_column(String, comment="Duty Description")
|
||||
|
||||
__table_args__ = ({"comment": "Duty Information"},)
|
||||
|
||||
|
||||
class Duties(CrudCollection):
|
||||
|
||||
__tablename__ = "duties"
|
||||
__exclude__fields__ = []
|
||||
|
||||
users_default_duty = mapped_column(
|
||||
ForeignKey("duty.id"), nullable=True, comment="Default Duty for Users"
|
||||
)
|
||||
company_id: Mapped[int] = mapped_column(Integer)
|
||||
company_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Company UUID"
|
||||
)
|
||||
duties_id: Mapped[int] = mapped_column(ForeignKey("duty.id"), nullable=False)
|
||||
duties_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Duty UUID"
|
||||
)
|
||||
department_id = mapped_column(
|
||||
ForeignKey("departments.id"), nullable=False, comment="Department ID"
|
||||
)
|
||||
department_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Department UUID"
|
||||
)
|
||||
# priority_id: Mapped[int] = mapped_column(ForeignKey("priority.id"), nullable=True)
|
||||
management_duty = mapped_column(
|
||||
Boolean, server_default="0"
|
||||
) # is this a prime Company Duty ???
|
||||
|
||||
__table_args__ = (
|
||||
Index("duty_ndx_00", company_id, duties_id, department_id, unique=True),
|
||||
{"comment": "Duty & Company & Department Information"},
|
||||
)
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
from schemas.base_imports import (
|
||||
CrudCollection,
|
||||
String,
|
||||
Integer,
|
||||
Boolean,
|
||||
ForeignKey,
|
||||
Index,
|
||||
TIMESTAMP,
|
||||
Numeric,
|
||||
SmallInteger,
|
||||
mapped_column,
|
||||
Mapped,
|
||||
)
|
||||
|
||||
|
||||
class Staff(CrudCollection):
|
||||
|
||||
__tablename__ = "staff"
|
||||
__exclude__fields__ = []
|
||||
|
||||
staff_description: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="Staff Description"
|
||||
)
|
||||
staff_name: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Staff Name"
|
||||
)
|
||||
staff_code: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Staff Code"
|
||||
)
|
||||
|
||||
duties_id: Mapped[int] = mapped_column(ForeignKey("duties.id"), nullable=False)
|
||||
duties_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Duty UUID"
|
||||
)
|
||||
|
||||
__table_args__ = ({"comment": "Staff Information"},)
|
||||
|
||||
|
||||
class Employees(CrudCollection):
|
||||
|
||||
__tablename__ = "employees"
|
||||
__exclude__fields__ = []
|
||||
|
||||
staff_id: Mapped[int] = mapped_column(ForeignKey("staff.id"))
|
||||
staff_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Staff UUID"
|
||||
)
|
||||
people_id: Mapped[int] = mapped_column(ForeignKey("people.id"), nullable=True)
|
||||
people_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="People UUID"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("employees_ndx_00", people_id, staff_id, unique=True),
|
||||
{"comment": "Employee Person Information"},
|
||||
)
|
||||
|
||||
|
||||
class EmployeeHistory(CrudCollection):
|
||||
|
||||
__tablename__ = "employee_history"
|
||||
__exclude__fields__ = []
|
||||
|
||||
staff_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("staff.id"), nullable=False, comment="Staff ID"
|
||||
)
|
||||
staff_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Staff UUID"
|
||||
)
|
||||
people_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("people.id"), nullable=False, comment="People ID"
|
||||
)
|
||||
people_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="People UUID"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("_employee_history_ndx_00", people_id, staff_id),
|
||||
{"comment": "Employee History Information"},
|
||||
)
|
||||
|
||||
|
||||
class EmployeesSalaries(CrudCollection):
|
||||
|
||||
__tablename__ = "employee_salaries"
|
||||
__exclude__fields__ = []
|
||||
|
||||
gross_salary: Mapped[float] = mapped_column(
|
||||
Numeric(20, 6), nullable=False, comment="Gross Salary"
|
||||
)
|
||||
net_salary: Mapped[float] = mapped_column(
|
||||
Numeric(20, 6), nullable=False, comment="Net Salary"
|
||||
)
|
||||
people_id: Mapped[int] = mapped_column(ForeignKey("people.id"), nullable=False)
|
||||
people_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="People UUID"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("_employee_salaries_ndx_00", people_id, "expiry_starts"),
|
||||
{"comment": "Employee Salaries Information"},
|
||||
)
|
||||
|
|
@ -0,0 +1,605 @@
|
|||
from schemas.base_imports import (
|
||||
CrudCollection,
|
||||
String,
|
||||
Integer,
|
||||
Boolean,
|
||||
ForeignKey,
|
||||
Index,
|
||||
TIMESTAMP,
|
||||
Numeric,
|
||||
SmallInteger,
|
||||
mapped_column,
|
||||
Mapped,
|
||||
)
|
||||
|
||||
|
||||
class Applications(CrudCollection):
|
||||
"""
|
||||
Applications class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "applications"
|
||||
__exclude__fields__ = []
|
||||
|
||||
name: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Application Name"
|
||||
)
|
||||
site_url: Mapped[str] = mapped_column(String, nullable=False, comment="Site URL")
|
||||
application_code: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Application Code"
|
||||
)
|
||||
application_type: Mapped[str] = mapped_column(String, comment="Application Type")
|
||||
application_for: Mapped[str] = mapped_column(
|
||||
String, server_default="EMP", comment="Application For"
|
||||
)
|
||||
description: Mapped[str] = mapped_column(String, comment="Application Description")
|
||||
|
||||
|
||||
class Events(CrudCollection):
|
||||
"""
|
||||
Events class based on declarative_base and BaseMixin via session
|
||||
If Events2Occupants and Events2Employees are not found for user request, response 401 Unauthorized
|
||||
"""
|
||||
|
||||
__tablename__ = "events"
|
||||
__exclude__fields__ = []
|
||||
|
||||
function_code: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="function code", unique=True
|
||||
)
|
||||
function_class: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="class name"
|
||||
)
|
||||
|
||||
# name: Mapped[str] = mapped_column(String, nullable=True) # form or page title
|
||||
description: Mapped[str] = mapped_column(String, server_default="")
|
||||
property_description: Mapped[str] = mapped_column(String, server_default="")
|
||||
|
||||
marketing_layer = mapped_column(SmallInteger, server_default="3")
|
||||
cost: Mapped[float] = mapped_column(Numeric(20, 2), server_default="0.00")
|
||||
unit_price: Mapped[float] = mapped_column(Numeric(20, 2), server_default="0.00")
|
||||
|
||||
endpoint_code: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Event Type"
|
||||
)
|
||||
endpoint_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("endpoint_restriction.id"), nullable=True
|
||||
)
|
||||
endpoint_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Endpoint UUID"
|
||||
)
|
||||
|
||||
__table_args__ = ({"comment": "Events Information"},)
|
||||
|
||||
|
||||
class Modules(CrudCollection):
|
||||
"""
|
||||
Modules class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "modules"
|
||||
__exclude__fields__ = []
|
||||
|
||||
module_name: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Module Name"
|
||||
)
|
||||
module_description: Mapped[str] = mapped_column(String, server_default="")
|
||||
module_code: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Module Code"
|
||||
)
|
||||
module_layer = mapped_column(Integer, nullable=False, comment="Module Layer")
|
||||
is_default_module = mapped_column(Boolean, server_default="0")
|
||||
|
||||
def retrieve_services(self):
|
||||
services = Services.filter_all(Services.module_id == self.id).data
|
||||
if not services:
|
||||
self.raise_http_exception(
|
||||
status_code="HTTP_404_NOT_FOUND",
|
||||
error_case="RECORD_NOT_FOUND",
|
||||
message=f"No services found for this module : {str(self.uu_id)}",
|
||||
data={
|
||||
"module_uu_id": str(self.uu_id),
|
||||
},
|
||||
)
|
||||
return services
|
||||
|
||||
__table_args__ = ({"comment": "Modules Information"},)
|
||||
|
||||
|
||||
class ModulePrice(CrudCollection):
|
||||
"""
|
||||
ModulePrice class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "module_price"
|
||||
__exclude__fields__ = []
|
||||
|
||||
campaign_code: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Campaign Code"
|
||||
)
|
||||
module_id: Mapped[int] = mapped_column(ForeignKey("modules.id"), nullable=False)
|
||||
module_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Module UUID"
|
||||
)
|
||||
service_id: Mapped[int] = mapped_column(ForeignKey("services.id"), nullable=False)
|
||||
service_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Service UUID"
|
||||
)
|
||||
event_id: Mapped[int] = mapped_column(ForeignKey("events.id"), nullable=False)
|
||||
event_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Event UUID"
|
||||
)
|
||||
is_counted_percentage: Mapped[float] = mapped_column(
|
||||
Numeric(6, 2), server_default="0.00"
|
||||
) # %22
|
||||
discounted_price: Mapped[float] = mapped_column(
|
||||
Numeric(20, 2), server_default="0.00"
|
||||
) # Normal: 78.00 TL
|
||||
calculated_price: Mapped[float] = mapped_column(
|
||||
Numeric(20, 2), server_default="0.00"
|
||||
) # sana düz 75.00 TL yapar
|
||||
|
||||
__table_args__ = ({"comment": "ModulePrice Information"},)
|
||||
|
||||
|
||||
class Services(CrudCollection):
|
||||
"""
|
||||
Services class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "services"
|
||||
__exclude__fields__ = []
|
||||
|
||||
module_id: Mapped[int] = mapped_column(ForeignKey("modules.id"), nullable=False)
|
||||
module_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Module UUID"
|
||||
)
|
||||
service_name: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Service Name"
|
||||
)
|
||||
service_description: Mapped[str] = mapped_column(String, server_default="")
|
||||
service_code: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Service Code"
|
||||
)
|
||||
related_responsibility: Mapped[str] = mapped_column(String, server_default="")
|
||||
|
||||
@classmethod
|
||||
def retrieve_service_via_occupant_code(cls, occupant_code):
|
||||
from Schemas import OccupantTypes
|
||||
|
||||
with cls.new_session() as db_session:
|
||||
occupant_type = OccupantTypes.filter_by_one(
|
||||
system=True, occupant_code=occupant_code, db=db_session
|
||||
).data
|
||||
if not occupant_type:
|
||||
cls.raise_http_exception(
|
||||
status_code="HTTP_404_NOT_FOUND",
|
||||
error_case="RECORD_NOT_FOUND",
|
||||
message=f"No occupant type found for this code : {occupant_code}",
|
||||
data={
|
||||
"occupant_code": occupant_code,
|
||||
},
|
||||
)
|
||||
return cls.filter_one(
|
||||
cls.related_responsibility == occupant_type.occupant_code, db=db_session
|
||||
).data
|
||||
|
||||
__table_args__ = ({"comment": "Services Information"},)
|
||||
|
||||
|
||||
class Service2Events(CrudCollection):
|
||||
"""
|
||||
Service2Actions class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "services2events"
|
||||
__exclude__fields__ = []
|
||||
|
||||
service_id: Mapped[int] = mapped_column(ForeignKey("services.id"), nullable=False)
|
||||
service_uu_id = mapped_column(String, nullable=False, comment="Service UUID")
|
||||
event_id: Mapped[int] = mapped_column(ForeignKey("events.id"), nullable=False)
|
||||
event_uu_id = mapped_column(String, nullable=False, comment="Event UUID")
|
||||
|
||||
__table_args__ = ({"comment": "Service2Events Information"},)
|
||||
|
||||
|
||||
class Service2Application(CrudCollection):
|
||||
"""
|
||||
Service2Application class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "services2applications"
|
||||
__exclude__fields__ = []
|
||||
|
||||
application_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("applications.id"), nullable=False
|
||||
)
|
||||
application_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Application UUID"
|
||||
)
|
||||
service_id: Mapped[int] = mapped_column(ForeignKey("services.id"), nullable=False)
|
||||
service_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Service UUID"
|
||||
)
|
||||
application_code: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Application Code"
|
||||
)
|
||||
site_url: Mapped[str] = mapped_column(String, nullable=False, comment="Site URL")
|
||||
|
||||
__table_args__ = {"comment": "Service2Applications Information"}
|
||||
|
||||
|
||||
class Event2OccupantExtra(CrudCollection):
|
||||
|
||||
__tablename__ = "event2occupant_extra"
|
||||
__exclude__fields__ = []
|
||||
|
||||
build_living_space_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_living_space.id"), nullable=False
|
||||
)
|
||||
build_living_space_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Build Living Space UUID"
|
||||
)
|
||||
event_id: Mapped[int] = mapped_column(ForeignKey("events.id"), nullable=False)
|
||||
event_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Event UUID"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"event2occupant_extra_bind_event_to_occupant",
|
||||
build_living_space_id,
|
||||
event_id,
|
||||
unique=True,
|
||||
),
|
||||
{"comment": "Occupant2Event Information"},
|
||||
)
|
||||
|
||||
|
||||
class Event2EmployeeExtra(CrudCollection):
|
||||
"""
|
||||
Employee2Event class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "event2employee_extra"
|
||||
__exclude__fields__ = []
|
||||
|
||||
employee_id: Mapped[int] = mapped_column(ForeignKey("employees.id"), nullable=False)
|
||||
employee_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Employee UUID"
|
||||
)
|
||||
|
||||
event_id: Mapped[int] = mapped_column(ForeignKey("events.id"), nullable=False)
|
||||
event_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Event UUID"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"event2employee_extra_employee_to_event",
|
||||
employee_id,
|
||||
event_id,
|
||||
unique=True,
|
||||
),
|
||||
{"comment": "Employee to Event Information"},
|
||||
)
|
||||
|
||||
|
||||
class Event2Employee(CrudCollection):
|
||||
"""
|
||||
Employee2Event class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "event2employee"
|
||||
__exclude__fields__ = []
|
||||
|
||||
employee_id: Mapped[int] = mapped_column(ForeignKey("employees.id"), nullable=False)
|
||||
employee_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Employee UUID"
|
||||
)
|
||||
event_service_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("services.id"), nullable=False
|
||||
)
|
||||
event_service_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Event Cluster UUID"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"event2employee_employee_to_event",
|
||||
employee_id,
|
||||
event_service_id,
|
||||
unique=True,
|
||||
),
|
||||
{"comment": "Employee to Event Information"},
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_event_codes(cls, employee_id: int, db) -> dict[str:str]:
|
||||
employee_events = cls.filter_all(
|
||||
cls.employee_id == employee_id,
|
||||
db=db,
|
||||
).data
|
||||
service_ids = list(set([event.event_service_id for event in employee_events]))
|
||||
active_event_ids = Service2Events.filter_all(
|
||||
Service2Events.service_id.in_(service_ids),
|
||||
db=db,
|
||||
).data
|
||||
active_events = Events.filter_all(
|
||||
Events.id.in_([event.event_id for event in active_event_ids]),
|
||||
db=db,
|
||||
).data
|
||||
if extra_events := Event2EmployeeExtra.filter_all(
|
||||
Event2EmployeeExtra.employee_id == employee_id,
|
||||
db=db,
|
||||
).data:
|
||||
events_extra = Events.filter_all(
|
||||
Events.id.in_([event.event_id for event in extra_events]),
|
||||
db=db,
|
||||
).data
|
||||
active_events.extend(events_extra)
|
||||
events_dict = {}
|
||||
for event in active_events:
|
||||
if not event.endpoint_code in events_dict:
|
||||
events_dict[str(event.endpoint_code)] = str(event.function_code)
|
||||
else:
|
||||
ValueError("Duplicate event code found for single endpoint")
|
||||
return events_dict
|
||||
|
||||
|
||||
class Event2Occupant(CrudCollection):
|
||||
"""
|
||||
Occupant2Event class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "event2occupant"
|
||||
__exclude__fields__ = []
|
||||
|
||||
build_living_space_id: Mapped[str] = mapped_column(
|
||||
ForeignKey("build_living_space.id"), nullable=False
|
||||
)
|
||||
build_living_space_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Build Living Space UUID"
|
||||
)
|
||||
event_service_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("services.id"), nullable=False
|
||||
)
|
||||
event_service_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Event Cluster UUID"
|
||||
)
|
||||
# event_id: Mapped[int] = mapped_column(ForeignKey("events.id"), nullable=False)
|
||||
# event_uu_id = mapped_column(String, nullable=False, comment="Event UUID")
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"event2occupant_bind_event_to_occupant",
|
||||
build_living_space_id,
|
||||
event_service_id,
|
||||
unique=True,
|
||||
),
|
||||
{"comment": "Occupant2Event Information"},
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_event_codes(cls, build_living_space_id: int, db) -> dict[str:str]:
|
||||
occupant_events = cls.filter_all(
|
||||
cls.build_living_space_id == build_living_space_id,
|
||||
db=db,
|
||||
).data
|
||||
service_ids = list(set([event.event_service_id for event in occupant_events]))
|
||||
active_event_ids = Service2Events.filter_all_system(
|
||||
Service2Events.service_id.in_(service_ids),
|
||||
db=db,
|
||||
).data
|
||||
active_events = Events.filter_all(
|
||||
Events.id.in_([event.event_id for event in active_event_ids]),
|
||||
db=db,
|
||||
).data
|
||||
if extra_events := Event2OccupantExtra.filter_all(
|
||||
Event2OccupantExtra.build_living_space_id == build_living_space_id,
|
||||
db=db,
|
||||
).data:
|
||||
events_extra = Events.filter_all(
|
||||
Events.id.in_([event.event_id for event in extra_events]),
|
||||
db=db,
|
||||
).data
|
||||
active_events.extend(events_extra)
|
||||
events_dict = {}
|
||||
for event in active_events:
|
||||
if not event.endpoint_code in events_dict:
|
||||
events_dict[str(event.endpoint_code)] = str(event.function_code)
|
||||
else:
|
||||
ValueError("Duplicate event code found for single endpoint")
|
||||
return events_dict
|
||||
|
||||
|
||||
class Application2Employee(CrudCollection):
|
||||
"""
|
||||
Application2Employee class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "application2employee"
|
||||
__exclude__fields__ = []
|
||||
|
||||
employee_id: Mapped[int] = mapped_column(ForeignKey("employees.id"), nullable=False)
|
||||
employee_uu_id = mapped_column(String, nullable=False, comment="Employee UUID")
|
||||
service_id: Mapped[int] = mapped_column(ForeignKey("services.id"), nullable=False)
|
||||
service_uu_id = mapped_column(String, nullable=False, comment="Service UUID")
|
||||
|
||||
@classmethod
|
||||
def get_application_codes(cls, employee_id: int, db) -> list[int]:
|
||||
employee_services = cls.filter_all(
|
||||
cls.employee_id == employee_id,
|
||||
db=db,
|
||||
).data
|
||||
service_ids = [service.service_id for service in employee_services]
|
||||
active_applications = Service2Application.filter_all(
|
||||
Service2Application.service_id.in_(service_ids),
|
||||
db=db,
|
||||
).data
|
||||
applications = Applications.filter_all(
|
||||
Applications.id.in_(
|
||||
[application.application_id for application in active_applications]
|
||||
),
|
||||
db=db,
|
||||
).data
|
||||
if extra_applications := Application2EmployeeExtra.filter_all(
|
||||
Application2EmployeeExtra.employee_id == employee_id,
|
||||
db=db,
|
||||
).data:
|
||||
applications_extra = Applications.filter_all(
|
||||
Applications.id.in_(
|
||||
[application.application_id for application in extra_applications]
|
||||
),
|
||||
db=db,
|
||||
).data
|
||||
applications.extend(applications_extra)
|
||||
applications_dict = {}
|
||||
for application in applications:
|
||||
if not application.site_url in applications_dict:
|
||||
applications_dict[str(application.site_url)] = str(application.application_code)
|
||||
else:
|
||||
ValueError("Duplicate application code found for single endpoint")
|
||||
return applications_dict
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"application2employee_employee_to_service",
|
||||
employee_uu_id,
|
||||
service_uu_id,
|
||||
unique=True,
|
||||
),
|
||||
{"comment": "Application to Employee Information"},
|
||||
)
|
||||
|
||||
|
||||
class Application2Occupant(CrudCollection):
|
||||
"""
|
||||
Application2Occupant class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "application2occupant"
|
||||
__exclude__fields__ = []
|
||||
|
||||
build_living_space_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_living_space.id"), nullable=False
|
||||
)
|
||||
build_living_space_uu_id = mapped_column(
|
||||
String, nullable=False, comment="Build Living Space UUID"
|
||||
)
|
||||
service_id: Mapped[int] = mapped_column(ForeignKey("services.id"), nullable=False)
|
||||
service_uu_id = mapped_column(String, nullable=False, comment="Service UUID")
|
||||
|
||||
@classmethod
|
||||
def get_application_codes(cls, build_living_space_id: int, db) -> list[int]:
|
||||
occupant_services = cls.filter_all(
|
||||
cls.build_living_space_id == build_living_space_id,
|
||||
db=db,
|
||||
).data
|
||||
service_ids = [service.service_id for service in occupant_services]
|
||||
active_applications = Service2Application.filter_all(
|
||||
Service2Application.service_id.in_(service_ids),
|
||||
db=db,
|
||||
).data
|
||||
applications = Applications.filter_all(
|
||||
Applications.id.in_(
|
||||
[application.application_id for application in active_applications]
|
||||
),
|
||||
db=db,
|
||||
).data
|
||||
if extra_applications := Application2OccupantExtra.filter_all(
|
||||
Application2OccupantExtra.build_living_space_id == build_living_space_id,
|
||||
db=db,
|
||||
).data:
|
||||
applications_extra = Applications.filter_all(
|
||||
Applications.id.in_(
|
||||
[application.application_id for application in extra_applications]
|
||||
),
|
||||
db=db,
|
||||
).data
|
||||
applications.extend(applications_extra)
|
||||
applications_dict = {}
|
||||
for application in applications:
|
||||
if not application.site_url in applications_dict:
|
||||
applications_dict[str(application.site_url)] = str(application.application_code)
|
||||
else:
|
||||
ValueError("Duplicate application code found for single endpoint")
|
||||
return applications_dict
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"application2occupant_occupant_to_service",
|
||||
build_living_space_uu_id,
|
||||
service_uu_id,
|
||||
unique=True,
|
||||
),
|
||||
{"comment": "Application to Occupant Information"},
|
||||
)
|
||||
|
||||
|
||||
class Application2EmployeeExtra(CrudCollection):
|
||||
"""
|
||||
Application2EmployeeExtra class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "application2employee_extra"
|
||||
__exclude__fields__ = []
|
||||
|
||||
employee_id: Mapped[int] = mapped_column(ForeignKey("employees.id"), nullable=False)
|
||||
employee_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Employee UUID"
|
||||
)
|
||||
application_id: Mapped[int] = mapped_column(ForeignKey("applications.id"))
|
||||
application_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Application UUID"
|
||||
)
|
||||
site_url: Mapped[str] = mapped_column(String, nullable=False, comment="Site URL")
|
||||
application_code: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Application Code"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"application_to_employee",
|
||||
employee_id,
|
||||
site_url,
|
||||
application_id,
|
||||
unique=True,
|
||||
),
|
||||
{"comment": "Application2Employee Information"},
|
||||
)
|
||||
|
||||
|
||||
class Application2OccupantExtra(CrudCollection):
|
||||
"""
|
||||
Application2OccupantExtra class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "application2occupant_extra"
|
||||
__exclude__fields__ = []
|
||||
|
||||
build_living_space_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("build_living_space.id"), nullable=False
|
||||
)
|
||||
build_living_space_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Build Living Space UUID"
|
||||
)
|
||||
application_id: Mapped[int] = mapped_column(ForeignKey("applications.id"))
|
||||
application_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Application UUID"
|
||||
)
|
||||
site_url: Mapped[str] = mapped_column(String, nullable=False, comment="Site URL")
|
||||
application_code: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Application Code"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"application_to_occupant",
|
||||
build_living_space_id,
|
||||
site_url,
|
||||
application_id,
|
||||
unique=True,
|
||||
),
|
||||
{"comment": "Application2Occupant Information"},
|
||||
)
|
||||
|
|
@ -0,0 +1,488 @@
|
|||
import arrow
|
||||
from schemas.base_imports import (
|
||||
CrudCollection,
|
||||
String,
|
||||
Integer,
|
||||
Boolean,
|
||||
ForeignKey,
|
||||
Index,
|
||||
TIMESTAMP,
|
||||
Text,
|
||||
func,
|
||||
BigInteger,
|
||||
Numeric,
|
||||
or_,
|
||||
and_,
|
||||
mapped_column,
|
||||
Mapped,
|
||||
relationship,
|
||||
)
|
||||
|
||||
|
||||
class UsersTokens(CrudCollection):
|
||||
|
||||
__tablename__ = "users_tokens"
|
||||
__exclude__fields__ = []
|
||||
|
||||
user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False)
|
||||
|
||||
token_type: Mapped[str] = mapped_column(String(16), server_default="RememberMe")
|
||||
token: Mapped[str] = mapped_column(String, server_default="")
|
||||
domain: Mapped[str] = mapped_column(String, server_default="")
|
||||
expires_at: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True),
|
||||
default=str(arrow.now().shift(days=3)),
|
||||
)
|
||||
|
||||
# users = relationship("Users", back_populates="tokens", foreign_keys=[user_id])
|
||||
|
||||
|
||||
class Credentials(CrudCollection):
|
||||
"""
|
||||
Credentials class to store user credentials
|
||||
"""
|
||||
|
||||
__tablename__ = "credentials"
|
||||
__exclude__fields__ = []
|
||||
|
||||
credential_token: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="Credential token for authentication"
|
||||
)
|
||||
user_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("users.id"), nullable=False, comment="Foreign key to users table"
|
||||
)
|
||||
user_uu_id: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="User UUID", index=True
|
||||
)
|
||||
person_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("people.id"), nullable=False, comment="Foreign key to person table"
|
||||
)
|
||||
person_uu_id: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="Person UUID", index=True
|
||||
)
|
||||
name: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="Name of the user", index=True
|
||||
)
|
||||
surname: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="Surname of the user", index=True
|
||||
)
|
||||
email: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="Email address of the user", index=True
|
||||
)
|
||||
phone: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="Phone number of the user", index=True
|
||||
)
|
||||
is_verified: Mapped[bool] = mapped_column(
|
||||
Boolean, server_default="0", comment="Flag to check if user is verified"
|
||||
)
|
||||
|
||||
def generate_token(self) -> str:
|
||||
"""
|
||||
Generate a unique token for the user
|
||||
"""
|
||||
name_token, rest_of_token = "", ""
|
||||
if self.name and self.surname:
|
||||
name_token = f"{self.name[0].upper()}{self.surname[0].upper()}"
|
||||
return ""
|
||||
|
||||
|
||||
class Users(CrudCollection):
|
||||
"""
|
||||
Application User frame to connect to API with assigned token-based HTTP connection
|
||||
"""
|
||||
|
||||
__tablename__ = "users"
|
||||
__exclude__fields__ = [
|
||||
"hash_password",
|
||||
"password_token",
|
||||
"expiry_begins",
|
||||
"related_company",
|
||||
]
|
||||
|
||||
user_tag: Mapped[str] = mapped_column(
|
||||
String(64), server_default="", comment="Unique tag for the user", index=True
|
||||
)
|
||||
email: Mapped[str] = mapped_column(
|
||||
String(128), server_default="", comment="Email address of the user", index=True
|
||||
)
|
||||
phone_number: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="Phone number of the user", index=True
|
||||
)
|
||||
via: Mapped[str] = mapped_column(
|
||||
String,
|
||||
server_default="111",
|
||||
comment="Email 1/ Phone 2/ User Tag 3 All 111 Only 100",
|
||||
)
|
||||
|
||||
avatar: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="Avatar URL for the user"
|
||||
)
|
||||
hash_password: Mapped[str] = mapped_column(
|
||||
String(256), server_default="", comment="Hashed password for security"
|
||||
)
|
||||
password_token: Mapped[str] = mapped_column(
|
||||
String(256), server_default="", comment="Token for password reset"
|
||||
)
|
||||
remember_me: Mapped[bool] = mapped_column(
|
||||
Boolean, server_default="0", comment="Flag to remember user login"
|
||||
)
|
||||
|
||||
password_expires_day: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
server_default=str(30),
|
||||
comment="Password expires in days",
|
||||
)
|
||||
password_expiry_begins: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True),
|
||||
server_default=func.now(),
|
||||
comment="Timestamp when password expiry begins",
|
||||
)
|
||||
related_company: Mapped[str] = mapped_column(String, comment="Related Company UUID")
|
||||
|
||||
person_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("people.id"), nullable=False, comment="Foreign key to person table"
|
||||
)
|
||||
person_uu_id: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="Person UUID", index=True
|
||||
)
|
||||
local_timezone = mapped_column(
|
||||
String, server_default="GMT+3", comment="Local timezone of user"
|
||||
)
|
||||
person = relationship("People", back_populates="user", foreign_keys=[person_id])
|
||||
default_language: Mapped[str] = mapped_column(
|
||||
String, server_default="tr", comment="Default language of user"
|
||||
)
|
||||
|
||||
|
||||
class RelationshipDutyPeople(CrudCollection):
|
||||
|
||||
__tablename__ = "relationship_duty_people"
|
||||
__exclude__fields__ = []
|
||||
|
||||
company_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("companies.id"), nullable=False
|
||||
) # 1, 2, 3
|
||||
duties_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("duties.id"), nullable=False
|
||||
) # duty -> (n)person Evyos LTD
|
||||
member_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("people.id"), nullable=False
|
||||
) # 2, 3, 4
|
||||
|
||||
relationship_type: Mapped[str] = mapped_column(
|
||||
String, nullable=True, server_default="Employee"
|
||||
) # Commercial
|
||||
show_only: Mapped[bool] = mapped_column(Boolean, server_default="0")
|
||||
|
||||
# related_company: Mapped[List["Company"]] = relationship(
|
||||
# "Company",
|
||||
# back_populates="related_companies",
|
||||
# foreign_keys=[related_company_id],
|
||||
# )
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"person_relationship_ndx_01",
|
||||
company_id,
|
||||
duties_id,
|
||||
member_id,
|
||||
relationship_type,
|
||||
unique=True,
|
||||
),
|
||||
{"comment": "Person Relationship Information"},
|
||||
)
|
||||
|
||||
|
||||
class People(CrudCollection):
|
||||
"""
|
||||
People that are related to users in the application
|
||||
"""
|
||||
|
||||
__tablename__ = "people"
|
||||
__exclude__fields__ = []
|
||||
__many__table__ = RelationshipDutyPeople
|
||||
__encrypt_list__ = [
|
||||
"father_name",
|
||||
"mother_name",
|
||||
"country_code",
|
||||
"national_identity_id",
|
||||
"birth_place",
|
||||
"birth_date",
|
||||
"tax_no",
|
||||
]
|
||||
|
||||
firstname: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="First name of the person"
|
||||
)
|
||||
surname: Mapped[str] = mapped_column(
|
||||
String(24), nullable=False, comment="Surname of the person"
|
||||
)
|
||||
middle_name: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="Middle name of the person"
|
||||
)
|
||||
sex_code: Mapped[str] = mapped_column(
|
||||
String(1), nullable=False, comment="Sex code of the person (e.g., M/F)"
|
||||
)
|
||||
person_ref: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="Reference ID for the person"
|
||||
)
|
||||
person_tag: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="Unique tag for the person"
|
||||
)
|
||||
|
||||
# ENCRYPT DATA
|
||||
father_name: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="Father's name of the person"
|
||||
)
|
||||
mother_name: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="Mother's name of the person"
|
||||
)
|
||||
country_code: Mapped[str] = mapped_column(
|
||||
String(4), server_default="TR", comment="Country code of the person"
|
||||
)
|
||||
national_identity_id: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="National identity ID of the person"
|
||||
)
|
||||
birth_place: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="Birth place of the person"
|
||||
)
|
||||
birth_date: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True),
|
||||
server_default="1900-01-01",
|
||||
comment="Birth date of the person",
|
||||
)
|
||||
tax_no: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="Tax number of the person"
|
||||
)
|
||||
# Receive at Create person
|
||||
# language = mapped_column(
|
||||
# String, comment="Language code of the person"
|
||||
# )
|
||||
# currency = mapped_column(
|
||||
# String, comment="Currency code of the person"
|
||||
# )
|
||||
|
||||
# ENCRYPT DATA
|
||||
user = relationship(
|
||||
"Users", back_populates="person", foreign_keys="Users.person_id"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"person_ndx_001",
|
||||
national_identity_id,
|
||||
unique=True,
|
||||
),
|
||||
{"comment": "Person Information"},
|
||||
)
|
||||
|
||||
@property
|
||||
def full_name(self):
|
||||
if self.middle_name:
|
||||
return f"{self.firstname} {self.middle_name} {self.surname}"
|
||||
return f"{self.firstname} {self.surname}"
|
||||
|
||||
|
||||
class OccupantTypes(CrudCollection):
|
||||
"""
|
||||
Occupant Types class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "occupant_types"
|
||||
__exclude__fields__ = []
|
||||
|
||||
occupant_type: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Occupant Type"
|
||||
)
|
||||
occupant_description: Mapped[str] = mapped_column(String, server_default="")
|
||||
occupant_code: Mapped[str] = mapped_column(String, server_default="")
|
||||
occupant_category: Mapped[str] = mapped_column(String, server_default="")
|
||||
occupant_category_type: Mapped[str] = mapped_column(String, server_default="")
|
||||
occupant_is_unique: Mapped[bool] = mapped_column(Boolean, server_default="0")
|
||||
|
||||
__table_args__ = ({"comment": "Occupant Types Information"},)
|
||||
|
||||
|
||||
class Contracts(CrudCollection):
|
||||
"""
|
||||
Contract class based on declarative_base and BaseMixin via session
|
||||
"""
|
||||
|
||||
__tablename__ = "contracts"
|
||||
__exclude__fields__ = []
|
||||
|
||||
contract_type: Mapped[str] = mapped_column(
|
||||
String(5),
|
||||
nullable=False,
|
||||
comment="The code for personnel is P and the code for companies is C.",
|
||||
)
|
||||
contract_title: Mapped[str] = mapped_column(String(255))
|
||||
contract_details: Mapped[str] = mapped_column(Text)
|
||||
contract_terms: Mapped[str] = mapped_column(Text)
|
||||
|
||||
contract_code: Mapped[str] = mapped_column(
|
||||
String(100),
|
||||
nullable=False,
|
||||
comment="contract_code is the unique code given by the system.",
|
||||
)
|
||||
contract_date: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True),
|
||||
server_default="2099-12-31 23:59:59",
|
||||
comment="contract date is the date the contract is made. "
|
||||
"expire start is the start date of the contract, expire en is the end date of the contract.",
|
||||
)
|
||||
|
||||
company_id: Mapped[int] = mapped_column(
|
||||
Integer, ForeignKey("companies.id"), nullable=True
|
||||
)
|
||||
company_uu_id: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="Company UUID"
|
||||
)
|
||||
|
||||
person_id: Mapped[int] = mapped_column(
|
||||
Integer, ForeignKey("people.id"), nullable=True
|
||||
)
|
||||
person_uu_id: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="Person UUID"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def retrieve_contact_no(cls):
|
||||
# todo When create record contract_code == below string
|
||||
related_date, counter = Contracts.client_arrow.now(), 1
|
||||
return (
|
||||
f"{related_date.date().year}{str(cls.contract_type)}{str(counter).zfill(6)}"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("_contract_ndx_01", contract_code, unique=True),
|
||||
{"comment": "Contract Information"},
|
||||
)
|
||||
|
||||
|
||||
# @property
|
||||
# def is_occupant(self):
|
||||
# return not str(self.email).split("@")[1] == Auth.ACCESS_EMAIL_EXT
|
||||
#
|
||||
# @property
|
||||
# def is_employee(self):
|
||||
# return str(self.email).split("@")[1] == Auth.ACCESS_EMAIL_EXT
|
||||
#
|
||||
# @property
|
||||
# def user_type(self):
|
||||
# return "Occupant" if self.is_occupant else "Employee"
|
||||
#
|
||||
# @classmethod
|
||||
# def credentials(cls):
|
||||
# db_session = cls.new_session()
|
||||
# person_object: People = People.filter_by_one(
|
||||
# db=db_session, system=True, id=cls.person_id
|
||||
# ).data
|
||||
# if person_object:
|
||||
# return {
|
||||
# "person_id": person_object.id,
|
||||
# "person_uu_id": str(person_object.uu_id),
|
||||
# }
|
||||
# return {
|
||||
# "person_id": None,
|
||||
# "person_uu_id": None,
|
||||
# }
|
||||
#
|
||||
# @property
|
||||
# def password_expiry_ends(self):
|
||||
# """Calculates the expiry end date based on expiry begins and expires day"""
|
||||
# return self.password_expiry_begins + timedelta(
|
||||
# days=int(
|
||||
# "".join(
|
||||
# [
|
||||
# _
|
||||
# for _ in str(self.password_expires_day).split(",")[0]
|
||||
# if _.isdigit()
|
||||
# ]
|
||||
# )
|
||||
# )
|
||||
# )
|
||||
#
|
||||
# @classmethod
|
||||
# def create_action(cls, create_user: InsertUsers, token_dict):
|
||||
# db_session = cls.new_session()
|
||||
# found_person = People.filter_one(
|
||||
# People.uu_id == create_user.people_uu_id,
|
||||
# db=db_session,
|
||||
# ).data
|
||||
#
|
||||
# if not found_person:
|
||||
# raise HTTPException(status_code=400, detail="Person not found.")
|
||||
# if (
|
||||
# not any(i in str(create_user.email) for i in ["@", "."])
|
||||
# and not len(str(create_user.phone_number)) >= 10
|
||||
# ):
|
||||
# raise HTTPException(
|
||||
# status_code=400,
|
||||
# detail="Please enter at least one valid email or phone number.",
|
||||
# )
|
||||
# if not create_user.avatar:
|
||||
# create_user.avatar = ApiStatic.PLACEHOLDER
|
||||
# create_dict = create_user.model_dump()
|
||||
# del create_dict["people_uu_id"]
|
||||
# create_dict["person_id"] = found_person.id
|
||||
# create_dict["person_uu_id"] = str(found_person.uu_id)
|
||||
# create_dict["related_company"] = token_dict.selected_company.company_uu_id
|
||||
# created_user = cls.find_or_create(**create_dict)
|
||||
# created_user.reset_password_token(found_user=created_user)
|
||||
# return created_user
|
||||
#
|
||||
# def get_employee_and_duty_details(self):
|
||||
# from ApiLayers.Schemas import Employees, Duties
|
||||
#
|
||||
# db_session = self.new_session()
|
||||
# found_person = People.filter_one(
|
||||
# People.id == self.person_id,
|
||||
# db=db_session,
|
||||
# )
|
||||
# found_employees = Employees.filter_by_active(
|
||||
# people_id=found_person.id, is_confirmed=True, db=db_session
|
||||
# )
|
||||
# found_duties = Duties.filter_all(
|
||||
# Duties.is_confirmed == True,
|
||||
# Duties.id.in_(
|
||||
# list(found_employee.duty_id for found_employee in found_employees.data)
|
||||
# ),
|
||||
# db=db_session,
|
||||
# )
|
||||
# if not found_employees.count:
|
||||
# raise HTTPException(
|
||||
# status_code=401,
|
||||
# detail={
|
||||
# "message": "Person has no confirmed duty. No employee match please register "
|
||||
# "your super admin",
|
||||
# "completed": False,
|
||||
# },
|
||||
# )
|
||||
# return {
|
||||
# "duty_list": [
|
||||
# {
|
||||
# "duty_id": duty.id,
|
||||
# "duty_uu_id": duty.uu_id.__str__(),
|
||||
# "duty_code": duty.duty_code,
|
||||
# "duty_name": duty.duty_name,
|
||||
# "duty_description": duty.duty_description,
|
||||
# }
|
||||
# for duty in found_duties.data
|
||||
# ],
|
||||
# }
|
||||
#
|
||||
# def get_main_domain_and_other_domains(self, get_main_domain: bool = True):
|
||||
# from ApiLayers.Schemas import MongoQueryIdentity
|
||||
#
|
||||
# query_engine = MongoQueryIdentity(company_uuid=self.related_company)
|
||||
# domain_via_user = query_engine.get_domain_via_user(user_uu_id=str(self.uu_id))
|
||||
# if not domain_via_user:
|
||||
# raise HTTPException(
|
||||
# status_code=401,
|
||||
# detail="Domain not found. Please contact the admin.",
|
||||
# )
|
||||
# domain_via_user = domain_via_user[0]
|
||||
# if get_main_domain:
|
||||
# return domain_via_user.get("main_domain", None)
|
||||
# return domain_via_user.get("other_domains_list", None)
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
from schemas.base_imports import (
|
||||
CrudCollection,
|
||||
UUID,
|
||||
String,
|
||||
text,
|
||||
mapped_column,
|
||||
Mapped,
|
||||
)
|
||||
|
||||
|
||||
class ApiEnumDropdown(CrudCollection):
|
||||
__tablename__ = "api_enum_dropdown"
|
||||
__exclude__fields__ = ["enum_class"]
|
||||
__language_model__ = None
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
uu_id: Mapped[str] = mapped_column(
|
||||
UUID, server_default=text("gen_random_uuid()"), index=True, unique=True
|
||||
)
|
||||
enum_class: Mapped[str] = mapped_column(
|
||||
String, nullable=False, comment="Enum Constant Name"
|
||||
)
|
||||
key: Mapped[str] = mapped_column(String, nullable=False, comment="Enum Key")
|
||||
value: Mapped[str] = mapped_column(String, nullable=False, comment="Enum Value")
|
||||
description: Mapped[str] = mapped_column(String, nullable=True)
|
||||
|
||||
__table_args__ = ({"comment": "Enum objets that are linked to tables"},)
|
||||
|
||||
@classmethod
|
||||
def get_by_uuid(cls, uuid: str):
|
||||
with cls.new_session() as db_session:
|
||||
return cls.filter_by_one(uu_id=str(uuid), db=db_session).data
|
||||
|
||||
@classmethod
|
||||
def get_debit_search(cls, search_debit: str = None, search_uu_id: str = None):
|
||||
with cls.new_session() as db_session:
|
||||
if search_uu_id:
|
||||
if search := cls.filter_one_system(
|
||||
cls.enum_class.in_(["DebitTypes"]),
|
||||
cls.uu_id == search_uu_id,
|
||||
db=db_session,
|
||||
).data:
|
||||
return search
|
||||
elif search_debit:
|
||||
if search := cls.filter_one(
|
||||
cls.enum_class.in_(["DebitTypes"]),
|
||||
cls.key == search_debit,
|
||||
db=db_session,
|
||||
).data:
|
||||
return search
|
||||
return cls.filter_all_system(
|
||||
cls.enum_class.in_(["DebitTypes"]), db=db_session
|
||||
).data
|
||||
|
||||
@classmethod
|
||||
def get_due_types(cls):
|
||||
with cls.new_session() as db_session:
|
||||
if due_list := cls.filter_all_system(
|
||||
cls.enum_class == "BuildDuesTypes",
|
||||
cls.key.in_(["BDT-A", "BDT-D"]),
|
||||
db=db_session,
|
||||
).data:
|
||||
return [due.uu_id.__str__() for due in due_list]
|
||||
# raise HTTPException(
|
||||
# status_code=404,
|
||||
# detail="No dues types found",
|
||||
# )
|
||||
|
||||
@classmethod
|
||||
def due_type_search(cls, search_management: str = None, search_uu_id: str = None):
|
||||
with cls.new_session() as db_session:
|
||||
if search_uu_id:
|
||||
if search := cls.filter_one_system(
|
||||
cls.enum_class.in_(["BuildDuesTypes"]),
|
||||
cls.uu_id == search_uu_id,
|
||||
db=db_session,
|
||||
).data:
|
||||
return search
|
||||
elif search_management:
|
||||
if search := cls.filter_one_system(
|
||||
cls.enum_class.in_(["BuildDuesTypes"]),
|
||||
cls.key == search_management,
|
||||
db=db_session,
|
||||
).data:
|
||||
return search
|
||||
return cls.filter_all_system(
|
||||
cls.enum_class.in_(["BuildDuesTypes"]), db=db_session
|
||||
).data
|
||||
|
||||
def get_enum_dict(self):
|
||||
return {
|
||||
"uu_id": str(self.uu_id),
|
||||
"enum_class": self.enum_class,
|
||||
"key": self.key,
|
||||
"value": self.value,
|
||||
"description": self.description,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def uuid_of_enum(cls, enum_class: str, key: str):
|
||||
with cls.new_session() as db_session:
|
||||
return str(
|
||||
getattr(
|
||||
cls.filter_one_system(
|
||||
cls.enum_class == enum_class, cls.key == key, db=db_session
|
||||
).data,
|
||||
"uu_id",
|
||||
None,
|
||||
)
|
||||
)
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
from schemas.base_imports import (
|
||||
CrudCollection,
|
||||
UUID,
|
||||
String,
|
||||
mapped_column,
|
||||
Mapped,
|
||||
Index,
|
||||
)
|
||||
|
||||
|
||||
class EndpointRestriction(CrudCollection):
|
||||
"""
|
||||
Initialize Endpoint Restriction with default values
|
||||
"""
|
||||
|
||||
__tablename__ = "endpoint_restriction"
|
||||
__exclude__fields__ = []
|
||||
|
||||
operation_uu_id: Mapped[UUID] = mapped_column(
|
||||
String, comment="UUID of the operation", nullable=False, unique=True
|
||||
)
|
||||
endpoint_function: Mapped[str] = mapped_column(
|
||||
String, comment="Function name of the API endpoint"
|
||||
)
|
||||
endpoint_name: Mapped[str] = mapped_column(
|
||||
String, comment="Name of the API endpoint"
|
||||
)
|
||||
endpoint_method: Mapped[str] = mapped_column(
|
||||
String, comment="HTTP method used by the endpoint"
|
||||
)
|
||||
endpoint_desc: Mapped[str] = mapped_column(
|
||||
String, server_default="", comment="Description of the endpoint"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("idx_endpoint_restriction_operation_uu_id", operation_uu_id, endpoint_method, endpoint_name, unique=True),
|
||||
)
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
services:
|
||||
# auth_service:
|
||||
# container_name: auth_service
|
||||
# build:
|
||||
# context: .
|
||||
# dockerfile: api_services/api_builds/auth-service/Dockerfile
|
||||
# env_file:
|
||||
# - api_env.env
|
||||
# environment:
|
||||
# - API_PATH=app:app
|
||||
# - API_HOST=0.0.0.0
|
||||
# - API_PORT=8001
|
||||
# - API_LOG_LEVEL=info
|
||||
# - API_RELOAD=1
|
||||
# - API_APP_NAME=evyos-auth-api-gateway
|
||||
# - API_TITLE=WAG API Auth Api Gateway
|
||||
# - API_FORGOT_LINK=https://auth_service/forgot-password
|
||||
# - API_DESCRIPTION=This api is serves as web auth api gateway only to evyos web services.
|
||||
# - API_APP_URL=https://auth_service
|
||||
# ports:
|
||||
# - "8000:8000"
|
||||
# restart: unless-stopped
|
||||
# logging:
|
||||
# driver: "json-file"
|
||||
# options:
|
||||
# max-size: "10m"
|
||||
# max-file: "3"
|
||||
|
||||
initializer_service:
|
||||
container_name: initializer_service
|
||||
build:
|
||||
context: .
|
||||
dockerfile: api_services/api_builds/initial-service/Dockerfile
|
||||
environment:
|
||||
- SET_ALEMBIC=0
|
||||
networks:
|
||||
- wag-services
|
||||
env_file:
|
||||
- api_env.env
|
||||
mem_limit: 512m
|
||||
cpus: 0.5
|
||||
|
||||
|
||||
networks:
|
||||
wag-services:
|
||||
driver: bridge
|
||||
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
[project]
|
||||
name = "prod-wag-backend-automate-services"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"alembic>=1.15.2",
|
||||
"arrow>=1.3.0",
|
||||
"cryptography>=44.0.2",
|
||||
"faker>=37.1.0",
|
||||
"fastapi>=0.115.12",
|
||||
"pandas>=2.2.3",
|
||||
"prometheus-fastapi-instrumentator>=7.1.0",
|
||||
"psycopg2-binary>=2.9.10",
|
||||
"pydantic-settings>=2.8.1",
|
||||
"pymongo>=4.11.3",
|
||||
"pytest>=8.3.5",
|
||||
"redbox>=0.2.1",
|
||||
"redis>=5.2.1",
|
||||
"redmail>=0.6.0",
|
||||
"requests>=2.32.3",
|
||||
"sqlalchemy-mixins>=2.0.5",
|
||||
"textdistance>=4.6.3",
|
||||
"unidecode>=1.3.8",
|
||||
"uvicorn>=0.34.0",
|
||||
]
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
|
||||
endpoints_index: dict = {
|
||||
"Slot1": "",
|
||||
"Slot2": "",
|
||||
"Slot3": "",
|
||||
"Slot4": "",
|
||||
"Slot5": "",
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
from fastapi import APIRouter
|
||||
|
||||
|
||||
def get_routes() -> list[APIRouter]:
|
||||
return []
|
||||
|
||||
|
||||
def get_safe_endpoint_urls() -> list[tuple[str, str]]:
|
||||
return [
|
||||
("/", "GET"),
|
||||
("/docs", "GET"),
|
||||
("/redoc", "GET"),
|
||||
("/openapi.json", "GET"),
|
||||
("/metrics", "GET"),
|
||||
]
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
|
||||
events_index: dict = {
|
||||
"Slot1": "",
|
||||
"Slot2": "",
|
||||
"Slot3": "",
|
||||
"Slot4": "",
|
||||
"Slot5": "",
|
||||
}
|
||||
Loading…
Reference in New Issue