Coverage for pepdbagent/modules/user.py: 98%
81 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-01 22:07 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-01 22:07 +0000
1import logging
3from sqlalchemy import and_, delete, select
4from sqlalchemy.exc import IntegrityError
5from sqlalchemy.orm import Session
7from pepdbagent.const import PKG_NAME
8from pepdbagent.db_utils import BaseEngine, Projects, Stars, User
9from pepdbagent.exceptions import (
10 ProjectAlreadyInFavorites,
11 ProjectNotInFavorites,
12 UserNotFoundError,
13)
14from pepdbagent.models import AnnotationList, AnnotationModel
16_LOGGER = logging.getLogger(PKG_NAME)
19class PEPDatabaseUser:
20 """
21 Class that represents Project in Database.
23 While using this class, user can create, retrieve, delete, and update projects from database
24 """
26 def __init__(self, pep_db_engine: BaseEngine):
27 """
28 Args:
29 pep_db_engine: PEPDatabaseAgent engine object.
30 """
31 self._sa_engine = pep_db_engine.engine
32 self._pep_db_engine = pep_db_engine
34 def create_user(self, namespace: str) -> int:
35 """Create a new user.
37 Args:
38 namespace: User namespace.
40 Returns:
41 New user id.
42 """
43 new_user_raw = User(namespace=namespace)
45 with Session(self._sa_engine) as session:
46 session.add(new_user_raw)
47 session.commit()
48 user_id = new_user_raw.id
49 return user_id
51 def get_user_id(self, namespace: str) -> int | None:
52 """Get user id by namespace.
54 Args:
55 namespace: User namespace.
57 Returns:
58 User id, or None if the user does not exist.
59 """
60 statement = select(User.id).where(User.namespace == namespace)
61 with Session(self._sa_engine) as session:
62 result = session.execute(statement).one_or_none()
64 if result:
65 return result[0]
66 return None
68 def add_project_to_favorites(
69 self,
70 namespace: str,
71 project_namespace: str,
72 project_name: str,
73 project_tag: str,
74 ) -> None:
75 """Add a project to a user's favorites.
77 Args:
78 namespace: Namespace of the user.
79 project_namespace: Namespace of the project.
80 project_name: Name of the project.
81 project_tag: Tag of the project.
82 """
84 user_id = self.get_user_id(namespace)
86 if not user_id:
87 user_id = self.create_user(namespace)
89 try:
90 with Session(self._sa_engine) as session:
91 project_mapping = session.scalar(
92 select(Projects).where(
93 and_(
94 Projects.namespace == project_namespace,
95 Projects.name == project_name,
96 Projects.tag == project_tag,
97 )
98 )
99 )
101 new_favorites_raw = Stars(
102 user_id=user_id, project_id=project_mapping.id
103 )
105 session.add(new_favorites_raw)
106 project_mapping.number_of_stars += 1
107 session.commit()
108 except IntegrityError:
109 raise ProjectAlreadyInFavorites()
110 return None
112 def remove_project_from_favorites(
113 self,
114 namespace: str,
115 project_namespace: str,
116 project_name: str,
117 project_tag: str,
118 ) -> None:
119 """Remove a project from a user's favorites.
121 Args:
122 namespace: Namespace of the user.
123 project_namespace: Namespace of the project.
124 project_name: Name of the project.
125 project_tag: Tag of the project.
126 """
127 _LOGGER.debug(
128 f"Removing project {project_namespace}/{project_name}:{project_tag} from favorites in {namespace}"
129 )
131 user_id = self.get_user_id(namespace)
133 with Session(self._sa_engine) as session:
134 project_mapping = session.scalar(
135 select(Projects).where(
136 and_(
137 Projects.namespace == project_namespace,
138 Projects.name == project_name,
139 Projects.tag == project_tag,
140 )
141 )
142 )
143 delete_statement = delete(Stars).where(
144 and_(
145 Stars.user_id == user_id,
146 Stars.project_id == project_mapping.id,
147 )
148 )
149 project_mapping.number_of_stars -= 1
150 result = session.execute(delete_statement)
151 session.commit()
152 row_count = result.rowcount
153 if row_count == 0:
154 raise ProjectNotInFavorites(
155 f"Project {project_namespace}/{project_name}:{project_tag} is not in favorites for user {namespace}"
156 )
157 return None
159 def get_favorites(self, namespace: str) -> AnnotationList:
160 """Get list of favorite projects for a user.
162 Args:
163 namespace: Namespace of the user.
165 Returns:
166 AnnotationList of favorite projects.
167 """
168 _LOGGER.debug(f"Getting favorites for user {namespace}")
169 if not self.exists(namespace):
170 return AnnotationList(
171 count=0,
172 limit=0,
173 offset=0,
174 results=[],
175 )
176 statement = select(User).where(User.namespace == namespace)
177 with Session(self._sa_engine) as session:
178 query_result = session.scalar(statement)
179 number_of_projects = len(
180 [kk.project_mapping for kk in query_result.stars_mapping]
181 )
182 project_list = []
183 for prj_list in query_result.stars_mapping:
184 prj = prj_list.project_mapping
185 project_list.append(
186 AnnotationModel(
187 namespace=prj.namespace,
188 name=prj.name,
189 tag=prj.tag,
190 is_private=prj.private,
191 number_of_samples=prj.number_of_samples,
192 description=prj.description,
193 last_update_date=str(prj.last_update_date),
194 submission_date=str(prj.submission_date),
195 digest=prj.digest,
196 pep_schema=(
197 f"{prj.schema_mapping.schema_mapping.namespace}/{prj.schema_mapping.schema_mapping.name}:{prj.schema_mapping.version}"
198 if prj.schema_mapping
199 else None
200 ),
201 pop=prj.pop,
202 stars_number=prj.number_of_stars,
203 forked_from=(
204 f"{prj.forked_from_mapping.namespace}/{prj.forked_from_mapping.name}:{prj.forked_from_mapping.tag}"
205 if prj.forked_from_mapping
206 else None
207 ),
208 )
209 )
210 favorite_prj = AnnotationList(
211 count=number_of_projects,
212 limit=number_of_projects,
213 offset=0,
214 results=project_list,
215 )
216 return favorite_prj
218 def exists(
219 self,
220 namespace: str,
221 ) -> bool:
222 """Check if a user exists in the database.
224 Args:
225 namespace: User namespace.
227 Returns:
228 True if the user exists.
229 """
231 statement = select(User.id)
232 statement = statement.where(
233 and_(
234 User.namespace == namespace,
235 )
236 )
237 found_prj = self._pep_db_engine.session_execute(statement).all()
239 if len(found_prj) > 0:
240 return True
241 else:
242 return False
244 def delete(self, namespace: str) -> None:
245 """Delete a user and all related data from the database.
247 Args:
248 namespace: User namespace.
249 """
250 if not self.exists(namespace):
251 raise UserNotFoundError
253 with Session(self._sa_engine) as session:
254 session.execute(delete(User).where(User.namespace == namespace))
255 session.commit()