feat: use Mapped syntax for columns
This commit is contained in:
parent
cb70f17ad5
commit
6397bd1316
9 changed files with 108 additions and 105 deletions
|
|
@ -12,7 +12,7 @@ from src.organisation.models import Organisation, OrgUsers
|
|||
from src.user.models import User
|
||||
from src.service.models import Service
|
||||
from src.iam.models import Permission, Group, GroupPermissions, UserGroups
|
||||
from src.database import Base
|
||||
from src.models import CustomBase
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
|
|
@ -27,7 +27,7 @@ if config.config_file_name is not None:
|
|||
# for 'autogenerate' support
|
||||
# from myapp import mymodel
|
||||
# target_metadata = mymodel.Base.metadata
|
||||
target_metadata = Base.metadata
|
||||
target_metadata = CustomBase.metadata
|
||||
|
||||
# other values from the config, defined by the needs of env.py,
|
||||
# can be acquired:
|
||||
|
|
|
|||
|
|
@ -5,30 +5,30 @@ Models:
|
|||
- Contact: id[pk], email, first_name, last_name, phonenumber, vat_number
|
||||
street_address, street_address_line_2, post_office_box_number, address_locality, country_code, address_region, postal_code
|
||||
"""
|
||||
from sqlalchemy import ForeignKey
|
||||
from sqlalchemy.orm import mapped_column, Mapped
|
||||
|
||||
from sqlalchemy import Column, Integer, String, ForeignKey
|
||||
|
||||
from src.database import Base
|
||||
from src.models import CustomBase
|
||||
|
||||
|
||||
class Contact(Base):
|
||||
class Contact(CustomBase):
|
||||
__tablename__ = "contact"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
email = Column(String)
|
||||
first_name = Column(String)
|
||||
last_name = Column(String)
|
||||
phonenumber = Column(String)
|
||||
vat_number = Column(String, default=None, nullable=True)
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
email: Mapped[str]
|
||||
first_name: Mapped[str]
|
||||
last_name: Mapped[str]
|
||||
phonenumber: Mapped[str]
|
||||
vat_number: Mapped[str | None] = mapped_column(default=None)
|
||||
|
||||
street_address = Column(String)
|
||||
street_address_line_2 = Column(String)
|
||||
post_office_box_number = Column(String, default=None, nullable=True)
|
||||
locality = Column(String) # Ie City
|
||||
country_code = Column(String) # Eg GB
|
||||
address_region = Column(String, default=None, nullable=True)
|
||||
postal_code = Column(String)
|
||||
street_address : Mapped[str]
|
||||
street_address_line_2 : Mapped[str]
|
||||
post_office_box_number: Mapped[str | None] = mapped_column(default=None)
|
||||
locality : Mapped[str] # Ie City
|
||||
country_code : Mapped[str] # Eg GB
|
||||
address_region: Mapped[str | None] = mapped_column(default=None)
|
||||
postal_code : Mapped[str]
|
||||
|
||||
org_id = Column(
|
||||
Integer, ForeignKey("organisation.id", ondelete="CASCADE"), nullable=False
|
||||
org_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("organisation.id", ondelete="CASCADE"), nullable=False
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5,10 +5,9 @@ Exports:
|
|||
- db_dependency
|
||||
- Base (sqlalchemy base model)
|
||||
"""
|
||||
|
||||
from typing import Annotated
|
||||
from sqlalchemy import create_engine, StaticPool
|
||||
from sqlalchemy.orm import DeclarativeBase, sessionmaker, Session
|
||||
from sqlalchemy.orm import sessionmaker, Session
|
||||
|
||||
from fastapi import Depends
|
||||
|
||||
|
|
@ -41,7 +40,3 @@ def get_db():
|
|||
|
||||
|
||||
db_dependency = Annotated[Session, Depends(get_db)]
|
||||
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -18,20 +18,20 @@ Models:
|
|||
- org_id[FK][PK], user_id[FK][PK], group_id[FK][PK]
|
||||
"""
|
||||
|
||||
from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy import ForeignKey, UniqueConstraint
|
||||
from sqlalchemy.orm import relationship, mapped_column, Mapped
|
||||
|
||||
from src.database import Base
|
||||
from src.models import CustomBase
|
||||
|
||||
|
||||
class Permission(Base):
|
||||
class Permission(CustomBase):
|
||||
__tablename__ = "permission"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
resource = Column(String, nullable=False)
|
||||
action = Column(String, nullable=False)
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
resource: Mapped[str]
|
||||
action: Mapped[str]
|
||||
|
||||
service_id = Column(Integer, ForeignKey("service.id", ondelete="CASCADE"))
|
||||
service_id: Mapped[int] = mapped_column(ForeignKey("service.id", ondelete="CASCADE"))
|
||||
|
||||
__table_args__ = (
|
||||
UniqueConstraint(
|
||||
|
|
@ -46,10 +46,6 @@ class Permission(Base):
|
|||
"Service", back_populates="permission_rel", foreign_keys="Permission.service_id"
|
||||
)
|
||||
|
||||
@property
|
||||
def service_name(self):
|
||||
return self.service_rel.name
|
||||
|
||||
group_rel = relationship(
|
||||
"Group", secondary="group_permissions", back_populates="permission_rel"
|
||||
)
|
||||
|
|
@ -58,13 +54,17 @@ class Permission(Base):
|
|||
"Organisation", secondary="org_permissions", back_populates="permission_rel"
|
||||
)
|
||||
|
||||
@property
|
||||
def service_name(self):
|
||||
return self.service_rel.name
|
||||
|
||||
class Group(Base):
|
||||
|
||||
class Group(CustomBase):
|
||||
__tablename__ = "group"
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String, nullable=False)
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
name: Mapped[str]
|
||||
|
||||
org_id = Column(Integer, ForeignKey("organisation.id", ondelete="CASCADE"))
|
||||
org_id: Mapped[int] = mapped_column(ForeignKey("organisation.id", ondelete="CASCADE"))
|
||||
|
||||
__table_args__ = (
|
||||
UniqueConstraint(
|
||||
|
|
@ -83,31 +83,31 @@ class Group(Base):
|
|||
)
|
||||
|
||||
|
||||
class GroupPermissions(Base):
|
||||
class GroupPermissions(CustomBase):
|
||||
__tablename__ = "group_permissions"
|
||||
group_id = Column(
|
||||
Integer, ForeignKey("group.id", ondelete="CASCADE"), primary_key=True
|
||||
group_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("group.id", ondelete="CASCADE"), primary_key=True
|
||||
)
|
||||
permission_id = Column(
|
||||
Integer, ForeignKey("permission.id", ondelete="CASCADE"), primary_key=True
|
||||
permission_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("permission.id", ondelete="CASCADE"), primary_key=True
|
||||
)
|
||||
|
||||
|
||||
class UserGroups(Base):
|
||||
class UserGroups(CustomBase):
|
||||
__tablename__ = "user_groups"
|
||||
user_id = Column(
|
||||
Integer, ForeignKey("user.id", ondelete="CASCADE"), primary_key=True
|
||||
user_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("user.id", ondelete="CASCADE"), primary_key=True
|
||||
)
|
||||
group_id = Column(
|
||||
Integer, ForeignKey("group.id", ondelete="CASCADE"), primary_key=True
|
||||
group_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("group.id", ondelete="CASCADE"), primary_key=True
|
||||
)
|
||||
|
||||
|
||||
class OrgPermissions(Base):
|
||||
class OrgPermissions(CustomBase):
|
||||
__tablename__ = "org_permissions"
|
||||
org_id = Column(
|
||||
Integer, ForeignKey("organisation.id", ondelete="CASCADE"), primary_key=True
|
||||
org_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("organisation.id", ondelete="CASCADE"), primary_key=True
|
||||
)
|
||||
permission_id = Column(
|
||||
Integer, ForeignKey("permission.id", ondelete="CASCADE"), primary_key=True
|
||||
permission_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("permission.id", ondelete="CASCADE"), primary_key=True
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,16 @@
|
|||
"""
|
||||
Global database models
|
||||
"""
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import DateTime, JSON
|
||||
from sqlalchemy.orm import DeclarativeBase
|
||||
|
||||
|
||||
class CustomBase(DeclarativeBase):
|
||||
type_annotation_map = {
|
||||
datetime: DateTime(timezone=True),
|
||||
dict[str, Any]: JSON,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,26 +13,26 @@ Models:
|
|||
- owner_contact_rel: ORM relationship to Contact with owner_contact FK
|
||||
- OrgUsers: org_id[FK][PK], user_id[FK][PK]
|
||||
"""
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import Column, Integer, String, ForeignKey, JSON
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy import ForeignKey
|
||||
from sqlalchemy.orm import relationship, Mapped, mapped_column
|
||||
|
||||
from src.database import Base
|
||||
from src.models import CustomBase
|
||||
|
||||
|
||||
class Organisation(Base):
|
||||
class Organisation(CustomBase):
|
||||
__tablename__ = "organisation"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String, unique=True)
|
||||
status = Column(String, default="partial")
|
||||
intake_questionnaire = Column(JSON)
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
name: Mapped[str]
|
||||
status: Mapped[str] = mapped_column(default="partial")
|
||||
intake_questionnaire: Mapped[dict[str, Any] | None]
|
||||
|
||||
root_user_id = Column(Integer, ForeignKey("user.id"))
|
||||
|
||||
billing_contact_id = Column(Integer, ForeignKey("contact.id"))
|
||||
security_contact_id = Column(Integer, ForeignKey("contact.id"))
|
||||
owner_contact_id = Column(Integer, ForeignKey("contact.id"))
|
||||
root_user_id: Mapped[int] = mapped_column(ForeignKey("user.id"))
|
||||
billing_contact_id: Mapped[int] = mapped_column(ForeignKey("contact.id"))
|
||||
security_contact_id: Mapped[int] = mapped_column(ForeignKey("contact.id"))
|
||||
owner_contact_id: Mapped[int] = mapped_column(ForeignKey("contact.id"))
|
||||
|
||||
user_rel = relationship(
|
||||
"User", secondary="orgusers", back_populates="organisation_rel"
|
||||
|
|
@ -41,10 +41,6 @@ class Organisation(Base):
|
|||
group_rel = relationship("Group", back_populates="org_rel")
|
||||
root_user_rel = relationship("User", foreign_keys="Organisation.root_user_id")
|
||||
|
||||
@property
|
||||
def root_user_email(self):
|
||||
return self.root_user_rel.email if self.root_user_rel else None
|
||||
|
||||
billing_contact_rel = relationship(
|
||||
"Contact", foreign_keys="Organisation.billing_contact_id"
|
||||
)
|
||||
|
|
@ -59,13 +55,14 @@ class Organisation(Base):
|
|||
"Permission", secondary="org_permissions", back_populates="org_rel"
|
||||
)
|
||||
|
||||
@property
|
||||
def root_user_email(self):
|
||||
return self.root_user_rel.email if self.root_user_rel else None
|
||||
|
||||
class OrgUsers(Base):
|
||||
class OrgUsers(CustomBase):
|
||||
__tablename__ = "orgusers"
|
||||
|
||||
org_id = Column(
|
||||
Integer, ForeignKey("organisation.id", ondelete="CASCADE"), primary_key=True
|
||||
)
|
||||
user_id = Column(
|
||||
Integer, ForeignKey("user.id", ondelete="CASCADE"), primary_key=True
|
||||
org_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("organisation.id", ondelete="CASCADE"), primary_key=True
|
||||
)
|
||||
user_id: Mapped[int] = mapped_column(ForeignKey("user.id", ondelete="CASCADE"), primary_key=True)
|
||||
|
|
|
|||
|
|
@ -6,17 +6,16 @@ Models:
|
|||
- id[PK], name[U], api_key[U]
|
||||
"""
|
||||
|
||||
from sqlalchemy import Column, Integer, String
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.orm import relationship, mapped_column, Mapped
|
||||
|
||||
from src.database import Base
|
||||
from src.models import CustomBase
|
||||
|
||||
|
||||
class Service(Base):
|
||||
class Service(CustomBase):
|
||||
__tablename__ = "service"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String, unique=True)
|
||||
api_key = Column(String, unique=True)
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
name: Mapped[str] = mapped_column(unique=True)
|
||||
api_key: Mapped[str]
|
||||
|
||||
permission_rel = relationship("Permission", back_populates="service_rel")
|
||||
|
|
|
|||
|
|
@ -12,33 +12,32 @@ Models:
|
|||
|
||||
from collections import defaultdict
|
||||
|
||||
from sqlalchemy import Column, Integer, String
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.orm import relationship, mapped_column, Mapped
|
||||
|
||||
from src.database import Base
|
||||
from src.models import CustomBase
|
||||
|
||||
|
||||
class User(Base):
|
||||
class User(CustomBase):
|
||||
__tablename__ = "user"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
email = Column(String)
|
||||
first_name = Column(String)
|
||||
last_name = Column(String)
|
||||
oidc_id = Column(String, index=True, unique=True)
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
email: Mapped[str]
|
||||
first_name: Mapped[str]
|
||||
last_name: Mapped[str]
|
||||
oidc_id: Mapped[str] = mapped_column(index=True, unique=True)
|
||||
|
||||
organisation_rel = relationship(
|
||||
"Organisation", secondary="orgusers", back_populates="user_rel"
|
||||
)
|
||||
|
||||
@property
|
||||
def organisations(self):
|
||||
return [{"name": org.name, "id": org.id} for org in self.organisation_rel]
|
||||
|
||||
group_rel = relationship(
|
||||
"Group", secondary="user_groups", back_populates="user_rel"
|
||||
)
|
||||
|
||||
@property
|
||||
def organisations(self):
|
||||
return [{"name": org.name, "id": org.id} for org in self.organisation_rel]
|
||||
|
||||
@property
|
||||
def groups(self):
|
||||
result = defaultdict(list)
|
||||
|
|
|
|||
|
|
@ -14,16 +14,16 @@ from src.iam.models import Group, Permission, OrgPermissions
|
|||
from src.auth.service import get_current_user, get_dev_user
|
||||
from src.auth.dependencies import empty_su_list, get_super_admin_list, testing_su_list
|
||||
from src.main import app # inited FastAPI app
|
||||
from src.database import engine, Base, get_db
|
||||
|
||||
from src.database import engine, get_db
|
||||
from models import CustomBase
|
||||
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def db_session():
|
||||
Base.metadata.drop_all(bind=engine)
|
||||
Base.metadata.create_all(bind=engine)
|
||||
CustomBase.metadata.drop_all(bind=engine)
|
||||
CustomBase.metadata.create_all(bind=engine)
|
||||
db = SessionLocal()
|
||||
try:
|
||||
_seed(db) # extracted seeding logic into a plain function
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue