cornflow 1.2.0a1__py3-none-any.whl → 1.2.0a2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -19,5 +19,5 @@ STATE_COLORS = {
19
19
  from airflow.www.utils import UIAlert
20
20
 
21
21
  DASHBOARD_UIALERTS = [
22
- UIAlert("Welcome! This is the backend of your Cornflow environment. Airflow™ is a platform created by the community to programmatically author, schedule and monitor workflows."),
22
+ UIAlert("Welcome! This is the backend of your cornflow environment. Airflow™ is a platform created by the community to programmatically author, schedule and monitor workflows."),
23
23
  ]
@@ -3,7 +3,7 @@ import os.path
3
3
 
4
4
  import click
5
5
  from cornflow.shared import db
6
- from flask_migrate import Migrate, migrate, upgrade, init
6
+ from flask_migrate import Migrate, migrate, upgrade, downgrade, init
7
7
 
8
8
  from .utils import get_app
9
9
 
@@ -28,7 +28,27 @@ def migrate_migrations():
28
28
 
29
29
 
30
30
  @migrations.command(name="upgrade", help="Apply migrations")
31
- def upgrade_migrations():
31
+ @click.option(
32
+ "-r", "--revision", type=str, help="The revision to upgrade to", default="head"
33
+ )
34
+ def upgrade_migrations(revision="head"):
35
+ app = get_app()
36
+ external = int(os.getenv("EXTERNAL_APP", 0))
37
+ if external == 0:
38
+ path = "./cornflow/migrations"
39
+ else:
40
+ path = f"./{os.getenv('EXTERNAL_APP_MODULE', 'external_app')}/migrations"
41
+
42
+ with app.app_context():
43
+ migration_client = Migrate(app=app, db=db, directory=path)
44
+ upgrade(revision=revision)
45
+
46
+
47
+ @migrations.command(name="downgrade", help="Downgrade migrations")
48
+ @click.option(
49
+ "-r", "--revision", type=str, help="The revision to downgrade to", default="-1"
50
+ )
51
+ def downgrade_migrations(revision="-1"):
32
52
  app = get_app()
33
53
  external = int(os.getenv("EXTERNAL_APP", 0))
34
54
  if external == 0:
@@ -38,7 +58,7 @@ def upgrade_migrations():
38
58
 
39
59
  with app.app_context():
40
60
  migration_client = Migrate(app=app, db=db, directory=path)
41
- upgrade()
61
+ downgrade(revision=revision)
42
62
 
43
63
 
44
64
  @migrations.command(
cornflow/cli/service.py CHANGED
@@ -6,6 +6,7 @@ from logging import error
6
6
 
7
7
 
8
8
  import click
9
+ from .utils import get_db_conn
9
10
  import cornflow
10
11
  from cornflow.app import create_app
11
12
  from cornflow.commands import (
@@ -56,15 +57,8 @@ def init_cornflow_service():
56
57
  os.environ["SECRET_KEY"] = os.getenv("FERNET_KEY", Fernet.generate_key().decode())
57
58
 
58
59
  # Cornflow db defaults
59
- cornflow_db_host = os.getenv("CORNFLOW_DB_HOST", "cornflow_db")
60
- cornflow_db_port = os.getenv("CORNFLOW_DB_PORT", "5432")
61
- cornflow_db_user = os.getenv("CORNFLOW_DB_USER", "cornflow")
62
- cornflow_db_password = os.getenv("CORNFLOW_DB_PASSWORD", "cornflow")
63
- cornflow_db = os.getenv("CORNFLOW_DB", "cornflow")
64
- cornflow_db_conn = os.getenv(
65
- "cornflow_db_conn",
66
- f"postgresql://{cornflow_db_user}:{cornflow_db_password}@{cornflow_db_host}:{cornflow_db_port}/{cornflow_db}",
67
- )
60
+ os.environ["DEFAULT_POSTGRES"] = "1"
61
+ cornflow_db_conn = get_db_conn()
68
62
  os.environ["DATABASE_URL"] = cornflow_db_conn
69
63
 
70
64
  # Platform auth config and service users
cornflow/cli/utils.py CHANGED
@@ -6,7 +6,7 @@ import warnings
6
6
 
7
7
  def get_app():
8
8
  env = os.getenv("FLASK_ENV", "development")
9
- data_conn = os.getenv("DATABASE_URL", "sqlite:///cornflow.db")
9
+ data_conn = get_db_conn()
10
10
  if env == "production":
11
11
  warnings.filterwarnings("ignore")
12
12
  external = int(os.getenv("EXTERNAL_APP", 0))
@@ -24,3 +24,18 @@ def get_app():
24
24
  app = create_app(env, data_conn)
25
25
 
26
26
  return app
27
+
28
+
29
+ def get_db_conn():
30
+ if int(os.getenv("DEFAULT_POSTGRES", 0)) == 0:
31
+ return os.getenv("DATABASE_URL", "sqlite:///cornflow.db")
32
+ else:
33
+ cornflow_db_host = os.getenv("CORNFLOW_DB_HOST", "cornflow_db")
34
+ cornflow_db_port = os.getenv("CORNFLOW_DB_PORT", "5432")
35
+ cornflow_db_user = os.getenv("CORNFLOW_DB_USER", "cornflow")
36
+ cornflow_db_password = os.getenv("CORNFLOW_DB_PASSWORD", "cornflow")
37
+ cornflow_db = os.getenv("CORNFLOW_DB", "cornflow")
38
+ return os.getenv(
39
+ "cornflow_db_conn",
40
+ f"postgresql://{cornflow_db_user}:{cornflow_db_password}@{cornflow_db_host}:{cornflow_db_port}/{cornflow_db}",
41
+ )
cornflow/config.py CHANGED
@@ -65,7 +65,7 @@ class DefaultConfig(object):
65
65
 
66
66
  # APISPEC:
67
67
  APISPEC_SPEC = APISpec(
68
- title="Cornflow API docs",
68
+ title="cornflow API docs",
69
69
  version="v1",
70
70
  plugins=[MarshmallowPlugin()],
71
71
  openapi_version="2.0.0",
@@ -3,8 +3,9 @@ Initialization file for the endpoints module
3
3
  All references to endpoints should be imported from here
4
4
  The login resource gets created on app startup as it depends on configuration
5
5
  """
6
+
6
7
  from .action import ActionListEndpoint
7
- from .alarms import AlarmsEndpoint
8
+ from .alarms import AlarmsEndpoint, AlarmDetailEndpoint
8
9
  from .apiview import ApiViewListEndpoint
9
10
  from .case import (
10
11
  CaseEndpoint,
@@ -225,6 +226,11 @@ alarms_resources = [
225
226
  urls="/alarms/",
226
227
  endpoint="alarms",
227
228
  ),
229
+ dict(
230
+ resource=AlarmDetailEndpoint,
231
+ urls="/alarms/<int:idx>/",
232
+ endpoint="alarms-detail",
233
+ ),
228
234
  dict(
229
235
  resource=MainAlarmsEndpoint,
230
236
  urls="/main-alarms/",
@@ -1,15 +1,19 @@
1
1
  # Imports from libraries
2
+ from flask import current_app
2
3
  from flask_apispec import doc, marshal_with, use_kwargs
3
4
 
4
5
  # Import from internal modules
5
6
  from cornflow.endpoints.meta_resource import BaseMetaResource
6
7
  from cornflow.models import AlarmsModel
7
8
  from cornflow.schemas.alarms import (
9
+ AlarmEditRequest,
8
10
  AlarmsResponse,
9
11
  AlarmsPostRequest,
10
- QueryFiltersAlarms
12
+ QueryFiltersAlarms,
11
13
  )
12
14
  from cornflow.shared.authentication import Auth, authenticate
15
+ from cornflow.shared.exceptions import AirflowError, ObjectDoesNotExist, InvalidData
16
+ from cornflow.shared.const import SERVICE_ROLE
13
17
 
14
18
 
15
19
  class AlarmsEndpoint(BaseMetaResource):
@@ -56,4 +60,64 @@ class AlarmsEndpoint(BaseMetaResource):
56
60
  and an integer with the HTTP status code.
57
61
  :rtype: Tuple(dict, integer)
58
62
  """
59
- return self.post_list(data=kwargs)
63
+ return self.post_list(data=kwargs)
64
+
65
+
66
+ class AlarmDetailEndpointBase(BaseMetaResource):
67
+ """
68
+ Endpoint used to get the information of a certain alarm. But not the data!
69
+ """
70
+
71
+ def __init__(self):
72
+ super().__init__()
73
+ self.data_model = AlarmsModel
74
+ self.unique = ["id"]
75
+
76
+
77
+ class AlarmDetailEndpoint(AlarmDetailEndpointBase):
78
+ @doc(description="Get details of an alarm", tags=["None"], inherit=False)
79
+ @authenticate(auth_class=Auth())
80
+ @marshal_with(AlarmsResponse)
81
+ @BaseMetaResource.get_data_or_404
82
+ def get(self, idx):
83
+ """
84
+ API method to get an execution created by the user and its related info.
85
+ It requires authentication to be passed in the form of a token that has to be linked to
86
+ an existing session (login) made by a user.
87
+
88
+ :param str idx: ID of the execution.
89
+ :return: A dictionary with a message (error if authentication failed, or the execution does not exist or
90
+ the data of the execution) and an integer with the HTTP status code.
91
+ :rtype: Tuple(dict, integer)
92
+ """
93
+ current_app.logger.info(
94
+ f"User {self.get_user()} gets details of execution {idx}"
95
+ )
96
+ return self.get_detail(idx=idx)
97
+
98
+ @doc(description="Edit an execution", tags=["Executions"], inherit=False)
99
+ @authenticate(auth_class=Auth())
100
+ @use_kwargs(AlarmEditRequest, location="json")
101
+ def put(self, idx, **data):
102
+ """
103
+ Edit an existing alarm
104
+
105
+ :param string idx: ID of the alarm.
106
+ :return: A dictionary with a message (error if authentication failed, or the alarm does not exist or
107
+ a message) and an integer with the HTTP status code.
108
+ :rtype: Tuple(dict, integer)
109
+ """
110
+ current_app.logger.info(f"User {self.get_user()} edits alarm {idx}")
111
+ return self.put_detail(data, track_user=False, idx=idx)
112
+
113
+ @doc(description="Disable an alarm", tags=["None"])
114
+ @authenticate(auth_class=Auth())
115
+ def delete(self, idx):
116
+ """
117
+ :param int alarm_id: Alarm id.
118
+ :return:
119
+ :rtype: Tuple(dict, integer)
120
+ """
121
+
122
+ current_app.logger.info(f"Alarm {idx} was disabled by user {self.get_user()}")
123
+ return self.disable_detail(idx=idx)
@@ -2,15 +2,16 @@
2
2
  External endpoint for the user to log in to the cornflow webserver
3
3
  """
4
4
 
5
+ from datetime import datetime, timezone, timedelta
6
+
5
7
  # Partial imports
6
8
  from flask import current_app, request
7
9
  from flask_apispec import use_kwargs, doc
8
10
  from sqlalchemy.exc import IntegrityError, DBAPIError
9
- from datetime import datetime, timedelta
10
11
 
11
12
  # Import from internal modules
12
13
  from cornflow.endpoints.meta_resource import BaseMetaResource
13
- from cornflow.models import UserModel, UserRoleModel
14
+ from cornflow.models import UserModel, UserRoleModel, PermissionsDAG
14
15
  from cornflow.schemas.user import LoginEndpointRequest, LoginOpenAuthRequest
15
16
  from cornflow.shared import db
16
17
  from cornflow.shared.authentication import Auth, LDAPBase
@@ -51,22 +52,35 @@ class LoginBaseEndpoint(BaseMetaResource):
51
52
  if auth_type == AUTH_DB:
52
53
  user = self.auth_db_authenticate(**kwargs)
53
54
  response.update({"change_password": check_last_password_change(user)})
54
- current_app.logger.info(f"User {user.id} logged in successfully using database authentication")
55
+ current_app.logger.info(
56
+ f"User {user.id} logged in successfully using database authentication"
57
+ )
55
58
  elif auth_type == AUTH_LDAP:
56
59
  user = self.auth_ldap_authenticate(**kwargs)
57
- current_app.logger.info(f"User {user.id} logged in successfully using LDAP authentication")
60
+ current_app.logger.info(
61
+ f"User {user.id} logged in successfully using LDAP authentication"
62
+ )
58
63
  elif auth_type == AUTH_OID:
59
- if (kwargs.get('username') and kwargs.get('password')):
64
+ if kwargs.get("username") and kwargs.get("password"):
60
65
  if not current_app.config.get("SERVICE_USER_ALLOW_PASSWORD_LOGIN", 0):
61
- raise InvalidUsage("Must provide a token in Authorization header. Cannot log in with username and password", 400)
62
- user = self.auth_oid_authenticate(username=kwargs['username'], password=kwargs['password'])
63
- current_app.logger.info(f"Service user {user.id} logged in successfully using password")
66
+ raise InvalidUsage(
67
+ "Must provide a token in Authorization header. Cannot log in with username and password",
68
+ 400,
69
+ )
70
+ user = self.auth_oid_authenticate(
71
+ username=kwargs["username"], password=kwargs["password"]
72
+ )
73
+ current_app.logger.info(
74
+ f"Service user {user.id} logged in successfully using password"
75
+ )
64
76
  token = self.auth_class.generate_token(user.id)
65
77
  else:
66
78
  token = self.auth_class().get_token_from_header(request.headers)
67
79
  user = self.auth_oid_authenticate(token=token)
68
- current_app.logger.info(f"User {user.id} logged in successfully using OpenID authentication")
69
-
80
+ current_app.logger.info(
81
+ f"User {user.id} logged in successfully using OpenID authentication"
82
+ )
83
+
70
84
  response.update({"token": token, "id": user.id})
71
85
  return response, 200
72
86
  else:
@@ -147,7 +161,9 @@ class LoginBaseEndpoint(BaseMetaResource):
147
161
 
148
162
  return user
149
163
 
150
- def auth_oid_authenticate(self, token: str = None, username: str = None, password: str = None):
164
+ def auth_oid_authenticate(
165
+ self, token: str = None, username: str = None, password: str = None
166
+ ):
151
167
  """
152
168
  Method in charge of performing the authentication using OpenID Connect tokens.
153
169
  Supports any OIDC provider configured via provider_url.
@@ -158,33 +174,22 @@ class LoginBaseEndpoint(BaseMetaResource):
158
174
  :return: the user object, or it raises an error if it has not been possible to log in
159
175
  :rtype: :class:`UserModel`
160
176
  """
161
- print("[auth_oid_authenticate] Starting OpenID authentication")
162
177
  if token:
163
- print("[auth_oid_authenticate] Authenticating with token")
178
+
164
179
  decoded_token = self.auth_class().decode_token(token)
165
- print(f"[auth_oid_authenticate] Token decoded successfully: {decoded_token}")
166
-
167
- username = decoded_token.get('username')
168
- if not username:
169
- print("[auth_oid_authenticate] Missing username in token claims")
170
- raise InvalidCredentials(
171
- "Invalid token: missing username claim",
172
- log_txt="Token validation failed: missing username claim",
173
- status_code=400
174
- )
175
180
 
176
- print(f"[auth_oid_authenticate] Looking up user: {username}")
181
+ username = decoded_token.get("sub")
182
+
177
183
  user = self.data_model.get_one_object(username=username)
178
184
 
179
185
  if not user:
180
- print(f"[auth_oid_authenticate] Creating new user: {username}")
181
186
  current_app.logger.info(
182
187
  f"OpenID user {username} does not exist and is created"
183
188
  )
184
189
 
185
- email = decoded_token.get('email', f"{username}@cornflow.org")
186
- first_name = decoded_token.get('given_name', '')
187
- last_name = decoded_token.get('family_name', '')
190
+ email = decoded_token.get("email", f"{username}@cornflow.org")
191
+ first_name = decoded_token.get("given_name", "")
192
+ last_name = decoded_token.get("family_name", "")
188
193
 
189
194
  data = {
190
195
  "username": username,
@@ -203,13 +208,12 @@ class LoginBaseEndpoint(BaseMetaResource):
203
208
  }
204
209
  )
205
210
  user_role.save()
211
+ if int(current_app.config["OPEN_DEPLOYMENT"]) == 1:
212
+ PermissionsDAG.add_all_permissions_to_user(user.id)
206
213
 
207
214
  return user
208
215
 
209
- elif (
210
- username
211
- and password
212
- ):
216
+ elif username and password:
213
217
  user = self.auth_db_authenticate(username, password)
214
218
  if user.is_service_user():
215
219
  return user
@@ -221,17 +225,34 @@ class LoginBaseEndpoint(BaseMetaResource):
221
225
  def check_last_password_change(user):
222
226
  """
223
227
  Check if the user needs to change their password based on the password rotation time.
224
-
228
+
225
229
  :param user: The user object to check
226
230
  :return: True if password needs to be changed, False otherwise
227
231
  :rtype: bool
228
232
  """
229
233
  if user.pwd_last_change:
230
- if (
231
- user.pwd_last_change
232
- + timedelta(days=int(current_app.config["PWD_ROTATION_TIME"]))
233
- < datetime.utcnow()
234
- ):
234
+ # Handle the case where pwd_last_change is already a datetime object
235
+ if isinstance(user.pwd_last_change, datetime):
236
+ # If it's a naive datetime (no timezone info), make it timezone-aware
237
+ if user.pwd_last_change.tzinfo is None:
238
+ last_change = user.pwd_last_change.replace(tzinfo=timezone.utc)
239
+ else:
240
+ # Already timezone-aware
241
+ last_change = user.pwd_last_change
242
+ else:
243
+ # It's a timestamp (integer), convert to datetime
244
+ last_change = datetime.fromtimestamp(user.pwd_last_change, timezone.utc)
245
+
246
+ # Get current time with UTC timezone for proper comparison
247
+ current_time = datetime.now(timezone.utc)
248
+
249
+ # Calculate the expiration time based on the password rotation setting
250
+ expiration_time = last_change + timedelta(
251
+ days=int(current_app.config["PWD_ROTATION_TIME"])
252
+ )
253
+
254
+ # Compare the timezone-aware datetimes
255
+ if expiration_time < current_time:
235
256
  return True
236
257
  return False
237
258
 
@@ -13,6 +13,7 @@ from cornflow.shared.const import ALL_DEFAULT_ROLES
13
13
  from cornflow.shared.exceptions import InvalidUsage, ObjectDoesNotExist, NoPermission
14
14
 
15
15
 
16
+
16
17
  class BaseMetaResource(Resource, MethodResource):
17
18
  """
18
19
  The base resource from all methods inherit from.
@@ -175,11 +176,18 @@ class BaseMetaResource(Resource, MethodResource):
175
176
  METHODS USED FOR ACTIVATING / DISABLING RECORDS IN CASE WE DO NOT WANT TO DELETE THEM STRAIGHT AWAY
176
177
  """
177
178
 
178
- def disable_detail(self):
179
+ def disable_detail(self, idx):
179
180
  """
180
- Method not implemented yet
181
+ Method to DISABLE an object from the database
182
+
183
+ :param idx: the idx which identifies the object
184
+ :return: the object and a status code.
181
185
  """
182
- raise NotImplemented
186
+ row = self.data_model.query.get(idx)
187
+ if row is None:
188
+ raise ObjectDoesNotExist(f"Object with id {idx} not found.")
189
+ row.disable()
190
+ return {"message": "Object marked as disabled"}, 200
183
191
 
184
192
  def activate_detail(self, **kwargs):
185
193
  """
@@ -1,14 +1,13 @@
1
1
  """
2
2
 
3
3
  """
4
- # Import from libraries
4
+
5
5
  from flask import current_app
6
6
  from sqlalchemy import desc
7
7
  from sqlalchemy.dialects.postgresql import JSON
8
8
  from sqlalchemy.dialects.postgresql import TEXT
9
9
  from sqlalchemy.ext.declarative import declared_attr
10
10
 
11
- # Import from internal modules
12
11
  from cornflow.models.meta_models import TraceAttributesModel
13
12
  from cornflow.shared import db
14
13
  from cornflow.shared.utils import hash_json_256
@@ -39,7 +38,7 @@ class BaseDataModel(TraceAttributesModel):
39
38
 
40
39
  def __init__(self, data):
41
40
  self.user_id = data.get("user_id")
42
- self.data = data.get("data") or data.get("execution_results")
41
+ self.data = data.get("data")
43
42
  self.data_hash = hash_json_256(self.data)
44
43
  self.name = data.get("name")
45
44
  self.description = data.get("description")
@@ -50,23 +49,16 @@ class BaseDataModel(TraceAttributesModel):
50
49
  @classmethod
51
50
  def get_all_objects(
52
51
  cls,
53
- user,
54
52
  schema=None,
55
53
  creation_date_gte=None,
56
54
  creation_date_lte=None,
57
55
  deletion_date_gte=None,
58
56
  deletion_date_lte=None,
59
- id=None,
60
57
  update_date_gte=None,
61
58
  update_date_lte=None,
62
- user_name=None,
63
- last_name=None,
64
- email=None,
65
- role_id=None,
66
- url_rule=None,
67
- description=None,
68
59
  offset=0,
69
60
  limit=10,
61
+ user=None,
70
62
  ):
71
63
  """
72
64
  Query to get all objects from a user
@@ -77,13 +69,6 @@ class BaseDataModel(TraceAttributesModel):
77
69
  :param string creation_date_lte: created_at needs to be smaller or equal to this
78
70
  :param string deletion_date_gte: deletion_at needs to be larger or equal to this
79
71
  :param string deletion_date_lte: deletion_at needs to be smaller or equal to this
80
- :param string id: user id
81
- :param string user_name: user first name
82
- :param string last_name: user last name
83
- :param string email: user email
84
- :param int role_id: user role id
85
- :param string url_rule: url_rule
86
- :param string description: description
87
72
  :param string update_date_gte: update_at needs to be larger or equal to this
88
73
  :param string update_date_lte: update_at needs to be smaller or equal to this
89
74
  :param int offset: query offset for pagination
@@ -114,20 +99,7 @@ class BaseDataModel(TraceAttributesModel):
114
99
  if update_date_gte:
115
100
  query = query.filter(cls.update_at >= update_date_gte)
116
101
  if update_date_lte:
117
- query = query.filter(cls.updat_at <= update_date_lte)
118
- if user_name:
119
- query = query.filter(cls.user_name == user_name)
120
- if last_name:
121
- query = query.filter(cls.last_name == last_name)
122
- if email:
123
- query = query.filter(cls.email == email)
124
- if role_id:
125
- query = query.filter(cls.role_id == role_id)
126
- if url_rule:
127
- query = query.filter(cls.url_rule == url_rule)
128
- if description:
129
- query = query.filter(cls.description == description)
130
- # if airflow they also return total_entries = query.count(), for some reason
102
+ query = query.filter(cls.update_at <= update_date_lte)
131
103
 
132
104
  return query.order_by(desc(cls.created_at)).offset(offset).limit(limit).all()
133
105
 
@@ -1,13 +1,13 @@
1
1
  """
2
2
  This file contains the base abstract models from which the rest of the models inherit
3
3
  """
4
- # Imports from libraries
5
- from datetime import datetime
4
+
5
+ from datetime import datetime, timezone
6
+ from typing import Dict, List
7
+
6
8
  from flask import current_app
7
9
  from sqlalchemy.exc import DBAPIError, IntegrityError
8
- from typing import Dict, List
9
10
 
10
- # Imports from internal modules
11
11
  from cornflow.shared import db
12
12
  from cornflow.shared.exceptions import InvalidData
13
13
 
@@ -33,7 +33,9 @@ class EmptyBaseModel(db.Model):
33
33
 
34
34
  try:
35
35
  db.session.commit()
36
- current_app.logger.debug(f"Transaction type: {action}, performed correctly on {self}")
36
+ current_app.logger.debug(
37
+ f"Transaction type: {action}, performed correctly on {self}"
38
+ )
37
39
  except IntegrityError as err:
38
40
  db.session.rollback()
39
41
  current_app.logger.error(f"Integrity error on {action} data: {err}")
@@ -99,7 +101,10 @@ class EmptyBaseModel(db.Model):
99
101
  action = "bulk create"
100
102
  try:
101
103
  db.session.commit()
102
- current_app.logger.debug(f"Transaction type: {action}, performed correctly on {cls}")
104
+ current_app.logger.debug(
105
+ f"Transaction type: {action}, performed correctly on {cls}"
106
+ )
107
+
103
108
  except IntegrityError as err:
104
109
  db.session.rollback()
105
110
  current_app.logger.error(f"Integrity error on {action} data: {err}")
@@ -120,7 +125,10 @@ class EmptyBaseModel(db.Model):
120
125
  action = "bulk create update"
121
126
  try:
122
127
  db.session.commit()
123
- current_app.logger.debug(f"Transaction type: {action}, performed correctly on {cls}")
128
+ current_app.logger.debug(
129
+ f"Transaction type: {action}, performed correctly on {cls}"
130
+ )
131
+
124
132
  except IntegrityError as err:
125
133
  db.session.rollback()
126
134
  current_app.logger.error(f"Integrity error on {action} data: {err}")
@@ -136,12 +144,7 @@ class EmptyBaseModel(db.Model):
136
144
  return instances
137
145
 
138
146
  @classmethod
139
- def get_all_objects(
140
- cls,
141
- offset=0,
142
- limit=None,
143
- **kwargs
144
- ):
147
+ def get_all_objects(cls, offset=0, limit=None, **kwargs):
145
148
  """
146
149
  Method to get all the objects from the database applying the filters passed as keyword arguments
147
150
 
@@ -154,7 +157,9 @@ class EmptyBaseModel(db.Model):
154
157
  """
155
158
  if "user" in kwargs:
156
159
  kwargs.pop("user")
157
- query = cls.query.filter_by(**kwargs).offset(offset)
160
+ query = cls.query.filter_by(**kwargs)
161
+ if offset:
162
+ query = query.offset(offset)
158
163
  if limit:
159
164
  query = query.limit(limit)
160
165
  return query
@@ -208,8 +213,8 @@ class TraceAttributesModel(EmptyBaseModel):
208
213
  deleted_at = db.Column(db.DateTime, nullable=True)
209
214
 
210
215
  def __init__(self):
211
- self.created_at = datetime.utcnow()
212
- self.updated_at = datetime.utcnow()
216
+ self.created_at = datetime.now(timezone.utc)
217
+ self.updated_at = datetime.now(timezone.utc)
213
218
  self.deleted_at = None
214
219
 
215
220
  def update(self, data):
@@ -220,11 +225,11 @@ class TraceAttributesModel(EmptyBaseModel):
220
225
  :return: None
221
226
  :rtype: None
222
227
  """
223
- self.updated_at = datetime.utcnow()
228
+ self.updated_at = datetime.now(timezone.utc)
224
229
  super().update(data)
225
230
 
226
231
  def pre_update(self, data):
227
- self.updated_at = datetime.utcnow()
232
+ self.updated_at = datetime.now(timezone.utc)
228
233
  super().pre_update(data)
229
234
 
230
235
  def disable(self):
@@ -234,7 +239,7 @@ class TraceAttributesModel(EmptyBaseModel):
234
239
  :return: None
235
240
  :rtype: None
236
241
  """
237
- self.deleted_at = datetime.utcnow()
242
+ self.deleted_at = datetime.now(timezone.utc)
238
243
  db.session.add(self)
239
244
  self.commit_changes("disabling")
240
245
 
@@ -245,7 +250,7 @@ class TraceAttributesModel(EmptyBaseModel):
245
250
  :return: None
246
251
  :rtype: None
247
252
  """
248
- self.updated_at = datetime.utcnow()
253
+ self.updated_at = datetime.now(timezone.utc)
249
254
  self.deleted_at = None
250
255
  db.session.add(self)
251
256
  self.commit_changes("activating")
@@ -261,7 +266,7 @@ class TraceAttributesModel(EmptyBaseModel):
261
266
  update_date_lte=None,
262
267
  offset=0,
263
268
  limit=None,
264
- **kwargs
269
+ **kwargs,
265
270
  ):
266
271
  """
267
272
  Method to get all the objects from the database applying the filters passed as keyword arguments
@@ -300,7 +305,8 @@ class TraceAttributesModel(EmptyBaseModel):
300
305
  if creation_date_lte:
301
306
  query = query.filter(cls.created_at <= creation_date_lte)
302
307
 
303
- query = query.offset(offset)
308
+ if offset:
309
+ query = query.offset(offset)
304
310
  if limit:
305
311
  query = query.limit(limit)
306
312
  return query