• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

andgineer / api-db-prototype / 8766121206

20 Apr 2024 03:45PM UTC coverage: 83.956% (-2.9%) from 86.892%
8766121206

push

github

andgineer
upgrade reqs

2 of 2 new or added lines in 2 files covered. (100.0%)

89 existing lines in 23 files now uncovered.

1057 of 1259 relevant lines covered (83.96%)

0.84 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.18
/src/db/models.py
1
from typing import Any, Dict
1✔
2

3
from sqlalchemy import Column, DateTime, Enum, ForeignKey, Integer, String, Table, event, func
1✔
4
from sqlalchemy.orm import attributes, declarative_base, relationship
1✔
5
from sqlalchemy.orm.base import NEVER_SET, NO_VALUE
1✔
6
from sqlalchemy.orm.query import Query
1✔
7

8
import db.conn
1✔
9
import password_hash
1✔
10
from controllers.models import APIError, UserGroup
1✔
11
from journaling import log
1✔
12

13

14
class ORMClass:
1✔
15
    """Base class for all ORM classes."""
16

17
    @classmethod
1✔
18
    def query(cls) -> Query[Any]:
1✔
19
        """Return query for this class."""
20
        return db.conn.session.query(cls)  # type: ignore  # mypy bug
1✔
21

22
    @classmethod
1✔
23
    def by_id(cls, id: str, check: bool = True) -> Any:
1✔
24
        """Find object by ID.
25

26
        If `check` then raise exception if not found.
27
        """
28
        result = cls.query().filter(cls.id == id).first()  # type: ignore  # we manually add id field to all models
1✔
29
        if not result:
1✔
30
            if check:
1✔
31
                raise APIError(f'There is no {cls.__name__} with id="{id}"')
×
32
            log.debug(f'{cls.__name__} with id="{id}" was not found')
1✔
33
        return result
1✔
34

35

36
Base = declarative_base(cls=ORMClass)
1✔
37

38

39
# Many-to-many relationship
40
projects_collaborators = Table(
1✔
41
    "projects_collaborators",
42
    Base.metadata,
43
    Column("project_id", Integer, ForeignKey("projects.id"), primary_key=True),
44
    Column("user_id", Integer, ForeignKey("users.id"), primary_key=True),
45
)
46

47

48
class Project(Base):  # type: ignore
1✔
49
    """Project model."""
50

51
    __tablename__ = "projects"
1✔
52
    id = Column(Integer, primary_key=True)
1✔
53
    name = Column(String(80), unique=True, nullable=False, comment="Project name")
1✔
54
    created = Column(
1✔
55
        DateTime(timezone=True),
56
        default=func.now(),  # pylint: disable=not-callable
57
        comment="Project creation time",
58
    )
59
    author_id = Column(Integer, ForeignKey("users.id"), comment="Project author")
1✔
60

61
    # we do not use 'backref' feature of SQLAlchemy but duplicate relationships
62
    # on both sides, because we want all this fields visible in auto-completion in IDE
63
    author = relationship(
1✔
64
        "User",
65
        foreign_keys=[author_id],
66
        back_populates="own_projects",
67
    )
68

69
    # all users that can see that project - author and who you add to it manually
70
    # author of the project added automatically
71
    collaborators = relationship(
1✔
72
        "User", secondary=projects_collaborators, back_populates="projects", lazy="dynamic"
73
    )
74

75
    def __repr__(self) -> str:
1✔
76
        """Return string representation of the object."""
77
        return f"name: {self.name}, id: {self.id}"
×
78

79

80
class User(Base):  # type: ignore
1✔
81
    """User model."""
82

83
    __tablename__ = "users"
1✔
84
    createdDatetime = Column(
1✔
85
        "created_datetime",
86
        DateTime(timezone=True),
87
        default=func.now(),  # pylint: disable=not-callable
88
    )
89
    name = Column(String(120), comment="User name")
1✔
90
    id = Column(Integer, primary_key=True)
1✔
91
    group = Column(  # type: ignore
1✔
92
        Enum(
93
            UserGroup,
94
            values_callable=lambda x: [e.value for e in x],  # to use enum values instead of names
95
            native_enum=False,
96
        ),
97
        comment="User group",
98
    )
99
    email = Column(String(120), unique=True, nullable=False)
1✔
100
    password_hash = Column(String(300))
1✔
101

102
    # all projects that this user can see - his own and where he added to collaborators
103
    own_projects = relationship("Project", back_populates="author", lazy="dynamic")
1✔
104
    projects = relationship(
1✔
105
        "Project", back_populates="collaborators", secondary=projects_collaborators, lazy="dynamic"
106
    )
107

108
    @property
1✔
109
    def password(self) -> str:
1✔
110
        """Password getter."""
111
        raise NotImplementedError("Password getter")
×
112

113
    @password.setter
1✔
114
    def password(self, value: str) -> None:
1✔
115
        """For hybrid_property in setter we should check.
116

117
        not isinstance(self._scaffold_smiles, sqlalchemy.orm.attributes.InstrumentedAttribute)
118
        """
119
        self.password_hash = password_hash.hash(value)  # type: ignore
1✔
120

121
    @staticmethod
1✔
122
    def by_email(email: str, check: bool = True) -> "User":
1✔
123
        """Find user by email.
124

125
        If `check` then raise exception if not found.
126
        """
127
        user = User.query().filter(func.lower(db.models.User.email) == email.lower()).first()
1✔
128
        if not user:
1✔
129
            if check:
1✔
130
                raise APIError(f'There is no user with email "{email}"')
×
131
            log.debug(f'User with email "{email}" was not found')
1✔
132
        return user  # type: ignore
1✔
133

134
    @property
1✔
135
    def as_dict(self) -> Dict[str, Any]:
1✔
136
        """Return dict representation of the object."""
UNCOV
137
        return {c.name: getattr(self, c.name) for c in self.__table__.columns}
×
138

139
    def __repr__(self) -> str:
1✔
140
        """Return string representation of the object."""
141
        return f"name: {self.name}, email: {self.email}, id: {self.id}"
1✔
142

143

144
@event.listens_for(Project.author, "set")
1✔
145
def project_author_set_listener(
1✔
146
    project: Project,
147
    author: User,
148
    old_author: User,
149
    initiator: attributes.Event,  # pylint: disable=unused-argument
150
) -> None:
151
    """Listen for Project.author set event."""
152
    author.projects.append(project)
1✔
153
    if old_author not in [NEVER_SET, NO_VALUE]:
1✔
UNCOV
154
        old_author.projects.remove(project)
×
155
    log.debug(
1✔
156
        f'{author.email}\'s own project "{project.name}" added also to her full list of projects'
157
    )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc