From 888cf5118aa1f794f9c87413aa29b5db54adc84a Mon Sep 17 00:00:00 2001 From: Kevin Morris Date: Sun, 6 Jun 2021 22:45:40 -0700 Subject: [PATCH] use declarative_base for all ORM models This rewrites the entire model base as declarative models. This allows us to more easily customize overlay fields in tables and is more common. This effort also brought some DB violations to light which this commit addresses. Signed-off-by: Kevin Morris --- aurweb/db.py | 13 ++---- aurweb/models/__init__.py | 1 + aurweb/models/accepted_term.py | 41 ++++++++++------- aurweb/models/account_type.py | 14 +++--- aurweb/models/api_rate_limit.py | 18 +++++--- aurweb/models/ban.py | 15 ++++--- aurweb/models/declarative.py | 10 +++++ aurweb/models/dependency_type.py | 15 ++++--- aurweb/models/group.py | 15 ++++--- aurweb/models/license.py | 15 ++++--- aurweb/models/official_provider.py | 15 ++++--- aurweb/models/package.py | 42 +++++++++++------- aurweb/models/package_base.py | 58 +++++++++++++++--------- aurweb/models/package_dependency.py | 55 ++++++++++++++--------- aurweb/models/package_group.py | 48 +++++++++++--------- aurweb/models/package_keyword.py | 32 +++++++------ aurweb/models/package_license.py | 52 ++++++++++++---------- aurweb/models/package_relation.py | 52 ++++++++++++---------- aurweb/models/relation_type.py | 15 ++++--- aurweb/models/session.py | 24 ++++++---- aurweb/models/ssh_pub_key.py | 26 +++++++---- aurweb/models/term.py | 15 ++++--- aurweb/models/user.py | 69 ++++++++++++----------------- test/test_accepted_term.py | 4 +- test/test_account_type.py | 3 +- test/test_package.py | 10 ++--- test/test_package_group.py | 2 +- test/test_package_license.py | 2 +- test/test_session.py | 9 ++-- 29 files changed, 398 insertions(+), 292 deletions(-) create mode 100644 aurweb/models/declarative.py diff --git a/aurweb/db.py b/aurweb/db.py index 1f6f50d8..9837c746 100644 --- a/aurweb/db.py +++ b/aurweb/db.py @@ -1,7 +1,5 @@ import math -from sqlalchemy.orm import backref, relationship - import aurweb.config import aurweb.util @@ -53,12 +51,6 @@ def make_random_value(table: str, column: str): return string -def make_relationship(model, foreign_key: str, backref_: str, **kwargs): - return relationship(model, foreign_keys=[foreign_key], - backref=backref(backref_, lazy="dynamic"), - **kwargs) - - def query(model, *args, **kwargs): return session.query(model).filter(*args, **kwargs) @@ -77,6 +69,10 @@ def delete(model, *args, **kwargs): session.commit() +def rollback(): + session.rollback() + + def get_sqlalchemy_url(): """ Build an SQLAlchemy for use with create_engine based on the aurweb configuration. @@ -137,7 +133,6 @@ def get_engine(echo: bool = False): # check_same_thread is for a SQLite technicality # https://fastapi.tiangolo.com/tutorial/sql-databases/#note connect_args["check_same_thread"] = False - engine = create_engine(get_sqlalchemy_url(), connect_args=connect_args, echo=echo) diff --git a/aurweb/models/__init__.py b/aurweb/models/__init__.py index e69de29b..ed0532c6 100644 --- a/aurweb/models/__init__.py +++ b/aurweb/models/__init__.py @@ -0,0 +1 @@ +# aurweb SQLAlchemy ORM model collection. diff --git a/aurweb/models/accepted_term.py b/aurweb/models/accepted_term.py index 483109f1..b46d086b 100644 --- a/aurweb/models/accepted_term.py +++ b/aurweb/models/accepted_term.py @@ -1,15 +1,33 @@ +from sqlalchemy import Column, ForeignKey, Integer from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import mapper +from sqlalchemy.orm import backref, relationship -from aurweb.db import make_relationship -from aurweb.models.term import Term -from aurweb.models.user import User -from aurweb.schema import AcceptedTerms +import aurweb.models.term +import aurweb.models.user + +from aurweb.models.declarative import Base -class AcceptedTerm: +class AcceptedTerm(Base): + __tablename__ = "AcceptedTerms" + + UsersID = Column(Integer, ForeignKey("Users.ID", ondelete="CASCADE"), + nullable=False) + User = relationship( + "User", backref=backref("accepted_terms", lazy="dynamic"), + foreign_keys=[UsersID]) + + TermsID = Column(Integer, ForeignKey("Terms.ID", ondelete="CASCADE"), + nullable=False) + Term = relationship( + "Term", backref=backref("accepted_terms", lazy="dynamic"), + foreign_keys=[TermsID]) + + __mapper_args__ = {"primary_key": [TermsID]} + def __init__(self, - User: User = None, Term: Term = None, + User: aurweb.models.user.User = None, + Term: aurweb.models.term.Term = None, Revision: int = None): self.User = User if not self.User: @@ -26,12 +44,3 @@ class AcceptedTerm: params=("NULL")) self.Revision = Revision - - -properties = { - "User": make_relationship(User, AcceptedTerms.c.UsersID, "accepted_terms"), - "Term": make_relationship(Term, AcceptedTerms.c.TermsID, "accepted") -} - -mapper(AcceptedTerm, AcceptedTerms, properties=properties, - primary_key=[AcceptedTerms.c.UsersID, AcceptedTerms.c.TermsID]) diff --git a/aurweb/models/account_type.py b/aurweb/models/account_type.py index 44225e35..502a86b1 100644 --- a/aurweb/models/account_type.py +++ b/aurweb/models/account_type.py @@ -1,10 +1,15 @@ -from sqlalchemy.orm import mapper +from sqlalchemy import Column, Integer -from aurweb.schema import AccountTypes +from aurweb.models.declarative import Base -class AccountType: +class AccountType(Base): """ An ORM model of a single AccountTypes record. """ + __tablename__ = "AccountTypes" + + ID = Column(Integer, primary_key=True) + + __mapper_args__ = {"primary_key": [ID]} def __init__(self, **kwargs): self.AccountType = kwargs.pop("AccountType") @@ -15,6 +20,3 @@ class AccountType: def __repr__(self): return "" % ( self.ID, str(self)) - - -mapper(AccountType, AccountTypes, confirm_deleted_rows=False) diff --git a/aurweb/models/api_rate_limit.py b/aurweb/models/api_rate_limit.py index 8b945b6a..f4590553 100644 --- a/aurweb/models/api_rate_limit.py +++ b/aurweb/models/api_rate_limit.py @@ -1,11 +1,18 @@ +from sqlalchemy import Column, String from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import mapper -from aurweb.schema import ApiRateLimit as _ApiRateLimit +from aurweb.models.declarative import Base -class ApiRateLimit: - def __init__(self, IP: str = None, +class ApiRateLimit(Base): + __tablename__ = "ApiRateLimit" + + IP = Column(String(45), primary_key=True, unique=True, default=str()) + + __mapper_args__ = {"primary_key": [IP]} + + def __init__(self, + IP: str = None, Requests: int = None, WindowStart: int = None): self.IP = IP @@ -23,6 +30,3 @@ class ApiRateLimit: statement="Column WindowStart cannot be null.", orig="ApiRateLimit.WindowStart", params=("NULL")) - - -mapper(ApiRateLimit, _ApiRateLimit, primary_key=[_ApiRateLimit.c.IP]) diff --git a/aurweb/models/ban.py b/aurweb/models/ban.py index be030380..e10087b0 100644 --- a/aurweb/models/ban.py +++ b/aurweb/models/ban.py @@ -1,10 +1,16 @@ from fastapi import Request -from sqlalchemy.orm import mapper +from sqlalchemy import Column, String -from aurweb.schema import Bans +from aurweb.models.declarative import Base -class Ban: +class Ban(Base): + __tablename__ = "Bans" + + IPAddress = Column(String(45), primary_key=True) + + __mapper_args__ = {"primary_key": [IPAddress]} + def __init__(self, **kwargs): self.IPAddress = kwargs.get("IPAddress") self.BanTS = kwargs.get("BanTS") @@ -14,6 +20,3 @@ def is_banned(request: Request): from aurweb.db import session ip = request.client.host return session.query(Ban).filter(Ban.IPAddress == ip).first() is not None - - -mapper(Ban, Bans) diff --git a/aurweb/models/declarative.py b/aurweb/models/declarative.py new file mode 100644 index 00000000..45a629ce --- /dev/null +++ b/aurweb/models/declarative.py @@ -0,0 +1,10 @@ +from sqlalchemy.ext.declarative import declarative_base + +import aurweb.db + +Base = declarative_base() +Base.__table_args__ = { + "autoload": True, + "autoload_with": aurweb.db.get_engine(), + "extend_existing": True +} diff --git a/aurweb/models/dependency_type.py b/aurweb/models/dependency_type.py index 87b38069..71acf368 100644 --- a/aurweb/models/dependency_type.py +++ b/aurweb/models/dependency_type.py @@ -1,11 +1,14 @@ -from sqlalchemy.orm import mapper +from sqlalchemy import Column, Integer -from aurweb.schema import DependencyTypes +from aurweb.models.declarative import Base -class DependencyType: +class DependencyType(Base): + __tablename__ = "DependencyTypes" + + ID = Column(Integer, primary_key=True) + + __mapper_args__ = {"primary_key": [ID]} + def __init__(self, Name: str = None): self.Name = Name - - -mapper(DependencyType, DependencyTypes) diff --git a/aurweb/models/group.py b/aurweb/models/group.py index c5583eb4..1bd3a402 100644 --- a/aurweb/models/group.py +++ b/aurweb/models/group.py @@ -1,10 +1,16 @@ +from sqlalchemy import Column, Integer from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import mapper -from aurweb.schema import Groups +from aurweb.models.declarative import Base -class Group: +class Group(Base): + __tablename__ = "Groups" + + ID = Column(Integer, primary_key=True) + + __mapper_args__ = {"primary_key": [ID]} + def __init__(self, Name: str = None): self.Name = Name if not self.Name: @@ -12,6 +18,3 @@ class Group: statement="Column Name cannot be null.", orig="Groups.Name", params=("NULL")) - - -mapper(Group, Groups) diff --git a/aurweb/models/license.py b/aurweb/models/license.py index bcc02713..aef6a619 100644 --- a/aurweb/models/license.py +++ b/aurweb/models/license.py @@ -1,10 +1,16 @@ +from sqlalchemy import Column, Integer from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import mapper -from aurweb.schema import Licenses +from aurweb.models.declarative import Base -class License: +class License(Base): + __tablename__ = "Licenses" + + ID = Column(Integer, primary_key=True) + + __mapper_args__ = {"primary_key": [ID]} + def __init__(self, Name: str = None): self.Name = Name if not self.Name: @@ -12,6 +18,3 @@ class License: statement="Column Name cannot be null.", orig="Licenses.Name", params=("NULL")) - - -mapper(License, Licenses) diff --git a/aurweb/models/official_provider.py b/aurweb/models/official_provider.py index 073eb435..756be843 100644 --- a/aurweb/models/official_provider.py +++ b/aurweb/models/official_provider.py @@ -1,10 +1,16 @@ +from sqlalchemy import Column, Integer from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import mapper -from aurweb.schema import OfficialProviders +from aurweb.models.declarative import Base -class OfficialProvider: +class OfficialProvider(Base): + __tablename__ = "OfficialProviders" + + ID = Column(Integer, primary_key=True) + + __mapper_args__ = {"primary_key": [ID]} + def __init__(self, Name: str = None, Repo: str = None, @@ -29,6 +35,3 @@ class OfficialProvider: statement="Column Provides cannot be null.", orig="OfficialProviders.Provides", params=("NULL")) - - -mapper(OfficialProvider, OfficialProviders) diff --git a/aurweb/models/package.py b/aurweb/models/package.py index 28a13791..ff518f20 100644 --- a/aurweb/models/package.py +++ b/aurweb/models/package.py @@ -1,20 +1,37 @@ +from sqlalchemy import Column, ForeignKey, Integer from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import mapper +from sqlalchemy.orm import backref, relationship -from aurweb.db import make_relationship -from aurweb.models.package_base import PackageBase -from aurweb.schema import Packages +import aurweb.db +import aurweb.models.package_base + +from aurweb.models.declarative import Base -class Package: +class Package(Base): + __tablename__ = "Packages" + + ID = Column(Integer, primary_key=True) + + PackageBaseID = Column( + Integer, ForeignKey("PackageBases.ID", ondelete="CASCADE"), + nullable=False) + PackageBase = relationship( + "PackageBase", backref=backref("package", uselist=False), + foreign_keys=[PackageBaseID]) + + __mapper_args__ = {"primary_key": [ID]} + def __init__(self, - PackageBase: PackageBase = None, - Name: str = None, Version: str = None, - Description: str = None, URL: str = None): + PackageBase: aurweb.models.package_base.PackageBase = None, + Name: str = None, + Version: str = None, + Description: str = None, + URL: str = None): self.PackageBase = PackageBase if not self.PackageBase: raise IntegrityError( - statement="Foreign key UserID cannot be null.", + statement="Foreign key PackageBaseID cannot be null.", orig="Packages.PackageBaseID", params=("NULL")) @@ -28,10 +45,3 @@ class Package: self.Version = Version self.Description = Description self.URL = URL - - -mapper(Package, Packages, properties={ - "PackageBase": make_relationship(PackageBase, - Packages.c.PackageBaseID, - "package", uselist=False) -}) diff --git a/aurweb/models/package_base.py b/aurweb/models/package_base.py index 699559d5..261c30f3 100644 --- a/aurweb/models/package_base.py +++ b/aurweb/models/package_base.py @@ -1,17 +1,47 @@ from datetime import datetime +from sqlalchemy import Column, ForeignKey, Integer from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import mapper +from sqlalchemy.orm import backref, relationship -from aurweb.db import make_relationship -from aurweb.models.user import User -from aurweb.schema import PackageBases +import aurweb.models.user + +from aurweb.models.declarative import Base -class PackageBase: - def __init__(self, Name: str = None, Flagger: User = None, - Maintainer: User = None, Submitter: User = None, - Packager: User = None, **kwargs): +class PackageBase(Base): + __tablename__ = "PackageBases" + + FlaggerUID = Column(Integer, + ForeignKey("Users.ID", ondelete="SET NULL")) + Flagger = relationship( + "User", backref=backref("flagged_bases", lazy="dynamic"), + foreign_keys=[FlaggerUID]) + + SubmitterUID = Column(Integer, + ForeignKey("Users.ID", ondelete="SET NULL")) + Submitter = relationship( + "User", backref=backref("submitted_bases", lazy="dynamic"), + foreign_keys=[SubmitterUID]) + + MaintainerUID = Column(Integer, + ForeignKey("Users.ID", ondelete="SET NULL")) + Maintainer = relationship( + "User", backref=backref("maintained_bases", lazy="dynamic"), + foreign_keys=[MaintainerUID]) + + PackagerUID = Column(Integer, ForeignKey("Users.ID", ondelete="SET NULL")) + Packager = relationship( + "User", backref=backref("package_bases", lazy="dynamic"), + foreign_keys=[PackagerUID]) + + def __init__(self, Name: str = None, + Flagger: aurweb.models.user.User = None, + Maintainer: aurweb.models.user.User = None, + Submitter: aurweb.models.user.User = None, + Packager: aurweb.models.user.User = None, + **kwargs): + super().__init__(**kwargs) self.Name = Name if not self.Name: raise IntegrityError( @@ -32,15 +62,3 @@ class PackageBase: datetime.utcnow().timestamp()) self.ModifiedTS = kwargs.get("ModifiedTS", datetime.utcnow().timestamp()) - - -mapper(PackageBase, PackageBases, properties={ - "Flagger": make_relationship(User, PackageBases.c.FlaggerUID, - "flagged_bases"), - "Submitter": make_relationship(User, PackageBases.c.SubmitterUID, - "submitted_bases"), - "Maintainer": make_relationship(User, PackageBases.c.MaintainerUID, - "maintained_bases"), - "Packager": make_relationship(User, PackageBases.c.PackagerUID, - "package_bases") -}) diff --git a/aurweb/models/package_dependency.py b/aurweb/models/package_dependency.py index 21801802..0bd84073 100644 --- a/aurweb/models/package_dependency.py +++ b/aurweb/models/package_dependency.py @@ -1,17 +1,40 @@ +from sqlalchemy import Column, ForeignKey, Integer from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import mapper +from sqlalchemy.orm import backref, relationship -from aurweb.db import make_relationship -from aurweb.models.dependency_type import DependencyType -from aurweb.models.package import Package -from aurweb.schema import PackageDepends +import aurweb.models.package + +from aurweb.models import dependency_type +from aurweb.models.declarative import Base -class PackageDependency: - def __init__(self, Package: Package = None, - DependencyType: DependencyType = None, - DepName: str = None, DepDesc: str = None, - DepCondition: str = None, DepArch: str = None): +class PackageDependency(Base): + __tablename__ = "PackageDepends" + + PackageID = Column( + Integer, ForeignKey("Packages.ID", ondelete="CASCADE"), + nullable=False) + Package = relationship( + "Package", backref=backref("package_dependencies", lazy="dynamic"), + foreign_keys=[PackageID], lazy="select") + + DepTypeID = Column( + Integer, ForeignKey("DependencyTypes.ID", ondelete="NO ACTION"), + nullable=False) + DependencyType = relationship( + "DependencyType", + backref=backref("package_dependencies", lazy="dynamic"), + foreign_keys=[DepTypeID], lazy="select") + + __mapper_args__ = {"primary_key": [PackageID, DepTypeID]} + + def __init__(self, + Package: aurweb.models.package.Package = None, + DependencyType: dependency_type.DependencyType = None, + DepName: str = None, + DepDesc: str = None, + DepCondition: str = None, + DepArch: str = None): self.Package = Package if not self.Package: raise IntegrityError( @@ -36,15 +59,3 @@ class PackageDependency: self.DepDesc = DepDesc self.DepCondition = DepCondition self.DepArch = DepArch - - -properties = { - "Package": make_relationship(Package, PackageDepends.c.PackageID, - "package_dependencies"), - "DependencyType": make_relationship(DependencyType, - PackageDepends.c.DepTypeID, - "package_dependencies") -} - -mapper(PackageDependency, PackageDepends, properties=properties, - primary_key=[PackageDepends.c.PackageID, PackageDepends.c.DepTypeID]) diff --git a/aurweb/models/package_group.py b/aurweb/models/package_group.py index 19a11c80..a8031e0d 100644 --- a/aurweb/models/package_group.py +++ b/aurweb/models/package_group.py @@ -1,14 +1,33 @@ +from sqlalchemy import Column, ForeignKey, Integer from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import mapper +from sqlalchemy.orm import backref, relationship -from aurweb.db import make_relationship -from aurweb.models.group import Group -from aurweb.models.package import Package -from aurweb.schema import PackageGroups +import aurweb.models.group +import aurweb.models.package + +from aurweb.models.declarative import Base -class PackageGroup: - def __init__(self, Package: Package = None, Group: Group = None): +class PackageGroup(Base): + __tablename__ = "PackageGroups" + + PackageID = Column(Integer, ForeignKey("Packages.ID", ondelete="CASCADE"), + primary_key=True, nullable=True) + Package = relationship( + "Package", backref=backref("package_groups", lazy="dynamic"), + foreign_keys=[PackageID]) + + GroupID = Column(Integer, ForeignKey("Groups.ID", ondelete="CASCADE"), + primary_key=True, nullable=True) + Group = relationship( + "Group", backref=backref("package_groups", lazy="dynamic"), + foreign_keys=[GroupID]) + + __mapper_args__ = {"primary_key": [PackageID, GroupID]} + + def __init__(self, + Package: aurweb.models.package.Package = None, + Group: aurweb.models.group.Group = None): self.Package = Package if not self.Package: raise IntegrityError( @@ -22,18 +41,3 @@ class PackageGroup: statement="Primary key GroupID cannot be null.", orig="PackageGroups.GroupID", params=("NULL")) - - -properties = { - "Package": make_relationship(Package, - PackageGroups.c.PackageID, - "package_group", - uselist=False), - "Group": make_relationship(Group, - PackageGroups.c.GroupID, - "package_group", - uselist=False) -} - -mapper(PackageGroup, PackageGroups, properties=properties, - primary_key=[PackageGroups.c.PackageID, PackageGroups.c.GroupID]) diff --git a/aurweb/models/package_keyword.py b/aurweb/models/package_keyword.py index 2bae223c..2926740d 100644 --- a/aurweb/models/package_keyword.py +++ b/aurweb/models/package_keyword.py @@ -1,14 +1,27 @@ +from sqlalchemy import Column, ForeignKey, Integer from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import mapper +from sqlalchemy.orm import backref, relationship -from aurweb.db import make_relationship -from aurweb.models.package_base import PackageBase -from aurweb.schema import PackageKeywords +import aurweb.db +import aurweb.models.package_base + +from aurweb.models.declarative import Base -class PackageKeyword: +class PackageKeyword(Base): + __tablename__ = "PackageKeywords" + + PackageBaseID = Column( + Integer, ForeignKey("PackageBases.ID", ondelete="CASCADE"), + primary_key=True, nullable=True) + PackageBase = relationship( + "PackageBase", backref=backref("keywords", lazy="dynamic"), + foreign_keys=[PackageBaseID]) + + __mapper_args__ = {"primary_key": [PackageBaseID]} + def __init__(self, - PackageBase: PackageBase = None, + PackageBase: aurweb.models.package_base.PackageBase = None, Keyword: str = None): self.PackageBase = PackageBase if not self.PackageBase: @@ -18,10 +31,3 @@ class PackageKeyword: params=("NULL")) self.Keyword = Keyword - - -mapper(PackageKeyword, PackageKeywords, properties={ - "PackageBase": make_relationship(PackageBase, - PackageKeywords.c.PackageBaseID, - "keywords") -}) diff --git a/aurweb/models/package_license.py b/aurweb/models/package_license.py index 491874a4..0689562f 100644 --- a/aurweb/models/package_license.py +++ b/aurweb/models/package_license.py @@ -1,14 +1,35 @@ +from sqlalchemy import Column, ForeignKey, Integer from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import mapper +from sqlalchemy.orm import backref, relationship -from aurweb.db import make_relationship -from aurweb.models.license import License -from aurweb.models.package import Package -from aurweb.schema import PackageLicenses +import aurweb.models.license +import aurweb.models.package + +from aurweb.models.declarative import Base -class PackageLicense: - def __init__(self, Package: Package = None, License: License = None): +class PackageLicense(Base): + __tablename__ = "PackageLicenses" + + PackageID = Column( + Integer, ForeignKey("Packages.ID", ondelete="CASCADE"), + primary_key=True, nullable=True) + Package = relationship( + "Package", backref=backref("package_license", uselist=False), + foreign_keys=[PackageID]) + + LicenseID = Column( + Integer, ForeignKey("Licenses.ID", ondelete="CASCADE"), + primary_key=True, nullable=True) + License = relationship( + "License", backref=backref("package_license", uselist=False), + foreign_keys=[LicenseID]) + + __mapper_args__ = {"primary_key": [PackageID, LicenseID]} + + def __init__(self, + Package: aurweb.models.package.Package = None, + License: aurweb.models.license.License = None): self.Package = Package if not self.Package: raise IntegrityError( @@ -22,20 +43,3 @@ class PackageLicense: statement="Primary key LicenseID cannot be null.", orig="PackageLicenses.LicenseID", params=("NULL")) - - -properties = { - "Package": make_relationship(Package, - PackageLicenses.c.PackageID, - "package_license", - uselist=False), - "License": make_relationship(License, - PackageLicenses.c.LicenseID, - "package_license", - uselist=False) - - -} - -mapper(PackageLicense, PackageLicenses, properties=properties, - primary_key=[PackageLicenses.c.PackageID, PackageLicenses.c.LicenseID]) diff --git a/aurweb/models/package_relation.py b/aurweb/models/package_relation.py index d9ade727..9204af59 100644 --- a/aurweb/models/package_relation.py +++ b/aurweb/models/package_relation.py @@ -1,15 +1,36 @@ +from sqlalchemy import Column, ForeignKey, Integer from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import mapper +from sqlalchemy.orm import backref, relationship -from aurweb.db import make_relationship -from aurweb.models.package import Package -from aurweb.models.relation_type import RelationType -from aurweb.schema import PackageRelations +import aurweb.db +import aurweb.models.package +import aurweb.models.relation_type + +from aurweb.models.declarative import Base -class PackageRelation: - def __init__(self, Package: Package = None, - RelationType: RelationType = None, +class PackageRelation(Base): + __tablename__ = "PackageRelations" + + PackageID = Column( + Integer, ForeignKey("Packages.ID", ondelete="CASCADE"), + nullable=False) + Package = relationship( + "Package", backref=backref("package_relations", lazy="dynamic"), + foreign_keys=[PackageID]) + + RelTypeID = Column( + Integer, ForeignKey("RelationTypes.ID", ondelete="CASCADE"), + nullable=False) + RelationType = relationship( + "RelationType", backref=backref("package_relations", lazy="dynamic"), + foreign_keys=[RelTypeID]) + + __mapper_args__ = {"primary_key": [PackageID, RelTypeID]} + + def __init__(self, + Package: aurweb.models.package.Package = None, + RelationType: aurweb.models.relation_type.RelationType = None, RelName: str = None, RelCondition: str = None, RelArch: str = None): self.Package = Package @@ -35,18 +56,3 @@ class PackageRelation: self.RelCondition = RelCondition self.RelArch = RelArch - - -properties = { - "Package": make_relationship(Package, PackageRelations.c.PackageID, - "package_relations"), - "RelationType": make_relationship(RelationType, - PackageRelations.c.RelTypeID, - "package_relations") -} - -mapper(PackageRelation, PackageRelations, properties=properties, - primary_key=[ - PackageRelations.c.PackageID, - PackageRelations.c.RelTypeID - ]) diff --git a/aurweb/models/relation_type.py b/aurweb/models/relation_type.py index b4d1efbc..319fb7f4 100644 --- a/aurweb/models/relation_type.py +++ b/aurweb/models/relation_type.py @@ -1,11 +1,14 @@ -from sqlalchemy.orm import mapper +from sqlalchemy import Column, Integer -from aurweb.schema import RelationTypes +from aurweb.models.declarative import Base -class RelationType: +class RelationType(Base): + __tablename__ = "RelationTypes" + + ID = Column(Integer, primary_key=True) + + __mapper_args__ = {"primary_key": [ID]} + def __init__(self, Name: str = None): self.Name = Name - - -mapper(RelationType, RelationTypes) diff --git a/aurweb/models/session.py b/aurweb/models/session.py index f1e0fff5..9154178e 100644 --- a/aurweb/models/session.py +++ b/aurweb/models/session.py @@ -1,12 +1,24 @@ +from sqlalchemy import Column, ForeignKey, Integer from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import backref, mapper, relationship +from sqlalchemy.orm import backref, relationship from aurweb.db import make_random_value, query +from aurweb.models.declarative import Base from aurweb.models.user import User -from aurweb.schema import Sessions -class Session: +class Session(Base): + __tablename__ = "Sessions" + + UsersID = Column( + Integer, ForeignKey("Users.ID", ondelete="CASCADE"), + nullable=False) + User = relationship( + "User", backref=backref("session", uselist=False), + foreign_keys=[UsersID]) + + __mapper_args__ = {"primary_key": [UsersID]} + def __init__(self, **kwargs): self.UsersID = kwargs.get("UsersID") if not query(User, User.ID == self.UsersID).first(): @@ -19,11 +31,5 @@ class Session: self.LastUpdateTS = kwargs.get("LastUpdateTS") -mapper(Session, Sessions, primary_key=[Sessions.c.SessionID], properties={ - "User": relationship(User, backref=backref("session", - uselist=False)) -}) - - def generate_unique_sid(): return make_random_value(Session, Session.SessionID) diff --git a/aurweb/models/ssh_pub_key.py b/aurweb/models/ssh_pub_key.py index 01ff558e..268a585b 100644 --- a/aurweb/models/ssh_pub_key.py +++ b/aurweb/models/ssh_pub_key.py @@ -3,13 +3,26 @@ import tempfile from subprocess import PIPE, Popen -from sqlalchemy.orm import backref, mapper, relationship +from sqlalchemy import Column, ForeignKey, Integer, String +from sqlalchemy.orm import backref, relationship -from aurweb.models.user import User -from aurweb.schema import SSHPubKeys +from aurweb.models.declarative import Base -class SSHPubKey: +class SSHPubKey(Base): + __tablename__ = "SSHPubKeys" + + UserID = Column( + Integer, ForeignKey("Users.ID", ondelete="CASCADE"), + nullable=False) + User = relationship( + "User", backref=backref("ssh_pub_key", uselist=False), + foreign_keys=[UserID]) + + Fingerprint = Column(String(44), primary_key=True) + + __mapper_args__ = {"primary_key": Fingerprint} + def __init__(self, **kwargs): self.UserID = kwargs.get("UserID") self.Fingerprint = kwargs.get("Fingerprint") @@ -34,8 +47,3 @@ def get_fingerprint(pubkey): fp = parts[1].replace("SHA256:", "") return fp - - -mapper(SSHPubKey, SSHPubKeys, properties={ - "User": relationship(User, backref=backref("ssh_pub_key", uselist=False)) -}) diff --git a/aurweb/models/term.py b/aurweb/models/term.py index 1a0780df..b0da71f7 100644 --- a/aurweb/models/term.py +++ b/aurweb/models/term.py @@ -1,10 +1,16 @@ +from sqlalchemy import Column, Integer from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import mapper -from aurweb.schema import Terms +from aurweb.models.declarative import Base -class Term: +class Term(Base): + __tablename__ = "Terms" + + ID = Column(Integer, primary_key=True) + + __mapper_args__ = {"primary_key": [ID]} + def __init__(self, Description: str = None, URL: str = None, Revision: int = None): @@ -23,6 +29,3 @@ class Term: params=("NULL")) self.Revision = Revision - - -mapper(Term, Terms) diff --git a/aurweb/models/user.py b/aurweb/models/user.py index 1961228e..83cde5f1 100644 --- a/aurweb/models/user.py +++ b/aurweb/models/user.py @@ -5,50 +5,44 @@ from datetime import datetime import bcrypt from fastapi import Request -from sqlalchemy.orm import backref, mapper, relationship +from sqlalchemy import Column, ForeignKey, Integer, String, text +from sqlalchemy.orm import backref, relationship import aurweb.config +import aurweb.models.account_type +import aurweb.schema -from aurweb.models.account_type import AccountType from aurweb.models.ban import is_banned -from aurweb.schema import Users +from aurweb.models.declarative import Base -class User: +class User(Base): """ An ORM model of a single Users record. """ + __tablename__ = "Users" + + ID = Column(Integer, primary_key=True) + + AccountTypeID = Column( + Integer, ForeignKey("AccountTypes.ID", ondelete="NO ACTION"), + nullable=False, server_default=text("1")) + AccountType = relationship( + "AccountType", + backref=backref("users", lazy="dynamic"), + foreign_keys=[AccountTypeID], + uselist=False) + + Passwd = Column(String(255), default=str()) + + __mapper_args__ = {"primary_key": [ID]} + + # High-level variables used to track authentication (not in DB). authenticated = False - def __init__(self, **kwargs): - # Set AccountTypeID if it was passed. - self.AccountTypeID = kwargs.get("AccountTypeID") + def __init__(self, Passwd: str = str(), **kwargs): + super().__init__(**kwargs) - account_type = kwargs.get("AccountType") - if account_type: - self.AccountType = account_type - - self.Username = kwargs.get("Username") - - self.ResetKey = kwargs.get("ResetKey") - self.Email = kwargs.get("Email") - self.BackupEmail = kwargs.get("BackupEmail") - self.RealName = kwargs.get("RealName") - self.LangPreference = kwargs.get("LangPreference") - self.Timezone = kwargs.get("Timezone") - self.Homepage = kwargs.get("Homepage") - self.IRCNick = kwargs.get("IRCNick") - self.PGPKey = kwargs.get("PGPKey") - self.RegistrationTS = datetime.utcnow() - self.CommentNotify = kwargs.get("CommentNotify") - self.UpdateNotify = kwargs.get("UpdateNotify") - self.OwnershipNotify = kwargs.get("OwnershipNotify") - self.SSOAccountID = kwargs.get("SSOAccountID") - - self.Salt = None - self.Passwd = str() - - passwd = kwargs.get("Passwd") - if passwd: - self.update_password(passwd) + if Passwd: + self.update_password(Passwd) def update_password(self, password, salt_rounds=12): self.Passwd = bcrypt.hashpw( @@ -154,10 +148,3 @@ class User: def __repr__(self): return "" % ( self.ID, str(self.AccountType), self.Username) - - -# Map schema.Users to User and give it some relationships. -mapper(User, Users, properties={ - "AccountType": relationship(AccountType, - backref=backref("users", lazy="dynamic")) -}) diff --git a/test/test_accepted_term.py b/test/test_accepted_term.py index 4ddf1fc3..8569b021 100644 --- a/test/test_accepted_term.py +++ b/test/test_accepted_term.py @@ -22,7 +22,7 @@ def setup(): AccountType.AccountType == "User").first() user = create(User, Username="test", Email="test@example.org", RealName="Test User", Passwd="testPassword", - account_type=account_type) + AccountType=account_type) term = create(Term, Description="Test term", URL="https://test.term") @@ -33,7 +33,7 @@ def test_accepted_term(): # Make sure our AcceptedTerm relationships got initialized properly. assert accepted_term.User == user assert accepted_term in user.accepted_terms - assert accepted_term in term.accepted + assert accepted_term in term.accepted_terms def test_accepted_term_null_user_raises_exception(): diff --git a/test/test_account_type.py b/test/test_account_type.py index 3bd76d1e..fa4bc5ad 100644 --- a/test/test_account_type.py +++ b/test/test_account_type.py @@ -43,6 +43,7 @@ def test_user_account_type_relationship(): AccountType=account_type) assert user.AccountType == account_type - assert account_type.users.filter(User.ID == user.ID).first() + # This must be deleted here to avoid foreign key issues when + # deleting the temporary AccountType in the fixture. delete(User, User.ID == user.ID) diff --git a/test/test_package.py b/test/test_package.py index a994f096..9532823d 100644 --- a/test/test_package.py +++ b/test/test_package.py @@ -1,7 +1,7 @@ import pytest from sqlalchemy import and_ -from sqlalchemy.exc import IntegrityError +from sqlalchemy.exc import IntegrityError, OperationalError import aurweb.config @@ -19,7 +19,7 @@ user = pkgbase = package = None def setup(): global user, pkgbase, package - setup_test_db("Users", "PackageBases", "Packages") + setup_test_db("Packages", "PackageBases", "Users") account_type = query(AccountType, AccountType.AccountType == "User").first() @@ -57,17 +57,17 @@ def test_package(): assert record is not None -def test_package_ci(): +def test_package_package_base_cant_change(): """ Test case insensitivity of the database table. """ if aurweb.config.get("database", "backend") == "sqlite": return None # SQLite doesn't seem handle this. from aurweb.db import session - with pytest.raises(IntegrityError): + with pytest.raises(OperationalError): create(Package, PackageBase=pkgbase, - Name="Beautiful-Package") + Name="invalidates-old-package-packagebase-relationship") session.rollback() diff --git a/test/test_package_group.py b/test/test_package_group.py index 28047a7f..0e6e41e3 100644 --- a/test/test_package_group.py +++ b/test/test_package_group.py @@ -25,7 +25,7 @@ def setup(): AccountType.AccountType == "User").first() user = create(User, Username="test", Email="test@example.org", RealName="Test User", Passwd="testPassword", - account_type=account_type) + AccountType=account_type) group = create(Group, Name="Test Group") pkgbase = create(PackageBase, Name="test-package", Maintainer=user) diff --git a/test/test_package_license.py b/test/test_package_license.py index 72eb3681..f7654dee 100644 --- a/test/test_package_license.py +++ b/test/test_package_license.py @@ -25,7 +25,7 @@ def setup(): AccountType.AccountType == "User").first() user = create(User, Username="test", Email="test@example.org", RealName="Test User", Passwd="testPassword", - account_type=account_type) + AccountType=account_type) license = create(License, Name="Test License") pkgbase = create(PackageBase, Name="test-package", Maintainer=user) diff --git a/test/test_session.py b/test/test_session.py index 1dd82db1..1ba11556 100644 --- a/test/test_session.py +++ b/test/test_session.py @@ -10,12 +10,12 @@ from aurweb.models.session import Session, generate_unique_sid from aurweb.models.user import User from aurweb.testing import setup_test_db -user = session = None +account_type = user = session = None @pytest.fixture(autouse=True) def setup(): - global user, session + global account_type, user, session setup_test_db("Users", "Sessions") @@ -35,7 +35,10 @@ def test_session(): def test_session_cs(): """ Test case sensitivity of the database table. """ - session_cs = create(Session, UsersID=user.ID, + user2 = create(User, Username="test2", Email="test2@example.org", + ResetKey="testReset2", Passwd="testPassword", + AccountType=account_type) + session_cs = create(Session, UsersID=user2.ID, SessionID="TESTSESSION", LastUpdateTS=datetime.utcnow().timestamp()) assert session_cs.SessionID == "TESTSESSION"