dataflow-core 2.1.14rc1__py3-none-any.whl → 2.1.15__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 (38) hide show
  1. authenticator/dataflowairflowauthenticator.py +3 -9
  2. authenticator/dataflowhubauthenticator.py +29 -44
  3. dataflow/dataflow.py +45 -69
  4. dataflow/environment.py +20 -11
  5. dataflow/models/__init__.py +5 -3
  6. dataflow/models/app_types.py +8 -3
  7. dataflow/models/connection.py +5 -4
  8. dataflow/models/dataflow_zone.py +2 -3
  9. dataflow/models/environment.py +64 -21
  10. dataflow/models/git_ssh.py +2 -1
  11. dataflow/models/org_associations.py +38 -0
  12. dataflow/models/organization.py +78 -0
  13. dataflow/models/pinned_projects.py +2 -2
  14. dataflow/models/project_details.py +9 -6
  15. dataflow/models/recent_project_studio.py +1 -1
  16. dataflow/models/role.py +12 -6
  17. dataflow/models/role_server.py +2 -5
  18. dataflow/models/role_zone.py +8 -3
  19. dataflow/models/server_config.py +9 -5
  20. dataflow/models/team.py +10 -4
  21. dataflow/models/user.py +49 -12
  22. dataflow/models/user_team.py +1 -4
  23. dataflow/models/variables.py +6 -4
  24. dataflow/secrets_manager/factory.py +14 -8
  25. dataflow/secrets_manager/providers/gcp_manager.py +332 -0
  26. dataflow/secrets_manager/service.py +11 -9
  27. dataflow/utils/get_current_user.py +51 -28
  28. {dataflow_core-2.1.14rc1.dist-info → dataflow_core-2.1.15.dist-info}/METADATA +4 -1
  29. dataflow_core-2.1.15.dist-info/RECORD +63 -0
  30. {dataflow_core-2.1.14rc1.dist-info → dataflow_core-2.1.15.dist-info}/top_level.txt +1 -0
  31. dfmigration/__init__.py +0 -0
  32. dfmigration/env.py +45 -0
  33. dfmigration/versions/001_initial_baseline_migration.py +20 -0
  34. dfmigration/versions/__init__.py +0 -0
  35. dataflow/models/user_environment.py +0 -16
  36. dataflow_core-2.1.14rc1.dist-info/RECORD +0 -57
  37. {dataflow_core-2.1.14rc1.dist-info → dataflow_core-2.1.15.dist-info}/WHEEL +0 -0
  38. {dataflow_core-2.1.14rc1.dist-info → dataflow_core-2.1.15.dist-info}/entry_points.txt +0 -0
@@ -4,7 +4,7 @@ from flask_appbuilder.security.views import expose
4
4
  from flask_login import login_user
5
5
  from airflow.www.security import FabAirflowSecurityManagerOverride
6
6
  from dataflow.dataflow import Dataflow
7
- import logging, os
7
+ import logging
8
8
 
9
9
  logging.basicConfig(
10
10
  level=logging.INFO,
@@ -29,15 +29,9 @@ class DataflowAuthDBView(AuthDBView):
29
29
 
30
30
  user_details = dataflow.auth(session_id)
31
31
  logger.info(f"User details retrieved for: {user_details['user_name']}")
32
- if os.getenv("RUNTIME"):
33
- role = self.appbuilder.sm.find_role(user_details.get("base_role", "user").title())
34
- else:
35
- role = self.appbuilder.sm.find_role("Admin")
36
32
  user = self.appbuilder.sm.find_user(username=user_details['user_name'])
37
33
  if user:
38
- # Update user role
39
- user.role = role
40
- self.appbuilder.sm.update_user(user=user)
34
+ logger.info(f"User found: {user}")
41
35
  login_user(user, remember=False)
42
36
  else:
43
37
  user = self.appbuilder.sm.add_user(
@@ -45,7 +39,7 @@ class DataflowAuthDBView(AuthDBView):
45
39
  first_name=user_details.get("first_name", ""),
46
40
  last_name=user_details.get("last_name", ""),
47
41
  email=user_details.get("email", ""),
48
- role=role,
42
+ role=self.appbuilder.sm.find_role(user_details.get("base_role", "user").title())
49
43
  )
50
44
  logger.info(f"New user created: {user}")
51
45
  if user:
@@ -1,6 +1,4 @@
1
- import os
2
- import uuid
3
- import re
1
+ import os, uuid, re, hashlib, secrets
4
2
  from datetime import datetime, timedelta
5
3
  from zoneinfo import ZoneInfo
6
4
  from traitlets import Bool, Unicode
@@ -8,7 +6,8 @@ from jupyterhub.auth import Authenticator
8
6
  from oauthenticator.google import GoogleOAuthenticator
9
7
  from oauthenticator.azuread import AzureAdOAuthenticator
10
8
  from dataflow.db import get_db
11
- from dataflow.models import user as m_user, session as m_session, role as m_role
9
+ from dataflow.models import user as m_user, session as m_session
10
+ from sqlalchemy import or_
12
11
 
13
12
  class DataflowBaseAuthenticator(Authenticator):
14
13
  enable_dataflow_auth = Bool(True, config=True, help="Enable username/password authentication")
@@ -28,7 +27,7 @@ class DataflowBaseAuthenticator(Authenticator):
28
27
  return str(uuid.uuid4())
29
28
 
30
29
  def set_session_cookie(self, handler, session_id):
31
- expires = datetime.now(ZoneInfo("UTC")) + timedelta(days=365)
30
+ expires = datetime.now(ZoneInfo("UTC")) + timedelta(days=60)
32
31
  host = handler.request.host
33
32
  domain = '.'.join(host.split('.')[-2:]) if len(host.split('.')) >= 2 else host
34
33
  handler.set_cookie(
@@ -44,19 +43,12 @@ class DataflowBaseAuthenticator(Authenticator):
44
43
  self.log.info(f"Set session cookie: dataflow_session={session_id} for host={host}")
45
44
 
46
45
  def get_or_create_session(self, user_id):
47
- existing_session = (
48
- self.db.query(m_session.Session)
49
- .filter(m_session.Session.user_id == str(user_id))
50
- .first()
51
- )
52
- if existing_session:
53
- self.log.info(f"Reusing existing session: {existing_session.session_id}")
54
- return existing_session.session_id
55
46
  session_id = self.generate_session_id()
56
47
  while self.db.query(m_session.Session).filter(
57
48
  m_session.Session.session_id == session_id
58
49
  ).first():
59
50
  session_id = self.generate_session_id()
51
+
60
52
  db_item = m_session.Session(user_id=user_id, session_id=session_id)
61
53
  self.db.add(db_item)
62
54
  self.db.commit()
@@ -77,42 +69,28 @@ class DataflowBaseAuthenticator(Authenticator):
77
69
 
78
70
  return super().check_blocked_users(username, authenticated)
79
71
 
80
- def get_applicant_role_id(self):
81
- """Get the role ID for 'Applicant' role"""
82
- try:
83
- applicant_role = (
84
- self.db.query(m_role.Role)
85
- .filter(m_role.Role.name == "Applicant")
86
- .first()
87
- )
88
- if applicant_role:
89
- return applicant_role.id
90
- else:
91
- self.log.warning("Applicant role not found in database")
92
- return None
93
- except Exception as e:
94
- self.log.error(f"Error getting Applicant role: {str(e)}")
95
- return None
96
-
97
72
  def extract_username_from_email(self, email):
98
73
  """Extract username from email by removing domain"""
99
74
  if '@' in email:
100
75
  return email.split('@')[0]
101
76
  return email
102
77
 
78
+ def generate_secure_password(self):
79
+ """Generate a secure random password hash"""
80
+ salt = secrets.token_hex(16)
81
+ random_uuid = str(uuid.uuid4())
82
+ hash_obj = hashlib.sha256((random_uuid + salt).encode())
83
+ return hash_obj.hexdigest()
84
+
103
85
  def create_new_user(self, email, first_name=None, last_name=None):
104
86
  """Create a new user with Applicant role"""
105
87
  try:
106
- role_id = self.get_applicant_role_id()
107
- if not role_id:
108
- self.log.error("Cannot create user: Applicant role not found")
109
- return None
110
-
111
88
  username = self.extract_username_from_email(email)
112
89
  username = re.sub(r'[^a-z0-9]', '', username.lower())
113
90
  if not username:
114
91
  self.log.error("Cannot create user: Username is empty")
115
92
  return None
93
+
116
94
  existing_user = (
117
95
  self.db.query(m_user.User)
118
96
  .filter(m_user.User.user_name == username)
@@ -122,7 +100,7 @@ class DataflowBaseAuthenticator(Authenticator):
122
100
  counter = 1
123
101
  original_username = username
124
102
  while existing_user:
125
- username = f"{original_username}_{counter}"
103
+ username = f"{original_username}{counter}"
126
104
  existing_user = (
127
105
  self.db.query(m_user.User)
128
106
  .filter(m_user.User.user_name == username)
@@ -130,13 +108,13 @@ class DataflowBaseAuthenticator(Authenticator):
130
108
  )
131
109
  counter += 1
132
110
 
111
+ secure_password = self.generate_secure_password()
133
112
  new_user = m_user.User(
134
113
  user_name=username,
135
114
  first_name=first_name or username,
136
115
  last_name=last_name or "",
137
116
  email=email,
138
- role_id=role_id,
139
- password='user@123',
117
+ password=secure_password,
140
118
  )
141
119
 
142
120
  self.db.add(new_user)
@@ -154,22 +132,29 @@ class DataflowBaseAuthenticator(Authenticator):
154
132
  async def authenticate_dataflow(self, handler, data):
155
133
  if not (self.enable_dataflow_auth and isinstance(data, dict) and data.get("username") and data.get("password")):
156
134
  return None
157
- username = data["username"]
135
+ user_name_or_email = data["username"]
158
136
  password = data["password"]
159
- self.log.info(f"Attempting Dataflow authentication for user: {username}")
137
+ self.log.info(f"Attempting Dataflow authentication for user: {user_name_or_email}")
160
138
  try:
161
139
  user = (
162
140
  self.db.query(m_user.User)
163
- .filter(m_user.User.user_name == username)
141
+ .filter(
142
+ or_(
143
+ m_user.User.email == user_name_or_email,
144
+ m_user.User.user_name == user_name_or_email
145
+ )
146
+ )
164
147
  .first()
165
148
  )
149
+
166
150
  if not user or user.password != password:
167
- self.log.warning(f"Dataflow authentication failed for user: {username}")
151
+ self.log.warning(f"Dataflow authentication failed for user: {user_name_or_email}")
168
152
  return None
153
+
169
154
  session_id = self.get_or_create_session(user.user_id)
170
155
  self.set_session_cookie(handler, session_id)
171
- self.log.info(f"Dataflow authentication successful for user: {username}")
172
- return {"name": username, "session_id": session_id, "auth_state": {}}
156
+ self.log.info(f"Dataflow authentication successful for user: {user.user_name}")
157
+ return {"name": user.user_name, "session_id": session_id, "auth_state": {}}
173
158
  except Exception as e:
174
159
  self.log.error(f"Dataflow authentication error: {str(e)}")
175
160
  return None
dataflow/dataflow.py CHANGED
@@ -25,16 +25,13 @@ class Dataflow:
25
25
  def _parse_response_data(self, response):
26
26
  """Parse response data based on datatype field or fallback to JSON parsing."""
27
27
  data = response.json()
28
+ if not isinstance(data, dict):
29
+ raise ValueError("Internal Dataflow Error!")
28
30
  value = data.get('value', '')
29
- if isinstance(data, dict) and 'datatype' in data:
30
- value = data.get('value', '')
31
- datatype = data.get('datatype')
32
- if datatype == 'json':
33
- return self._json_parse(value)
34
- else:
35
- return value
36
- else:
31
+ if data.get('datatype') == 'json':
37
32
  return self._json_parse(value)
33
+ else:
34
+ return value
38
35
 
39
36
  def auth(self, session_id: str):
40
37
  """
@@ -84,12 +81,19 @@ class Dataflow:
84
81
  host_name = os.environ.get("HOSTNAME", "")
85
82
  runtime = os.environ.get("RUNTIME")
86
83
  slug = os.environ.get("SLUG")
84
+ org_id = os.environ.get("ORGANIZATION")
87
85
 
88
86
  dataflow_config = ConfigurationManager('/dataflow/app/auth_config/dataflow_auth.cfg')
87
+ query_params = {
88
+ "key": variable_name,
89
+ }
89
90
 
90
91
  variable_api = None
91
92
  if runtime and slug:
92
93
  variable_api = dataflow_config.get_config_value("auth", "variable_ui_api")
94
+ query_params["runtime"] = runtime
95
+ query_params["slug"] = slug
96
+ query_params["org_id"] = org_id
93
97
  elif host_name:
94
98
  variable_api = dataflow_config.get_config_value("auth", "variable_manager_api")
95
99
  else:
@@ -98,36 +102,13 @@ class Dataflow:
98
102
  if not variable_api:
99
103
  print("[Dataflow.variable] Variable Unreachable")
100
104
  return None
101
-
102
- if runtime:
103
- query_params = {
104
- "key": variable_name,
105
- "runtime": runtime,
106
- "slug": slug
107
- }
108
- response = requests.get(variable_api, params=query_params)
109
- if response.status_code == 200:
110
- response_text = response.text.strip().strip('"')
111
- return response_text
112
-
113
- query_params["slug"] = "global"
114
- response = requests.get(variable_api, params=query_params)
115
- if response.status_code == 200:
116
- response_text = response.text.strip().strip('"')
117
- return response_text
118
- else:
119
- return None
120
-
121
- query_params = {
122
- "key": variable_name,
123
- }
105
+
124
106
  response = requests.get(variable_api, params=query_params)
125
107
 
126
- # Handle different HTTP status codes gracefully
127
108
  if response.status_code == 404:
128
- return None # Variable not found
109
+ return None
129
110
  elif response.status_code >= 500:
130
- response.raise_for_status() # Let server errors propagate
111
+ response.raise_for_status()
131
112
  elif response.status_code >= 400:
132
113
  print(f"[Dataflow.variable] Client error {response.status_code} for variable '{variable_name}'")
133
114
  return None
@@ -158,32 +139,30 @@ class Dataflow:
158
139
  host_name = os.environ.get("HOSTNAME", "")
159
140
  runtime = os.environ.get("RUNTIME")
160
141
  slug = os.environ.get("SLUG")
142
+ org_id = os.environ.get("ORGANIZATION")
161
143
 
162
144
  dataflow_config = ConfigurationManager('/dataflow/app/auth_config/dataflow_auth.cfg')
163
- if runtime:
164
- secret_api = dataflow_config.get_config_value("auth", "secret_ui_api")
165
- else:
166
- secret_api = dataflow_config.get_config_value("auth", "secret_manager_api")
167
- if not secret_api:
168
- print("[Dataflow.secret] Secret API Unreachable")
169
- return None
170
-
171
145
  query_params = {
172
146
  "key": secret_name
173
147
  }
174
148
 
175
149
  if runtime:
150
+ secret_api = dataflow_config.get_config_value("auth", "secret_ui_api")
176
151
  query_params["runtime"] = runtime
177
- if slug:
178
152
  query_params["slug"] = slug
153
+ query_params["org_id"] = org_id
154
+ else:
155
+ secret_api = dataflow_config.get_config_value("auth", "secret_manager_api")
156
+ if not secret_api:
157
+ print("[Dataflow.secret] Secret API Unreachable")
158
+ return None
179
159
 
180
160
  response = requests.get(secret_api, params=query_params)
181
-
182
- # Handle different HTTP status codes gracefully
161
+
183
162
  if response.status_code == 404:
184
- return None # Secret not found
163
+ return None
185
164
  elif response.status_code >= 500:
186
- response.raise_for_status() # Let server errors propagate
165
+ response.raise_for_status()
187
166
  elif response.status_code >= 400:
188
167
  print(f"[Dataflow.secret] Client error {response.status_code} for secret '{secret_name}'")
189
168
  return None
@@ -214,31 +193,29 @@ class Dataflow:
214
193
  host_name = os.environ["HOSTNAME"]
215
194
  runtime = os.environ.get("RUNTIME")
216
195
  slug = os.environ.get("SLUG")
217
-
196
+ org_id = os.environ.get("ORGANIZATION")
197
+
218
198
  dataflow_config = ConfigurationManager('/dataflow/app/auth_config/dataflow_auth.cfg')
219
- if runtime:
220
- connection_api = dataflow_config.get_config_value("auth", "connection_ui_api")
221
- elif host_name:
222
- connection_api = dataflow_config.get_config_value("auth", "connection_manager_api")
223
- else:
224
- raise Exception("Cannot run dataflow methods here! HOSTNAME or RUNTIME env variable not set.")
225
-
226
199
  query_params = {
227
200
  "conn_id": conn_id
228
201
  }
229
202
 
230
203
  if runtime:
231
204
  query_params["runtime"] = runtime
232
- if slug:
205
+ query_params["org_id"] = org_id
233
206
  query_params["slug"] = slug
207
+ connection_api = dataflow_config.get_config_value("auth", "connection_ui_api")
208
+ elif host_name:
209
+ connection_api = dataflow_config.get_config_value("auth", "connection_manager_api")
210
+ else:
211
+ raise Exception("Cannot run dataflow methods here! HOSTNAME or RUNTIME env variable not set.")
234
212
 
235
213
  response = requests.get(connection_api, params=query_params)
236
214
 
237
- # Handle different HTTP status codes gracefully
238
215
  if response.status_code == 404:
239
216
  raise RuntimeError(f"[Dataflow.connection] Connection '{conn_id}' not found!")
240
217
  elif response.status_code >= 500:
241
- response.raise_for_status() # Let server errors propagate
218
+ response.raise_for_status()
242
219
  elif response.status_code >= 400:
243
220
  raise RuntimeError(f"[Dataflow.connection] Client error {response.status_code} for connection '{conn_id}'")
244
221
  elif response.status_code != 200:
@@ -306,20 +283,20 @@ class Dataflow:
306
283
  host_name = os.environ.get("HOSTNAME", "")
307
284
  runtime = os.environ.get("RUNTIME")
308
285
  slug = os.environ.get("SLUG")
286
+ org_id = os.environ.get("ORGANIZATION")
309
287
 
310
288
  dataflow_config = ConfigurationManager('/dataflow/app/auth_config/dataflow_auth.cfg')
311
- if runtime and slug:
312
- variableorsecret_api = dataflow_config.get_config_value("auth", "variableorsecret_ui_api")
313
- query_params = {
314
- "key": key,
315
- "runtime": runtime,
316
- "slug": slug
289
+ query_params = {
290
+ "key": key
317
291
  }
292
+
293
+ if runtime:
294
+ variableorsecret_api = dataflow_config.get_config_value("auth", "variableorsecret_ui_api")
295
+ query_params["runtime"] = runtime
296
+ query_params["slug"] = slug
297
+ query_params["org_id"] = org_id
318
298
  elif host_name:
319
299
  variableorsecret_api = dataflow_config.get_config_value("auth", "variableorsecret_manager_api")
320
- query_params = {
321
- "key": key
322
- }
323
300
  else:
324
301
  raise Exception("Cannot run dataflow methods here!")
325
302
 
@@ -329,9 +306,8 @@ class Dataflow:
329
306
 
330
307
  response = requests.get(variableorsecret_api, params=query_params)
331
308
 
332
- # Handle different HTTP status codes gracefully
333
309
  if response.status_code == 404:
334
- return None # Variable/secret not found
310
+ return None
335
311
  elif response.status_code >= 500:
336
312
  response.raise_for_status() # Let server errors propagate
337
313
  elif response.status_code >= 400:
dataflow/environment.py CHANGED
@@ -6,12 +6,14 @@ from .configuration import ConfigurationManager
6
6
  from .utils.logger import CustomLogger
7
7
 
8
8
  class EnvironmentManager:
9
- def __init__(self):
9
+ def __init__(self, org_id: int = None):
10
10
  """Initialize the EnvironmentManager"""
11
11
  self.config = ConfigurationManager('/dataflow/app/config/dataflow.cfg')
12
- self.env_base_path = self.config.get_config_value('paths', 'env_path')
13
- self.env_logs_path = self.config.get_config_value('paths', 'env_logs_path')
14
- self.env_version_path = self.config.get_config_value('paths', 'env_versions_path')
12
+ self.org_id = org_id
13
+ self.env_sub_path = f"{self.org_id}" if self.org_id is not None else "dataflow"
14
+ self.env_base_path = os.path.join(self.config.get_config_value('paths', 'env_path'), self.env_sub_path, 'python_envs')
15
+ self.env_logs_path = os.path.join(self.config.get_config_value('paths', 'env_logs_path'), self.env_sub_path, 'logs')
16
+ self.env_version_path = os.path.join(self.config.get_config_value('paths', 'env_versions_path'), self.env_sub_path, 'versions')
15
17
  self.local_env_logs_path = self.config.get_config_value('paths', 'local_env_logs_path')
16
18
  os.makedirs(self.env_version_path, exist_ok=True)
17
19
  self.logger = CustomLogger().get_logger(__name__)
@@ -334,10 +336,9 @@ class EnvironmentManager:
334
336
  """
335
337
  versioned_name = f"{env_name}_v{env_version}"
336
338
  log_file_name = f"envlog_{versioned_name}.log"
337
- log_file_dir = self.config.get_config_value('paths', 'env_logs_path')
338
- os.makedirs(log_file_dir, exist_ok=True)
339
- log_file_location = os.path.join(log_file_dir, log_file_name)
340
-
339
+ os.makedirs(self.env_logs_path, exist_ok=True)
340
+ log_file_location = os.path.join(self.env_logs_path, log_file_name)
341
+
341
342
  # Clear log file if it exists
342
343
  if os.path.exists(log_file_location):
343
344
  open(log_file_location, "w").close()
@@ -394,8 +395,14 @@ class EnvironmentManager:
394
395
  Returns:
395
396
  JobLogs: The created or updated job entry
396
397
  """
397
- job = db.query(JobLogs).filter(JobLogs.log_file_name == log_file_name).first()
398
-
398
+ job = (
399
+ db.query(JobLogs)
400
+ .filter(
401
+ JobLogs.log_file_name == log_file_name,
402
+ JobLogs.org_id == self.org_id if self.org_id is not None else JobLogs.org_id.is_(None)
403
+ )
404
+ .first()
405
+ )
399
406
  if job:
400
407
  if job.status == "success":
401
408
  self.logger.error(f"Job with log_file_name '{log_file_name}' already completed successfully.")
@@ -409,6 +416,7 @@ class EnvironmentManager:
409
416
  log_file_name=log_file_name,
410
417
  log_file_location=log_file_location,
411
418
  created_by=user_name,
419
+ org_id=self.org_id if self.org_id else None,
412
420
  status="in_progress"
413
421
  )
414
422
  db.add(job)
@@ -464,7 +472,8 @@ class EnvironmentManager:
464
472
  env_status = "Draft" if status == "success" else "Failed"
465
473
 
466
474
  db.query(Environment).filter(
467
- Environment.short_name == env_short_name
475
+ Environment.short_name == env_short_name,
476
+ Environment.org_id == self.org_id if self.org_id is not None else Environment.org_id.is_(None)
468
477
  ).update({"version": version, "pip_libraries": pip_libraries, "conda_libraries": conda_libraries, "status": env_status})
469
478
  db.commit()
470
479
 
@@ -1,9 +1,9 @@
1
1
  # init for loading models in the application
2
2
 
3
3
  from .role import Role
4
- from .user import User
4
+ from .user import User, UserOnboarding, OnboardingStatus
5
5
  from .team import Team
6
- from .environment import (Environment, LocalEnvironment, ArchivedEnvironment, JobLogs)
6
+ from .environment import (Environment, LocalEnvironment, ArchivedEnvironment, JobLogs, PipSource)
7
7
  from .project_details import ProjectDetails
8
8
  from .recent_projects import RecentProjects
9
9
  from .pinned_projects import PinnedProject
@@ -22,4 +22,6 @@ from .recent_project_studio import RecentProjectStudio
22
22
  from .connection import Connection
23
23
  from .git_ssh import GitSSH
24
24
  from .pod_activity import PodActivity
25
- from .pod_session_history import PodSessionHistory
25
+ from .pod_session_history import PodSessionHistory
26
+ from .organization import Organization, OrganizationOnboarding
27
+ from .org_associations import OrganizationServer, OrganizationUser, OrganizationAppType
@@ -1,10 +1,15 @@
1
1
  from sqlalchemy import Column, Integer, String, Boolean
2
+ from sqlalchemy.orm import relationship
2
3
  from dataflow.db import Base
3
4
 
4
5
  class AppType(Base):
5
- __tablename__ = "RUNTIME_APP_TYPE"
6
+ __tablename__ = "APP_TYPE"
6
7
 
7
8
  id = Column(Integer, primary_key=True, autoincrement=True, unique=True)
8
- name = Column(String, unique=True, nullable=True)
9
+ name = Column(String, unique=True, nullable=False)
9
10
  display_name = Column(String, nullable=False)
10
- code_based = Column(Boolean, nullable=False)
11
+ code_based = Column(Boolean, nullable=False)
12
+ studio = Column(Boolean, nullable=False, default=False, server_default='false')
13
+ runtime = Column(Boolean, nullable=False, default=False, server_default='false')
14
+
15
+ organizations = relationship("Organization", secondary="ORGANIZATION_APP_TYPE", back_populates="apps")
@@ -1,4 +1,4 @@
1
- from sqlalchemy import Column, String, Integer, Boolean, DateTime, UniqueConstraint
1
+ from sqlalchemy import Column, String, Integer, Boolean, DateTime, UniqueConstraint, ForeignKey
2
2
  from sqlalchemy.sql import func
3
3
  from dataflow.db import Base
4
4
 
@@ -10,16 +10,17 @@ class Connection(Base):
10
10
 
11
11
  id = Column(Integer, primary_key=True, index=True)
12
12
  conn_id = Column(String, index=True, nullable=False)
13
+ org_id = Column(Integer, ForeignKey("ORGANIZATION.id"), index=True, nullable=False)
13
14
  description = Column(String, nullable=True)
14
15
  conn_type = Column(String, nullable=False)
15
16
  runtime = Column(String, nullable=True)
16
17
  slug = Column(String, nullable=True)
17
- status = Column(Boolean, default=False)
18
+ status = Column(Boolean, default=False, server_default='false')
18
19
  created_by = Column(String, nullable=True)
19
20
  created_at = Column(DateTime(timezone=True), server_default=func.now())
20
21
  updated_at = Column(DateTime(timezone=True), onupdate=func.now())
21
- is_active = Column(Boolean, default=True)
22
+ is_active = Column(Boolean, default=True, server_default='true')
22
23
 
23
24
  __table_args__ = (
24
- UniqueConstraint('conn_id', 'runtime', 'slug', 'is_active', 'created_by', name='uq_active_conn_with_runtime_slug'),
25
+ UniqueConstraint('conn_id', 'org_id', 'runtime', 'slug', 'is_active', 'created_by', name='uq_active_conn_with_runtime_slug'),
25
26
  )
@@ -8,10 +8,9 @@ class DataflowZone(Base):
8
8
  id = Column(Integer, primary_key=True, autoincrement=True)
9
9
  slug = Column(String, unique=True, nullable=False)
10
10
  display_name = Column(String, nullable=False)
11
- is_runtime = Column(Boolean, default=False)
11
+ is_runtime = Column(Boolean, default=False, server_default='false')
12
12
  subdomain = Column(String)
13
- spark_enabled = Column(Boolean, default=False)
14
- display_order = Column(Integer, default=0)
13
+ display_order = Column(Integer, default=0, server_default='0')
15
14
 
16
15
  role_zone_assocs = relationship("RoleZone", back_populates="zone")
17
16