cornflow 2.0.0a11__py3-none-any.whl → 2.0.0a12__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.
Files changed (52) hide show
  1. airflow_config/airflow_local_settings.py +1 -1
  2. cornflow/app.py +8 -3
  3. cornflow/cli/migrations.py +23 -3
  4. cornflow/cli/service.py +18 -18
  5. cornflow/cli/utils.py +16 -1
  6. cornflow/commands/dag.py +1 -1
  7. cornflow/config.py +13 -8
  8. cornflow/endpoints/__init__.py +8 -2
  9. cornflow/endpoints/alarms.py +66 -2
  10. cornflow/endpoints/data_check.py +53 -26
  11. cornflow/endpoints/execution.py +387 -132
  12. cornflow/endpoints/login.py +81 -63
  13. cornflow/endpoints/meta_resource.py +11 -3
  14. cornflow/migrations/versions/999b98e24225.py +34 -0
  15. cornflow/models/base_data_model.py +4 -32
  16. cornflow/models/execution.py +2 -3
  17. cornflow/models/meta_models.py +28 -22
  18. cornflow/models/user.py +7 -10
  19. cornflow/schemas/alarms.py +8 -0
  20. cornflow/schemas/execution.py +1 -1
  21. cornflow/schemas/query.py +2 -1
  22. cornflow/schemas/user.py +5 -20
  23. cornflow/shared/authentication/auth.py +201 -264
  24. cornflow/shared/const.py +3 -14
  25. cornflow/shared/databricks.py +5 -1
  26. cornflow/tests/const.py +1 -0
  27. cornflow/tests/custom_test_case.py +77 -26
  28. cornflow/tests/unit/test_actions.py +2 -2
  29. cornflow/tests/unit/test_alarms.py +55 -1
  30. cornflow/tests/unit/test_apiview.py +108 -3
  31. cornflow/tests/unit/test_cases.py +20 -29
  32. cornflow/tests/unit/test_cli.py +6 -5
  33. cornflow/tests/unit/test_commands.py +3 -3
  34. cornflow/tests/unit/test_dags.py +5 -6
  35. cornflow/tests/unit/test_executions.py +443 -123
  36. cornflow/tests/unit/test_instances.py +14 -2
  37. cornflow/tests/unit/test_instances_file.py +1 -1
  38. cornflow/tests/unit/test_licenses.py +1 -1
  39. cornflow/tests/unit/test_log_in.py +230 -207
  40. cornflow/tests/unit/test_permissions.py +8 -8
  41. cornflow/tests/unit/test_roles.py +48 -10
  42. cornflow/tests/unit/test_schemas.py +1 -1
  43. cornflow/tests/unit/test_tables.py +7 -7
  44. cornflow/tests/unit/test_token.py +19 -5
  45. cornflow/tests/unit/test_users.py +22 -6
  46. cornflow/tests/unit/tools.py +75 -10
  47. {cornflow-2.0.0a11.dist-info → cornflow-2.0.0a12.dist-info}/METADATA +16 -15
  48. {cornflow-2.0.0a11.dist-info → cornflow-2.0.0a12.dist-info}/RECORD +51 -51
  49. {cornflow-2.0.0a11.dist-info → cornflow-2.0.0a12.dist-info}/WHEEL +1 -1
  50. cornflow/endpoints/execution_databricks.py +0 -808
  51. {cornflow-2.0.0a11.dist-info → cornflow-2.0.0a12.dist-info}/entry_points.txt +0 -0
  52. {cornflow-2.0.0a11.dist-info → cornflow-2.0.0a12.dist-info}/top_level.txt +0 -0
@@ -1,16 +1,17 @@
1
1
  """
2
- External endpoint for the user to login to the cornflow webserver
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
- from flask import current_app
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
@@ -18,15 +19,11 @@ from cornflow.shared.const import (
18
19
  AUTH_DB,
19
20
  AUTH_LDAP,
20
21
  AUTH_OID,
21
- OID_AZURE,
22
- OID_GOOGLE,
23
- OID_NONE,
24
22
  )
25
23
  from cornflow.shared.exceptions import (
26
24
  ConfigurationError,
27
25
  InvalidCredentials,
28
26
  InvalidUsage,
29
- EndpointNotImplemented,
30
27
  )
31
28
 
32
29
 
@@ -45,7 +42,7 @@ class LoginBaseEndpoint(BaseMetaResource):
45
42
  This method is in charge of performing the log in of the user
46
43
 
47
44
  :param kwargs: keyword arguments passed for the login, these can be username, password or a token
48
- :return: the response of the login or it raises an error. The correct response is a dict
45
+ :return: the response of the login, or it raises an error. The correct response is a dict
49
46
  with the newly issued token and the user id, and a status code of 200
50
47
  :rtype: dict
51
48
  """
@@ -55,10 +52,37 @@ class LoginBaseEndpoint(BaseMetaResource):
55
52
  if auth_type == AUTH_DB:
56
53
  user = self.auth_db_authenticate(**kwargs)
57
54
  response.update({"change_password": check_last_password_change(user)})
55
+ current_app.logger.info(
56
+ f"User {user.id} logged in successfully using database authentication"
57
+ )
58
58
  elif auth_type == AUTH_LDAP:
59
59
  user = self.auth_ldap_authenticate(**kwargs)
60
+ current_app.logger.info(
61
+ f"User {user.id} logged in successfully using LDAP authentication"
62
+ )
60
63
  elif auth_type == AUTH_OID:
61
- user = self.auth_oid_authenticate(**kwargs)
64
+ if kwargs.get("username") and kwargs.get("password"):
65
+ if not current_app.config.get("SERVICE_USER_ALLOW_PASSWORD_LOGIN", 0):
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
+ )
76
+ token = self.auth_class.generate_token(user.id)
77
+ else:
78
+ token = self.auth_class().get_token_from_header(request.headers)
79
+ user = self.auth_oid_authenticate(token=token)
80
+ current_app.logger.info(
81
+ f"User {user.id} logged in successfully using OpenID authentication"
82
+ )
83
+
84
+ response.update({"token": token, "id": user.id})
85
+ return response, 200
62
86
  else:
63
87
  raise ConfigurationError()
64
88
 
@@ -77,7 +101,7 @@ class LoginBaseEndpoint(BaseMetaResource):
77
101
 
78
102
  :param str username: the username of the user to log in
79
103
  :param str password: the password of the user to log in
80
- :return: the user object or it raises an error if it has not been possible to log in
104
+ :return: the user object, or it raises an error if it has not been possible to log in
81
105
  :rtype: :class:`UserModel`
82
106
  """
83
107
  user = self.data_model.get_one_object(username=username)
@@ -96,7 +120,7 @@ class LoginBaseEndpoint(BaseMetaResource):
96
120
 
97
121
  :param str username: the username of the user to log in
98
122
  :param str password: the password of the user to log in
99
- :return: the user object or it raises an error if it has not been possible to log in
123
+ :return: the user object, or it raises an error if it has not been possible to log in
100
124
  :rtype: :class:`UserModel`
101
125
  """
102
126
  ldap_obj = self.ldap_class(current_app.config)
@@ -141,49 +165,20 @@ class LoginBaseEndpoint(BaseMetaResource):
141
165
  self, token: str = None, username: str = None, password: str = None
142
166
  ):
143
167
  """
144
- Method in charge of performing the log in with the token issued by an Open ID provider.
145
- It has an exception and thus accepts username and password for service users if needed.
168
+ Method in charge of performing the authentication using OpenID Connect tokens.
169
+ Supports any OIDC provider configured via provider_url.
146
170
 
147
- :param str token: the token that the user has obtained from the Open ID provider
148
- :param str username: the username of the user to log in
149
- :param str password: the password of the user to log in
150
- :return: the user object or it raises an error if it has not been possible to log in
171
+ :param str token: the JWT token from the OIDC provider
172
+ :param str username: username for service users
173
+ :param str password: password for service users
174
+ :return: the user object, or it raises an error if it has not been possible to log in
151
175
  :rtype: :class:`UserModel`
152
176
  """
153
-
154
177
  if token:
155
178
 
156
- oid_provider = int(current_app.config["OID_PROVIDER"])
157
-
158
- client_id = current_app.config["OID_CLIENT_ID"]
159
- tenant_id = current_app.config["OID_TENANT_ID"]
160
- issuer = current_app.config["OID_ISSUER"]
161
-
162
- if client_id is None or tenant_id is None or issuer is None:
163
- raise ConfigurationError("The OID provider configuration is not valid")
164
-
165
- if oid_provider == OID_AZURE:
166
- decoded_token = self.auth_class().validate_oid_token(
167
- token, client_id, tenant_id, issuer, oid_provider
168
- )
169
-
170
- elif oid_provider == OID_GOOGLE:
171
- raise EndpointNotImplemented(
172
- "The selected OID provider is not implemented"
173
- )
174
- elif oid_provider == OID_NONE:
175
- raise EndpointNotImplemented(
176
- "The OID provider configuration is not valid"
177
- )
178
- else:
179
- raise EndpointNotImplemented(
180
- "The OID provider configuration is not valid"
181
- )
179
+ decoded_token = self.auth_class().decode_token(token)
182
180
 
183
- username = decoded_token["preferred_username"]
184
- email = decoded_token.get("email", f"{username}@test.org")
185
- first_name = decoded_token.get("given_name", "")
186
- last_name = decoded_token.get("family_name", "")
181
+ username = decoded_token.get("sub")
187
182
 
188
183
  user = self.data_model.get_one_object(username=username)
189
184
 
@@ -192,6 +187,10 @@ class LoginBaseEndpoint(BaseMetaResource):
192
187
  f"OpenID user {username} does not exist and is created"
193
188
  )
194
189
 
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", "")
193
+
195
194
  data = {
196
195
  "username": username,
197
196
  "email": email,
@@ -208,33 +207,52 @@ class LoginBaseEndpoint(BaseMetaResource):
208
207
  "role_id": int(current_app.config["DEFAULT_ROLE"]),
209
208
  }
210
209
  )
211
-
212
210
  user_role.save()
211
+ if int(current_app.config["OPEN_DEPLOYMENT"]) == 1:
212
+ PermissionsDAG.add_all_permissions_to_user(user.id)
213
213
 
214
214
  return user
215
- elif (
216
- username
217
- and password
218
- and current_app.config["SERVICE_USER_ALLOW_PASSWORD_LOGIN"] == 1
219
- ):
220
215
 
216
+ elif username and password:
221
217
  user = self.auth_db_authenticate(username, password)
222
-
223
218
  if user.is_service_user():
224
219
  return user
225
- else:
226
- raise InvalidUsage("Invalid request")
220
+ raise InvalidUsage("Invalid request")
227
221
  else:
228
222
  raise InvalidUsage("Invalid request")
229
223
 
230
224
 
231
225
  def check_last_password_change(user):
226
+ """
227
+ Check if the user needs to change their password based on the password rotation time.
228
+
229
+ :param user: The user object to check
230
+ :return: True if password needs to be changed, False otherwise
231
+ :rtype: bool
232
+ """
232
233
  if user.pwd_last_change:
233
- if (
234
- user.pwd_last_change
235
- + timedelta(days=int(current_app.config["PWD_ROTATION_TIME"]))
236
- < datetime.utcnow()
237
- ):
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:
238
256
  return True
239
257
  return False
240
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
  """
@@ -0,0 +1,34 @@
1
+ """
2
+ Rename dag_run_id column to run_id in executions table
3
+
4
+ Revision ID: 999b98e24225
5
+ Revises: 991b98e24225
6
+ Create Date: 2024-04-08 12:00:00.000000
7
+
8
+ """
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision = "abc123456789"
16
+ down_revision = "991b98e24225"
17
+ branch_labels = None
18
+ depends_on = None
19
+
20
+
21
+ def upgrade():
22
+ # ### commands auto generated by Alembic - please adjust! ###
23
+ with op.batch_alter_table("executions", schema=None) as batch_op:
24
+ batch_op.alter_column("dag_run_id", new_column_name="run_id")
25
+
26
+ # ### end Alembic commands ###
27
+
28
+
29
+ def downgrade():
30
+ # ### commands auto generated by Alembic - please adjust! ###
31
+ with op.batch_alter_table("executions", schema=None) as batch_op:
32
+ batch_op.alter_column("run_id", new_column_name="dag_run_id")
33
+
34
+ # ### end Alembic commands ###
@@ -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
 
@@ -55,7 +55,7 @@ class ExecutionModel(BaseDataModel):
55
55
  db.String(256), db.ForeignKey("instances.id"), nullable=False
56
56
  )
57
57
  config = db.Column(JSON, nullable=False)
58
- dag_run_id = db.Column(db.String(256), nullable=True)
58
+ run_id = db.Column(db.String(256), nullable=True)
59
59
  log_text = db.Column(TEXT, nullable=True)
60
60
  log_json = db.Column(JSON, nullable=True)
61
61
  state = db.Column(db.SmallInteger, default=DEFAULT_EXECUTION_CODE, nullable=False)
@@ -78,8 +78,7 @@ class ExecutionModel(BaseDataModel):
78
78
  + str(self.instance_id)
79
79
  ).encode()
80
80
  ).hexdigest()
81
- # TODO AGA: modificar a run_id, tanto la columna como el parámetro.
82
- self.dag_run_id = data.get("dag_run_id")
81
+ self.run_id = data.get("run_id")
83
82
  self.state = data.get("state", DEFAULT_EXECUTION_CODE)
84
83
  self.state_message = EXECUTION_STATE_MESSAGE_DICT[self.state]
85
84
  self.config = data.get("config")
@@ -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
cornflow/models/user.py CHANGED
@@ -1,10 +1,11 @@
1
1
  """
2
2
  This file contains the UserModel
3
3
  """
4
+
4
5
  # Imports from external libraries
5
6
  import random
6
7
  import string
7
- from datetime import datetime
8
+ from datetime import datetime, timezone, timedelta
8
9
 
9
10
  # Imports from internal modules
10
11
  from cornflow.models.meta_models import TraceAttributesModel
@@ -55,9 +56,7 @@ class UserModel(TraceAttributesModel):
55
56
  pwd_last_change = db.Column(db.DateTime, nullable=True)
56
57
  email = db.Column(db.String(128), nullable=False, unique=True)
57
58
 
58
- user_roles = db.relationship(
59
- "UserRoleModel", cascade="all,delete", backref="users"
60
- )
59
+ user_roles = db.relationship("UserRoleModel", cascade="all,delete", backref="users")
61
60
 
62
61
  instances = db.relationship(
63
62
  "InstanceModel",
@@ -95,15 +94,14 @@ class UserModel(TraceAttributesModel):
95
94
  self.first_name = data.get("first_name")
96
95
  self.last_name = data.get("last_name")
97
96
  self.username = data.get("username")
98
- self.pwd_last_change = datetime.utcnow()
97
+ self.pwd_last_change = datetime.now(timezone.utc)
99
98
  # TODO: handle better None passwords that can be found when using ldap
100
99
  check_pass, msg = check_password_pattern(data.get("password"))
101
100
  if check_pass:
102
101
  self.password = self.__generate_hash(data.get("password"))
103
102
  else:
104
103
  raise InvalidCredentials(
105
- msg,
106
- log_txt="Error while trying to create a new user. " + msg
104
+ msg, log_txt="Error while trying to create a new user. " + msg
107
105
  )
108
106
 
109
107
  check_email, msg = check_email_pattern(data.get("email"))
@@ -111,8 +109,7 @@ class UserModel(TraceAttributesModel):
111
109
  self.email = data.get("email")
112
110
  else:
113
111
  raise InvalidCredentials(
114
- msg,
115
- log_txt="Error while trying to create a new user. " + msg
112
+ msg, log_txt="Error while trying to create a new user. " + msg
116
113
  )
117
114
 
118
115
  def update(self, data):
@@ -126,7 +123,7 @@ class UserModel(TraceAttributesModel):
126
123
  if new_password:
127
124
  new_password = self.__generate_hash(new_password)
128
125
  data["password"] = new_password
129
- data["pwd_last_change"] = datetime.utcnow()
126
+ data["pwd_last_change"] = datetime.now(timezone.utc)
130
127
  super().update(data)
131
128
 
132
129
  def comes_from_external_provider(self):
@@ -1,6 +1,7 @@
1
1
  """
2
2
  This file contains the schemas used for the table alarms defined in the application None
3
3
  """
4
+
4
5
  from marshmallow import fields, Schema
5
6
 
6
7
 
@@ -18,3 +19,10 @@ class AlarmsResponse(AlarmsPostRequest):
18
19
  class QueryFiltersAlarms(Schema):
19
20
  schema = fields.Str(required=False)
20
21
  criticality = fields.Number(required=False)
22
+
23
+
24
+ class AlarmEditRequest(Schema):
25
+ name = fields.Str(required=False)
26
+ criticality = fields.Number(required=False)
27
+ description = fields.Str(required=False)
28
+ schema = fields.Str(required=False)
@@ -43,7 +43,7 @@ class ExecutionSchema(Schema):
43
43
  name = fields.Str()
44
44
  description = fields.Str()
45
45
  schema = fields.Str(required=False)
46
- dag_run_id = fields.Str(required=False, dump_only=True)
46
+ run_id = fields.Str(required=False, dump_only=True)
47
47
  config = fields.Nested(ConfigSchema, required=True)
48
48
  data = fields.Raw(dump_only=True)
49
49
  checks = fields.Raw(required=False, allow_none=True)
cornflow/schemas/query.py CHANGED
@@ -1,13 +1,14 @@
1
1
  """
2
2
  This file contains the schemas used to query the results of a GET request
3
3
  """
4
+
4
5
  from marshmallow import fields, Schema
5
6
 
6
7
 
7
8
  class BaseQueryFilters(Schema):
8
9
  """This is the schema of the base query arguments"""
9
10
 
10
- limit = fields.Int(required=False, dump_default=20)
11
+ limit = fields.Int(required=False, dump_default=10)
11
12
  offset = fields.Int(required=False, dump_default=0)
12
13
  creation_date_gte = fields.DateTime(required=False)
13
14
  creation_date_lte = fields.DateTime(required=False)