This commit is contained in:
Bernhard Radermacher
2025-09-01 11:36:22 +00:00
parent eb1d8d793c
commit 292e296f01
26 changed files with 461 additions and 410 deletions

View File

@@ -2,12 +2,12 @@
<configuration default="false" name="sandboxapi" type="Python.FastAPI"> <configuration default="false" name="sandboxapi" type="Python.FastAPI">
<option name="additionalOptions" value="--reload --host 0.0.0.0 --port 1234" /> <option name="additionalOptions" value="--reload --host 0.0.0.0 --port 1234" />
<option name="file" value="$PROJECT_DIR$/app/main.py" /> <option name="file" value="$PROJECT_DIR$/app/main.py" />
<module name="sandboxapi" /> <module name="vpsx-fast" />
<option name="ENV_FILES" value="" /> <option name="ENV_FILES" value="" />
<option name="INTERPRETER_OPTIONS" value="" /> <option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" /> <option name="PARENT_ENVS" value="true" />
<option name="SDK_HOME" value="$PROJECT_DIR$/.venv/bin/python" /> <option name="SDK_HOME" value="$PROJECT_DIR$/.venv/bin/python" />
<option name="SDK_NAME" value="uv (sandboxapi)" /> <option name="SDK_NAME" value="uv (vpsx-fast)" />
<option name="WORKING_DIRECTORY" value="" /> <option name="WORKING_DIRECTORY" value="" />
<option name="IS_MODULE_SDK" value="false" /> <option name="IS_MODULE_SDK" value="false" />
<option name="ADD_CONTENT_ROOTS" value="true" /> <option name="ADD_CONTENT_ROOTS" value="true" />

26
app/alchemy/base.plantuml Normal file
View File

@@ -0,0 +1,26 @@
@startuml
top to bottom direction
skinparam linetype ortho
abstract Base {
id\t\t\t\tInteger\t\t<<Primary>> {field}
# __tablename__\tto_snake_case(__name__) {field}
}
'abstract DeclarativeBase {
' + registry
' + metadata
' # __name__
' # __mapper__
' # __table__
' # __tablename__
' # __mapper_args__
' # __table_args__
' - _sa_registry
' - _sa_inspect_type()
' - _sa_inspect_instance()
'}
'
'DeclarativeBase <|-- Base
@enduml

View File

@@ -0,0 +1,27 @@
@startuml
'!include base.plantuml
!include status.plantuml
!include user.plantuml
skinparam linetype polyline
class Contact {
{field} + code\tString(80)\t<<Unique>>
{field} + address\tString(255) | None
{field} + notes\tText | None
}
abstract ContactForeignKey {
contact_id\tContact.id {field}
+ contact\t\tContact {field}
}
'Base <|--Contact
StatusForeignKey <|-[#blue]- Contact
Versioned <|-[#blue]- Contact
Contact *-- ContactForeignKey
@enduml

25
app/alchemy/contact.puml Normal file
View File

@@ -0,0 +1,25 @@
@startuml
'!include base.plantuml
!include status.puml
!include user.puml
skinparam linetype polyline
entity contact {
* id\t\t\tint
--
* code\t\tvarchar(80)\t<<UK>>
address\t\tvarchar(255)
notes\t\ttext
status_id\t\tvarchar(3)\t<<FK>>
* _created__\tdatetime
_updated__\tdatetime
_user__id\t\tint\t\t\t<<FK>>
}
status --{ contact : status_id
user ..{ contact : _user__id
@enduml

View File

@@ -13,7 +13,7 @@ class Contact(StatusForeignKey, Versioned, Base):
"""Contact""" """Contact"""
code: Mapped[str] = mapped_column(String(80), unique=True) code: Mapped[str] = mapped_column(String(80), unique=True)
address: Mapped[str | None] = mapped_column(String(253)) address: Mapped[str | None] = mapped_column(String(255))
notes: Mapped[str | None] = mapped_column(Text) notes: Mapped[str | None] = mapped_column(Text)

View File

@@ -0,0 +1,72 @@
@startuml
'!include base.plantuml
!include status.plantuml
!include contact.plantuml
skinparam linetype polyline
class Country {
{field} + code\t\tString(2)\t\t<<Unique>>
{field} + name\t\tString(80)
{field} + notes\t\tText | None
+ __repr__()\t\tstr
}
abstract CountryForeignKey {
country_id\tCountry.id {field}
+ country\t\tCountry {field}
}
class LocationCode {
{field} + code\t\tString(8)\t\t<<Unique>>
{field} + description\tString(80)
{field} + notes\t\tText | None
+ __repr__()\t\tstr
}
abstract LocationCodeForeignKey {
location_code_id\tLocationCode.id
+ location_code\t\tLocationCode
}
class Location {
{field} + code\t\tString(30)
{field} + description\tString(80)
{field} + notes\t\tText | None
{field} <<Unique>>\n\tlocation_code_id\n\tcode
+ __repr__()\t\tstr
}
abstract LocationForeignKey {
location_id\tLocation.id
+ location\t\tLocation
}
Versioned <|-[#blue]- Country
StatusForeignKey <|-[#blue]- Country
Country *-- CountryForeignKey
Versioned <|-[#blue]- LocationCode
StatusForeignKey <|-[#blue]- LocationCode
LocationCode *-- LocationCodeForeignKey
CountryForeignKey <|-[#blue]- LocationCode
ContactForeignKey o.. LocationCode
Versioned <|-[#blue]- Location
StatusForeignKey <|-[#blue]- Location
Location *-- LocationForeignKey
LocationCodeForeignKey <|-[#blue]- Location
ContactForeignKey o.. Location
@enduml

66
app/alchemy/location.puml Normal file
View File

@@ -0,0 +1,66 @@
@startuml
'!include base.plantuml
!include status.puml
!include contact.puml
skinparam linetype polyline
entity country {
* id\t\t\tint
--
* code\t\tvarchar(2)\t<<UK>>
name\t\tvarchar(80)
notes\t\ttext
status_id\t\tvarchar(3)\t<<FK>>
* _created__\tdatetime
_updated__\tdatetime
_user__id\t\tint\t\t\t<<FK>>
}
entity location_code {
* id\t\t\tint
--
* code\t\tvarchar(8)\t<<UK>>
description\tvarchar(80)
notes\t\ttext
* country_id\tint\t\t\t<<FK>>
contact_id\tint\t\t\t<<FK>>
status_id\t\tvarchar(3)\t<<FK>>
* _created__\tdatetime
_updated__\tdatetime
_user__id\t\tint\t\t\t<<FK>>
}
entity location {
* id\t\t\t\tint
.. <<UK>> ..
* location_code_id\tint\t\t\t<<FK>>
* code\t\t\tvarchar(30)
--
description\t\tvarchar(80)
notes\t\t\ttext
contact_id\tint\t\t\t<<FK>>
status_id\t\t\tvarchar(3)\t<<FK>>
* _created__\t\tdatetime
_updated__\t\tdatetime
_user__id\t\t\tint\t\t\t<<FK>>
}
contact ..{ location : contact_id
contact ..{ location_code : contact_id
country --{ location_code : country_id
location_code --{ location : location_code_id
status --{ country : status_id
status --{ location : status_id
status --{ location_code : status_id
user ..{ country : _user__id
user ..{ location : _user__id
user ..{ location_code : _user__id
@enduml

1
app/alchemy/location.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -0,0 +1,20 @@
@startuml
'!include base.plantuml
class Status {
id\t\tString(3)\t\t<<Primary>> {field}
+ name\tString(30)\t<<Unique>> {field}
}
'Base <|-- Status
abstract StatusForeignKey {
status_id\t\tStatus.id {field}
+ status\t\tStatus {field}
}
Status *-- StatusForeignKey
@enduml

9
app/alchemy/status.puml Normal file
View File

@@ -0,0 +1,9 @@
@startuml
entity status {
* id\t\tvarchar(3)
--
* name\tvarchar(30)\t<<UK>>
}
@enduml

31
app/alchemy/user.plantuml Normal file
View File

@@ -0,0 +1,31 @@
@startuml
'!include base.plantuml
!include status.plantuml
skinparam linetype ortho
class User {
{field} + code\t\tString(253)\t<<Unique>>
{field} + name\t\tString(253)
{field} + password\tString(255) | None
{field} + ldap_name\tString(255) | None
{field} + notes\t\tText | None
+ __repr__()\t\tstr
}
abstract Versioned {
{field} + user__\t\tUser
{field} # _created__\tDatetime
{field} # _updated__\tDatetime | None
{field} _user__id\t\tInteger | None
{field} # __versioned__\t= True
}
'Base <|-- User
StatusForeignKey <|-[#blue]- User
Versioned <|-[#blue]- User
User *-- Versioned
@enduml

25
app/alchemy/user.puml Normal file
View File

@@ -0,0 +1,25 @@
@startuml
!include status.puml
skinparam linetype polyline
entity user {
* id\t\t\tint
--
* code\t\tvarchar(255)\t<<UK>>
* name\t\tvarchar(255)
password\tvarchar(255)
ldap_name\tvarchar(255)
notes\t\ttext
status_id\t\tvarchar(3)\t<<FK>>
* _created__\tdatetime
_updated__\tdatetime
_user__id\t\tint\t\t\t<<FK>>
}
status --{ user : status_id
user ..{ user : _user__id
@enduml

View File

@@ -61,15 +61,15 @@ class Versioned:
# noinspection PyMethodParameters # noinspection PyMethodParameters
@declared_attr @declared_attr
def _user__(cls) -> Mapped["User"]: def user__(cls) -> Mapped["User"]:
return relationship() return relationship()
class User(StatusForeignKey, Versioned, Base): class User(StatusForeignKey, Versioned, Base):
"""User""" """User"""
code: Mapped[str] = mapped_column(String(253), unique=True) code: Mapped[str] = mapped_column(String(255), unique=True)
name: Mapped[str] = mapped_column(String(253)) name: Mapped[str] = mapped_column(String(255))
password: Mapped[str | None] = mapped_column(String(255)) password: Mapped[str | None] = mapped_column(String(255))
ldap_name: Mapped[str | None] = mapped_column(String(255)) ldap_name: Mapped[str | None] = mapped_column(String(255))
notes: Mapped[str | None] = mapped_column(Text) notes: Mapped[str | None] = mapped_column(Text)
@@ -90,10 +90,8 @@ def initialize_user(target, connection, **kwargs):
session.commit() session.commit()
for kwargs in ( for kwargs in (
dict(code="CTM", name="Control-M", password=get_password_hash("secret"), notes="user for automation"), dict(code="CTM", name="Control-M", password=get_password_hash("secret"), notes="user for automation"),
dict(code="exde37c8", name="Bernhard Radermacher", dict(code="de31c7", name="Andy Le", ldap_name="a0032514@kiongroup.com"),
password=get_password_hash("secret"), dict(code="exde37c8", name="Bernhard Radermacher", ldap_name="a0061806@kiongroup.com"),
ldap_name="a0061806@kiongroup.com",
),
): ):
kwargs.update(dict(status_id='A', _user__id=qsys.id)) kwargs.update(dict(status_id='A', _user__id=qsys.id))
session.add(User(**kwargs)) session.add(User(**kwargs))

View File

@@ -1,7 +1,7 @@
from sqlalchemy import create_engine from sqlalchemy import create_engine
# engine_url="sqlite+pysqlite:///vpsx.db" # engine_url="sqlite+pysqlite:///vpsx.db"
engine_url="mariadb+pymysql://fast:fast@localhost/fast_vpsx?charset=utf8mb4" engine_url="mariadb+pymysql://fast:fast@mariadb.ctmapp.kiongroup.net/fast_vpsx?charset=utf8mb4"
def get_engine(): def get_engine():

View File

@@ -9,118 +9,94 @@ import alchemy
import utils import utils
from dependencies import get_session from dependencies import get_session
from utils import update_item, create_item from utils import update_item, create_item
from .status_model import Status
from .user import ACTIVE_USER from .user import ACTIVE_USER
from .models import Contact, ContactCreate, ContactUpdate
PRIMARY_ANNOTATION = utils.make_primary_annotation('Contact') PRIMARY_ANNOTATION = utils.make_primary_annotation('Contact')
# ----------------------------------------------------------------
# Models
class ContactBase(SQLModel):
code: str = Field(max_length=80, unique=True)
address: str = Field(max_length=253)
notes: str | None
class Contact(ContactBase):
id: int | None = Field(default=None, primary_key=True)
class ContactCreate(ContactBase):
address: str | None = None
notes: str | None = None
class ContactPublic(ContactBase):
id: int
status: Status
class ContactUpdate(ContactBase):
code: str | None = None
address: str | None = None
notes: str | None = None
# ---------------------------------------------------------------- # ----------------------------------------------------------------
# Routes # Routes
router = APIRouter(prefix="/contact", tags=["contact"]) router = APIRouter(prefix="/contact", tags=["contact"])
@router.get("/", response_model=list[ContactPublic]) @router.get("/",
response_model=list[Contact])
async def get_contacts( async def get_contacts(
offset: int = 0, offset: int = 0,
limit: Annotated[int, Query] = 100, limit: Annotated[int, Query] = 100,
session=Depends(get_session)): session=Depends(get_session)):
"""Get list of all contacts""" """Get list of all Contacts"""
if limit < 1: return session.exec(
limit = sys.maxsize select(
return session.exec(select(alchemy.Contact).offset(offset).limit(limit)).all() alchemy.Contact
).offset(offset).limit(limit or sys.maxsize)).all()
@router.get("/{contact_id}", # noinspection PyTypeHints
response_model=ContactPublic, @router.get("/{contact}",
response_model=Contact,
responses={404: {"description": "Not found"}}) responses={404: {"description": "Not found"}})
async def get_contact( async def get_contact(
contact_id: PRIMARY_ANNOTATION, contact: PRIMARY_ANNOTATION,
session=Depends(get_session)): session=Depends(get_session)):
return utils.get_single_record(session, alchemy.Contact, contact_id) return utils.get_single_record(session, alchemy.Contact, 'Contact', contact)
# noinspection PyTypeHints
@router.post("/", @router.post("/",
response_model=ContactPublic) response_model=Contact)
async def create_contact( async def create_contact(
contact: ContactCreate, data: ContactCreate,
current_user: ACTIVE_USER, current_user: ACTIVE_USER,
session=Depends(get_session)): session=Depends(get_session)):
return create_item( return create_item(
session=session, session=session,
cls=alchemy.Contact, cls=alchemy.Contact,
name='Contact',
current_user=current_user, current_user=current_user,
data=Contact.model_validate(contact)) data=data)
@router.patch("/{contact_id}", @router.patch("/{contact}",
response_model=ContactPublic, response_model=Contact,
responses={404: {"description": "Not found"}}) responses={404: {"description": "Not found"}})
async def update_contact( async def update_contact(
contact_id: PRIMARY_ANNOTATION, contact: PRIMARY_ANNOTATION,
contact: ContactUpdate, data: ContactUpdate,
current_user: ACTIVE_USER, current_user: ACTIVE_USER,
session=Depends(get_session)): session=Depends(get_session)):
return update_item( return update_item(
session=session, session=session,
current_user=current_user, current_user=current_user,
item=utils.get_single_record(session, alchemy.Contact, contact_id), item=utils.get_single_record(session, alchemy.Contact, 'Contact', contact),
data=contact) data=data)
@router.put("/{contact_id}/activate", @router.put("/{contact}/activate",
response_model=ContactPublic, response_model=Contact,
responses={404: {"description": "Not found"}}) responses={404: {"description": "Not found"}})
async def activate_contact( async def activate_contact(
contact_id: PRIMARY_ANNOTATION, contact: PRIMARY_ANNOTATION,
current_user: ACTIVE_USER, current_user: ACTIVE_USER,
session=Depends(get_session)): session=Depends(get_session)):
return utils.set_item_status( return utils.set_item_status(
session=session, session=session,
current_user=current_user, current_user=current_user,
item=utils.get_single_record(session, alchemy.Contact, contact_id), item=utils.get_single_record(session, alchemy.Contact, 'Contact', contact),
status='A') status='A')
@router.put("/{contact_id}/deactivate", @router.put("/{contact}/deactivate",
response_model=ContactPublic, response_model=Contact,
responses={404: {"description": "Not found"}}) responses={404: {"description": "Not found"}})
async def deactivate_contact( async def deactivate_contact(
contact_id: PRIMARY_ANNOTATION, contact: PRIMARY_ANNOTATION,
current_user: ACTIVE_USER, current_user: ACTIVE_USER,
session=Depends(get_session)): session=Depends(get_session)):
return utils.set_item_status( return utils.set_item_status(
session=session, session=session,
current_user=current_user, current_user=current_user,
item=utils.get_single_record(session, alchemy.Contact, contact_id), item=utils.get_single_record(session, alchemy.Contact, 'Contact', contact),
status='I') status='I')

View File

@@ -1,125 +0,0 @@
import sys
from typing import Annotated
from fastapi import APIRouter, Query, Depends
from sqlmodel import SQLModel, Field
from sqlmodel import select
import alchemy
import utils
from dependencies import get_session
from utils import update_item, create_item
from .status_model import Status
from .user import ACTIVE_USER
PRIMARY_ANNOTATION = utils.make_primary_annotation('Country')
# ----------------------------------------------------------------
# Models
class CountryBase(SQLModel):
code: str = Field(max_length=2, unique=True)
name: str = Field(max_length=80)
notes: str | None
class Country(CountryBase):
id: int | None = Field(default=None, primary_key=True)
class CountryCreate(CountryBase):
notes: str | None = None
class CountryPublic(CountryBase):
id: int
status: Status
class CountryUpdate(CountryBase):
code: str | None = None
name: str | None = None
notes: str | None = None
# ----------------------------------------------------------------
# Routes
router = APIRouter(prefix="/country", tags=["country"])
@router.get("/", response_model=list[CountryPublic])
async def get_countries(
offset: int = 0,
limit: Annotated[int, Query] = 100,
session=Depends(get_session)):
"""Get list of all countries"""
if limit < 1:
limit = sys.maxsize
return session.exec(select(alchemy.Country).offset(offset).limit(limit)).all()
@router.get("/{country_id}",
response_model=CountryPublic,
responses={404: {"description": "Not found"}})
async def get_country(
country_id: PRIMARY_ANNOTATION,
session=Depends(get_session)):
return utils.get_single_record(session, alchemy.Country, country_id)
@router.post("/",
response_model=CountryPublic)
async def create_country(
country: CountryCreate,
current_user: ACTIVE_USER,
session=Depends(get_session)):
return create_item(
session=session,
cls=alchemy.Country,
current_user=current_user,
data=Country.model_validate(country))
@router.patch("/{country_id}",
response_model=CountryPublic,
responses={404: {"description": "Not found"}})
async def update_country(
country_id: PRIMARY_ANNOTATION,
country: CountryUpdate,
current_user: ACTIVE_USER,
session=Depends(get_session)):
return update_item(
session=session,
current_user=current_user,
item=utils.get_single_record(session, alchemy.Country, country_id),
data=country)
@router.put("/{country_id}/activate",
response_model=CountryPublic,
responses={404: {"description": "Not found"}})
async def activate_country(
country_id: PRIMARY_ANNOTATION,
current_user: ACTIVE_USER,
session=Depends(get_session)):
return utils.set_item_status(
session=session,
current_user=current_user,
item=utils.get_single_record(session, alchemy.Country, country_id),
status='A')
@router.put("/{country_id}/deactivate",
response_model=CountryPublic,
responses={404: {"description": "Not found"}})
async def deactivate_country(
country_id: PRIMARY_ANNOTATION,
current_user: ACTIVE_USER,
session=Depends(get_session)):
return utils.set_item_status(
session=session,
current_user=current_user,
item=utils.get_single_record(session, alchemy.Country, country_id),
status='I')

View File

@@ -4,14 +4,13 @@ from typing import Annotated
from fastapi import APIRouter, Query, Depends, HTTPException from fastapi import APIRouter, Query, Depends, HTTPException
# from sqlmodel import SQLModel, Field # from sqlmodel import SQLModel, Field
from sqlmodel import select, and_ from sqlmodel import select, and_
from pydantic import BaseModel, Field
# from sqlalchemy import select # from sqlalchemy import select
import alchemy import alchemy
import utils import utils
from dependencies import get_session from dependencies import get_session
from utils import update_item, create_item from utils import update_item
from .status_model import Status from .models.location import LocationCode, Country, LocationCreate, LocationCodeCreate, CountryCreate, CountryUpdate
from .user import ACTIVE_USER from .user import ACTIVE_USER
PRIMARY_ANNOTATION = utils.make_primary_annotation('Country') PRIMARY_ANNOTATION = utils.make_primary_annotation('Country')
@@ -73,66 +72,6 @@ PRIMARY_ANNOTATION = utils.make_primary_annotation('Country')
# notes: str | None = None # notes: str | None = None
class Contact(BaseModel):
code: str
address: str | None
notes: str | None
class Location(BaseModel):
id: int
code: str
description: str | None
notes: str | None
status: Status
contact: Contact | None
class LocationCode(BaseModel):
id: int
code: str
description: str | None
notes: str | None
status: Status
locations: list[Location]
contact: Contact | None
class Country(BaseModel):
id: int
code: str
name: str
notes: str | None
status: Status
location_codes: list[LocationCode] | None
class LocationCreate(BaseModel):
code: str = Field(max_length=30)
description: str | None = None
notes: str | None = None
class LocationCodeCreate(BaseModel):
code: str = Field(max_length=8)
description: str | None = None
notes: str | None = None
locations: list[LocationCreate] | None = None
class CountryCreate(BaseModel):
code: str = Field(max_length=2)
name: str = Field(max_length=80)
notes: str | None = None
location_codes: list[LocationCodeCreate] | None = None
class CountryUpdate(BaseModel):
code: str | None = Field(default=None, max_length=2)
name: str | None = Field(default=None, max_length=80)
notes: str | None = None
# class CountryUpdate(CountryBase): # class CountryUpdate(CountryBase):
# code: str | None = None # code: str | None = None
# name: str | None = None # name: str | None = None
@@ -243,7 +182,7 @@ async def deactivate_country(
@router.post("/{country}", @router.post("/{country}",
response_model=LocationCode,) response_model=LocationCode, )
async def create_location_code( async def create_location_code(
country: PRIMARY_ANNOTATION, country: PRIMARY_ANNOTATION,
data: LocationCodeCreate, data: LocationCodeCreate,
@@ -274,7 +213,7 @@ async def create_location_code(
@router.post("/{country}/{location_code}", @router.post("/{country}/{location_code}",
response_model=LocationCode,) response_model=LocationCode, )
async def create_location( async def create_location(
country: PRIMARY_ANNOTATION, country: PRIMARY_ANNOTATION,
location_code: PRIMARY_ANNOTATION, location_code: PRIMARY_ANNOTATION,

View File

@@ -1,134 +0,0 @@
import sys
from typing import Annotated
from fastapi import APIRouter, Query, Depends
from sqlmodel import SQLModel, Field
from sqlmodel import select
import alchemy
import utils
from dependencies import get_session
from utils import update_item, create_item
from .status_model import Status
from .user import ACTIVE_USER
from .contact import Contact
from .country import Country
PRIMARY_ANNOTATION = utils.make_primary_annotation('Location Code')
# ----------------------------------------------------------------
# Models
class LocationCodeBase(SQLModel):
code: str = Field(max_length=8, unique=True)
description: str = Field(max_length=256)
notes: str | None
class LocationCode(LocationCodeBase):
id: int | None = Field(default=None, primary_key=True)
country: Country
contact: Contact | None = None
class LocationCodeCreate(LocationCodeBase):
country_id: int
contact_id : int | None = None
notes: str | None = None
class LocationCodePublic(LocationCode):
status: Status
class LocationCodeUpdate(LocationCodeBase):
code: str | None = None
description: str | None = None
country_id: int | None = None
contact_id: int | None = None
notes: str | None = None
# ----------------------------------------------------------------
# Routes
router = APIRouter(prefix="/location_code", tags=["location_code"])
@router.get("/", response_model=list[LocationCodePublic])
async def get_location_codes(
offset: int = 0,
limit: Annotated[int, Query] = 100,
session=Depends(get_session)):
"""Get list of all location codes"""
if limit < 1:
limit = sys.maxsize
return session.exec(select(alchemy.LocationCode).offset(offset).limit(limit)).all()
@router.get("/{location_code_id}",
response_model=LocationCodePublic,
responses={404: {"description": "Not found"}})
async def get_location_code(
location_code_id: PRIMARY_ANNOTATION,
session=Depends(get_session)):
return utils.get_single_record(session, alchemy.LocationCode, location_code_id)
@router.post("/",
response_model=LocationCodePublic)
async def create_location_code(
location_code: LocationCodeCreate,
current_user: ACTIVE_USER,
session=Depends(get_session)):
return create_item(
session=session,
cls=alchemy.LocationCode,
current_user=current_user,
data=LocationCodeCreate.model_validate(location_code))
@router.patch("/{location_code_id}",
response_model=LocationCodePublic,
responses={404: {"description": "Not found"}})
async def update_location_code(
location_code_id: PRIMARY_ANNOTATION,
location_code: LocationCodeUpdate,
current_user: ACTIVE_USER,
session=Depends(get_session)):
return update_item(
session=session,
current_user=current_user,
item=utils.get_single_record(session, alchemy.LocationCode, location_code_id),
data=location_code)
@router.put("/{location_code_id}/activate",
response_model=LocationCodePublic,
responses={404: {"description": "Not found"}})
async def activate_location_code(
locationCode_id: PRIMARY_ANNOTATION,
current_user: ACTIVE_USER,
session=Depends(get_session)):
return utils.set_item_status(
session=session,
current_user=current_user,
item=utils.get_single_record(session, alchemy.LocationCode, locationCode_id),
status='A')
@router.put("/{location_code_id}/deactivate",
response_model=LocationCodePublic,
responses={404: {"description": "Not found"}})
async def deactivate_location_code(
location_code_id: PRIMARY_ANNOTATION,
current_user: ACTIVE_USER,
session=Depends(get_session)):
return utils.set_item_status(
session=session,
current_user=current_user,
item=utils.get_single_record(session, alchemy.LocationCode, location_code_id),
status='I')

View File

@@ -0,0 +1,5 @@
from .contact import Contact, ContactCreate, ContactUpdate
from .location import Country, CountryCreate, CountryUpdate, LocationCode, LocationCodeCreate, Location, LocationCreate
from .status import Status

View File

@@ -0,0 +1,23 @@
from typing import Annotated
from pydantic import BaseModel, Field
from .status import Status
class Contact(BaseModel):
id: int
code: str
address: str | None
notes: str | None
status: Status | None
class ContactCreate(BaseModel):
code: str = Field(max_length=80)
address: Annotated[str | None, Field(max_length=253)] = None
notes: str | None = None
class ContactUpdate(ContactCreate):
code: Annotated[str | None, Field(max_length=80)] = None

View File

@@ -0,0 +1,66 @@
from typing import Annotated
from pydantic import BaseModel, Field
from .contact import Contact
from .status import Status
class Location(BaseModel):
id: int
code: str
description: str | None
notes: str | None
status: Status
contact: Contact | None
class LocationCreate(BaseModel):
code: str = Field(max_length=30)
description: Annotated[str | None, Field(max_length=80)] = None
notes: str | None = None
class LocationUpdate(LocationCreate):
code: Annotated[str | None, Field(max_length=30)] = None
class LocationCode(BaseModel):
id: int
code: str
description: str | None
notes: str | None
status: Status
locations: list[Location]
contact: Contact | None
class LocationCodeCreate(BaseModel):
code: str = Field(max_length=8)
description: Annotated[str | None, Field(max_length=80)] = None
notes: str | None = None
locations: list[LocationCreate] | None = None
class Country(BaseModel):
id: int
code: str
name: str
notes: str | None
status: Status
location_codes: list[LocationCode] | None
class CountryCreate(BaseModel):
code: str = Field(max_length=2)
name: str = Field(max_length=80)
notes: str | None = None
location_codes: list[LocationCodeCreate] | None = None
class CountryUpdate(BaseModel):
code: str | None = Field(default=None, max_length=2)
name: str | None = Field(default=None, max_length=80)
notes: str | None = None

View File

@@ -0,0 +1,6 @@
from pydantic import BaseModel
class Status(BaseModel):
id: str
name: str

View File

@@ -3,7 +3,7 @@ from sqlmodel import select, Session
import alchemy import alchemy
from dependencies import get_session from dependencies import get_session
from routers.status_model import Status from routers.models import Status
router = APIRouter(prefix="/status", tags=["status"]) router = APIRouter(prefix="/status", tags=["status"])

View File

@@ -1,6 +0,0 @@
from sqlmodel import SQLModel, Field
class Status(SQLModel):
id: str | None = Field(max_length=3, primary_key=True)
name: str = Field(max_length=30, unique=True)

View File

@@ -16,7 +16,7 @@ from sqlmodel import SQLModel, Field, Session, select
import alchemy import alchemy
import utils import utils
from dependencies import get_session from dependencies import get_session
from routers.status_model import Status from routers.models import Status
logging.getLogger('passlib').setLevel(logging.ERROR) logging.getLogger('passlib').setLevel(logging.ERROR)

View File

@@ -35,10 +35,11 @@ def set_item_status(session, current_user, item, status: str):
def update_item(session, current_user, item, data): def update_item(session, current_user, item, data):
for k, v in data.model_dump(exclude_unset=True).items(): for k, v in data.model_dump(exclude_unset=True).items():
if (k == 'code' and if k == 'code':
session.scalar(select(item.__class__).where(item.__class__.code == v)) != item): r = session.scalar(select(item.__class__).where(item.__class__.code == v))
if r and r != item:
raise HTTPException(status_code=422, raise HTTPException(status_code=422,
detail=[dict(msg=f"{item.__class__.__name__} {k!r} already exists", detail=[dict(msg=f"{item.__class__.__name__} {v!r} already exists",
type="Integrity Error")]) type="Integrity Error")])
setattr(item, k, v) setattr(item, k, v)
item._user__id = current_user.id item._user__id = current_user.id
@@ -52,11 +53,11 @@ def update_item(session, current_user, item, data):
return item return item
def create_item(session, cls, current_user, data): def create_item(session, cls, name, current_user, data):
item = cls(**data.model_dump()) item = cls(**data.model_dump())
if session.scalar(select(cls).where(cls.code == item.code)): if session.scalar(select(cls).where(cls.code == item.code)):
raise HTTPException(status_code=422, raise HTTPException(status_code=422,
detail=[dict(msg=f"{cls.__class__.__name__} {item.code} already exists", detail=[dict(msg=f"{name} {item.code!r} already exists",
type="Integrity Error")]) type="Integrity Error")])
item._user__id = current_user.id item._user__id = current_user.id
session.add(item) session.add(item)