cornflow 1.1.5__py3-none-any.whl → 1.2.0__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 (40) 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 +17 -15
  5. cornflow/cli/utils.py +16 -1
  6. cornflow/config.py +10 -11
  7. cornflow/endpoints/__init__.py +7 -1
  8. cornflow/endpoints/alarms.py +66 -2
  9. cornflow/endpoints/login.py +81 -63
  10. cornflow/endpoints/meta_resource.py +11 -3
  11. cornflow/models/base_data_model.py +4 -32
  12. cornflow/models/meta_models.py +28 -22
  13. cornflow/models/user.py +7 -10
  14. cornflow/schemas/alarms.py +8 -0
  15. cornflow/schemas/query.py +2 -1
  16. cornflow/schemas/user.py +5 -20
  17. cornflow/shared/authentication/auth.py +201 -264
  18. cornflow/shared/const.py +3 -14
  19. cornflow/tests/const.py +1 -0
  20. cornflow/tests/custom_test_case.py +77 -26
  21. cornflow/tests/unit/test_actions.py +2 -2
  22. cornflow/tests/unit/test_alarms.py +55 -1
  23. cornflow/tests/unit/test_apiview.py +108 -3
  24. cornflow/tests/unit/test_cases.py +20 -29
  25. cornflow/tests/unit/test_cli.py +6 -5
  26. cornflow/tests/unit/test_dags.py +5 -6
  27. cornflow/tests/unit/test_instances.py +14 -2
  28. cornflow/tests/unit/test_instances_file.py +1 -1
  29. cornflow/tests/unit/test_licenses.py +1 -1
  30. cornflow/tests/unit/test_log_in.py +230 -207
  31. cornflow/tests/unit/test_permissions.py +8 -8
  32. cornflow/tests/unit/test_roles.py +48 -10
  33. cornflow/tests/unit/test_tables.py +7 -7
  34. cornflow/tests/unit/test_token.py +19 -5
  35. cornflow/tests/unit/test_users.py +22 -6
  36. {cornflow-1.1.5.dist-info → cornflow-1.2.0.dist-info}/METADATA +13 -12
  37. {cornflow-1.1.5.dist-info → cornflow-1.2.0.dist-info}/RECORD +40 -40
  38. {cornflow-1.1.5.dist-info → cornflow-1.2.0.dist-info}/WHEEL +1 -1
  39. {cornflow-1.1.5.dist-info → cornflow-1.2.0.dist-info}/entry_points.txt +0 -0
  40. {cornflow-1.1.5.dist-info → cornflow-1.2.0.dist-info}/top_level.txt +0 -0
@@ -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
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)
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)
cornflow/schemas/user.py CHANGED
@@ -27,7 +27,7 @@ class UserEndpointResponse(Schema):
27
27
  last_name = fields.Str()
28
28
  email = fields.Str()
29
29
  created_at = fields.Str()
30
- pwd_last_change = fields.Str()
30
+ pwd_last_change = fields.DateTime()
31
31
 
32
32
 
33
33
  class UserDetailsEndpointResponse(Schema):
@@ -36,7 +36,7 @@ class UserDetailsEndpointResponse(Schema):
36
36
  last_name = fields.Str()
37
37
  username = fields.Str()
38
38
  email = fields.Str()
39
- pwd_last_change = fields.Str()
39
+ pwd_last_change = fields.DateTime()
40
40
 
41
41
 
42
42
  class TokenEndpointResponse(Schema):
@@ -53,7 +53,6 @@ class UserEditRequest(Schema):
53
53
  last_name = fields.Str(required=False)
54
54
  email = fields.Str(required=False)
55
55
  password = fields.Str(required=False)
56
- pwd_last_change = fields.DateTime(required=False)
57
56
 
58
57
 
59
58
  class LoginEndpointRequest(Schema):
@@ -67,24 +66,10 @@ class LoginEndpointRequest(Schema):
67
66
 
68
67
  class LoginOpenAuthRequest(Schema):
69
68
  """
70
- This is the schema used by the login endpoint with Open ID protocol
71
- Validates that either a token is provided, or both username and password are present
69
+ Schema for the login request with OpenID authentication
72
70
  """
73
-
74
- token = fields.Str(required=False)
75
- username = fields.Str(required=False)
76
- password = fields.Str(required=False)
77
-
78
- @validates_schema
79
- def validate_fields(self, data, **kwargs):
80
- if data.get("token") is None:
81
- if not data.get("username") or not data.get("password"):
82
- raise ValidationError(
83
- "A token needs to be provided when using Open ID authentication"
84
- )
85
- else:
86
- if data.get("username") or data.get("password"):
87
- raise ValidationError("The login needs to be done with a token only")
71
+ username = fields.String(required=False)
72
+ password = fields.String(required=False)
88
73
 
89
74
 
90
75
  class SignupRequest(Schema):