dataflow-core 2.1.15rc1__py3-none-any.whl → 2.1.15rc2__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.

Potentially problematic release.


This version of dataflow-core might be problematic. Click here for more details.

@@ -8,7 +8,8 @@ from jupyterhub.auth import Authenticator
8
8
  from oauthenticator.google import GoogleOAuthenticator
9
9
  from oauthenticator.azuread import AzureAdOAuthenticator
10
10
  from dataflow.db import get_db
11
- from dataflow.models import user as m_user, session as m_session, role as m_role
11
+ from dataflow.models import user as m_user, session as m_session
12
+ from sqlalchemy import or_
12
13
 
13
14
  class DataflowBaseAuthenticator(Authenticator):
14
15
  enable_dataflow_auth = Bool(True, config=True, help="Enable username/password authentication")
@@ -28,7 +29,7 @@ class DataflowBaseAuthenticator(Authenticator):
28
29
  return str(uuid.uuid4())
29
30
 
30
31
  def set_session_cookie(self, handler, session_id):
31
- expires = datetime.now(ZoneInfo("UTC")) + timedelta(days=365)
32
+ expires = datetime.now(ZoneInfo("UTC")) + timedelta(days=60)
32
33
  host = handler.request.host
33
34
  domain = '.'.join(host.split('.')[-2:]) if len(host.split('.')) >= 2 else host
34
35
  handler.set_cookie(
@@ -44,19 +45,12 @@ class DataflowBaseAuthenticator(Authenticator):
44
45
  self.log.info(f"Set session cookie: dataflow_session={session_id} for host={host}")
45
46
 
46
47
  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
48
  session_id = self.generate_session_id()
56
49
  while self.db.query(m_session.Session).filter(
57
50
  m_session.Session.session_id == session_id
58
51
  ).first():
59
52
  session_id = self.generate_session_id()
53
+
60
54
  db_item = m_session.Session(user_id=user_id, session_id=session_id)
61
55
  self.db.add(db_item)
62
56
  self.db.commit()
@@ -77,23 +71,6 @@ class DataflowBaseAuthenticator(Authenticator):
77
71
 
78
72
  return super().check_blocked_users(username, authenticated)
79
73
 
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
74
  def extract_username_from_email(self, email):
98
75
  """Extract username from email by removing domain"""
99
76
  if '@' in email:
@@ -103,16 +80,12 @@ class DataflowBaseAuthenticator(Authenticator):
103
80
  def create_new_user(self, email, first_name=None, last_name=None):
104
81
  """Create a new user with Applicant role"""
105
82
  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
83
  username = self.extract_username_from_email(email)
112
84
  username = re.sub(r'[^a-z0-9]', '', username.lower())
113
85
  if not username:
114
86
  self.log.error("Cannot create user: Username is empty")
115
87
  return None
88
+
116
89
  existing_user = (
117
90
  self.db.query(m_user.User)
118
91
  .filter(m_user.User.user_name == username)
@@ -122,7 +95,7 @@ class DataflowBaseAuthenticator(Authenticator):
122
95
  counter = 1
123
96
  original_username = username
124
97
  while existing_user:
125
- username = f"{original_username}_{counter}"
98
+ username = f"{original_username}{counter}"
126
99
  existing_user = (
127
100
  self.db.query(m_user.User)
128
101
  .filter(m_user.User.user_name == username)
@@ -135,8 +108,6 @@ class DataflowBaseAuthenticator(Authenticator):
135
108
  first_name=first_name or username,
136
109
  last_name=last_name or "",
137
110
  email=email,
138
- role_id=role_id,
139
- password='user@123',
140
111
  )
141
112
 
142
113
  self.db.add(new_user)
@@ -154,22 +125,29 @@ class DataflowBaseAuthenticator(Authenticator):
154
125
  async def authenticate_dataflow(self, handler, data):
155
126
  if not (self.enable_dataflow_auth and isinstance(data, dict) and data.get("username") and data.get("password")):
156
127
  return None
157
- username = data["username"]
128
+ user_name_or_email = data["username"]
158
129
  password = data["password"]
159
- self.log.info(f"Attempting Dataflow authentication for user: {username}")
130
+ self.log.info(f"Attempting Dataflow authentication for user: {user_name_or_email}")
160
131
  try:
161
132
  user = (
162
133
  self.db.query(m_user.User)
163
- .filter(m_user.User.user_name == username)
134
+ .filter(
135
+ or_(
136
+ m_user.User.email == user_name_or_email,
137
+ m_user.User.user_name == user_name_or_email
138
+ )
139
+ )
164
140
  .first()
165
141
  )
142
+
166
143
  if not user or user.password != password:
167
- self.log.warning(f"Dataflow authentication failed for user: {username}")
144
+ self.log.warning(f"Dataflow authentication failed for user: {user_name_or_email}")
168
145
  return None
146
+
169
147
  session_id = self.get_or_create_session(user.user_id)
170
148
  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": {}}
149
+ self.log.info(f"Dataflow authentication successful for user: {user.user_name}")
150
+ return {"name": user.user_name, "session_id": session_id, "auth_state": {}}
173
151
  except Exception as e:
174
152
  self.log.error(f"Dataflow authentication error: {str(e)}")
175
153
  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,7 +1,7 @@
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
6
  from .environment import (Environment, LocalEnvironment, ArchivedEnvironment, JobLogs)
7
7
  from .project_details import ProjectDetails
@@ -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
9
  name = Column(String, unique=True, nullable=True)
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)
13
+ runtime = Column(Boolean, nullable=False, 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,6 +10,7 @@ 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)
@@ -21,5 +22,5 @@ class Connection(Base):
21
22
  is_active = Column(Boolean, 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,9 +8,8 @@ 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)
12
12
  subdomain = Column(String)
13
- spark_enabled = Column(Boolean, default=False)
14
13
  display_order = Column(Integer, default=0)
15
14
 
16
15
  role_zone_assocs = relationship("RoleZone", back_populates="zone")
@@ -1,4 +1,4 @@
1
- from sqlalchemy import Column, Integer, String, Boolean, Text, ForeignKey, DateTime
1
+ from sqlalchemy import Column, Integer, String, Boolean, Text, ForeignKey, DateTime, UniqueConstraint
2
2
  from sqlalchemy.orm import relationship
3
3
  from sqlalchemy.sql import func
4
4
  from datetime import datetime, timezone
@@ -16,7 +16,7 @@ class EnvironmentAttributes(Base):
16
16
  enabled = Column(Boolean, default=True)
17
17
  version = Column(String, default=0)
18
18
  is_latest = Column(Boolean, default=True)
19
- base_env_id = Column(Integer, default=None)
19
+ base_env_id = Column(Integer, nullable=True)
20
20
  short_name = Column(String(5))
21
21
  status = Column(String, default="Saved")
22
22
  icon = Column(String)
@@ -27,16 +27,15 @@ class EnvironmentAttributes(Base):
27
27
  r_requirements = Column(Text)
28
28
  created_date = Column(DateTime, server_default=func.now())
29
29
  created_by = Column(String)
30
-
31
-
30
+ org_id = Column(Integer, ForeignKey('ORGANIZATION.id'))
32
31
 
33
32
  class Environment(EnvironmentAttributes):
34
33
  __tablename__ = 'ENVIRONMENT'
35
-
34
+ __table_args__ = (UniqueConstraint('short_name', 'org_id', name='_env_short_name_org_uc'),)
36
35
  id = Column(Integer, primary_key=True, autoincrement=True)
37
- short_name = Column(String(5), unique=True)
38
36
 
39
- # Relationship with ArchivedEnvironment
37
+ # Relationships
38
+ organization = relationship("Organization", back_populates="environments")
40
39
  archived_versions = relationship("ArchivedEnvironment", back_populates="original_environment")
41
40
 
42
41
  class ArchivedEnvironment(EnvironmentAttributes):
@@ -44,23 +43,22 @@ class ArchivedEnvironment(EnvironmentAttributes):
44
43
 
45
44
  id = Column(Integer, primary_key=True, autoincrement=True)
46
45
  original_env_id = Column(Integer, ForeignKey('ENVIRONMENT.id', ondelete='CASCADE'))
47
- is_latest = Column(Boolean, default=False)
48
46
 
49
47
  # Relationship with Environment
50
48
  original_environment = relationship("Environment", back_populates="archived_versions")
51
49
 
52
-
53
-
54
50
  class JobLogs(Base):
55
51
  __tablename__ = "JOB_LOG"
52
+ __table_args__ = (UniqueConstraint('log_file_name', 'org_id', name='_job_log_file_org_uc'),)
56
53
 
57
54
  id = Column(Integer, primary_key=True, index=True)
58
55
  created_at = Column(DateTime, default=datetime.now)
59
56
  completed_at = Column(DateTime, nullable=True)
60
- log_file_name = Column(String, unique=True, nullable=False)
57
+ log_file_name = Column(String, nullable=False)
61
58
  log_file_location = Column(String, nullable=False)
62
59
  status = Column(String)
63
60
  created_by = Column(String)
61
+ org_id = Column(Integer, ForeignKey('ORGANIZATION.id', ondelete='CASCADE'))
64
62
 
65
63
 
66
64
  class LocalEnvironment(Base):
@@ -69,14 +67,15 @@ class LocalEnvironment(Base):
69
67
  id = Column(Integer, primary_key=True, autoincrement=True)
70
68
  name = Column(String, nullable=False, index=True)
71
69
  user_name = Column(String, ForeignKey('USER.user_name', ondelete='CASCADE'), nullable=False, index=True)
70
+ org_id = Column(Integer, ForeignKey('ORGANIZATION.id', ondelete='CASCADE'), nullable=False, index=True)
72
71
  py_version = Column(String)
73
72
  pip_libraries = Column(Text)
74
73
  conda_libraries = Column(Text)
75
74
  status = Column(String, default="Created")
76
- cloned_from = Column(String, ForeignKey('ENVIRONMENT.short_name', ondelete='SET NULL'), nullable=True)
75
+ cloned_from = Column(String, nullable=True)
77
76
  updated_at = Column(DateTime, default=datetime.now(timezone.utc), onupdate=datetime.now(timezone.utc))
78
77
  need_refresh = Column(Boolean, default=False)
79
78
 
80
79
  class EnvType(str, Enum):
81
80
  dataflow = "dataflow"
82
- local = "local"
81
+ local = "local"
@@ -8,11 +8,12 @@ class GitSSH(Base):
8
8
 
9
9
  id = Column(Integer, primary_key=True, index=True, autoincrement=True)
10
10
  user_name = Column(String, ForeignKey('USER.user_name', ondelete="CASCADE"), nullable=False)
11
+ org_id = Column(Integer, ForeignKey("ORGANIZATION.id"), index=True, nullable=False)
11
12
  description = Column(String)
12
13
  key_name = Column(String, nullable=False)
13
14
  created_date = Column(DateTime, server_default=func.now(), nullable=False)
14
15
  last_used_date = Column(DateTime)
15
16
 
16
17
  __table_args__ = (
17
- UniqueConstraint(user_name, key_name, name='user_name_key_name_unique'),
18
+ UniqueConstraint(user_name, key_name, org_id, name='user_name_key_name_org_id_unique'),
18
19
  )
@@ -0,0 +1,38 @@
1
+ from sqlalchemy import Column , Integer, String, Boolean, ForeignKey, UniqueConstraint, Enum
2
+ from sqlalchemy.orm import relationship
3
+ from dataflow.db import Base
4
+ import enum
5
+ from dataflow.models.environment import EnvType
6
+
7
+ class OrganizationUser(Base):
8
+ """
9
+ Association Table between USER, ROLE, and ORGANIZATION
10
+ """
11
+ __tablename__ = "ORGANIZATION_USER"
12
+ __table_args__ = (UniqueConstraint('org_id', 'user_id', name='uq_org_user'),)
13
+
14
+ org_id = Column(Integer, ForeignKey('ORGANIZATION.id', ondelete="CASCADE"), primary_key=True, nullable=False)
15
+ user_id = Column(Integer, ForeignKey('USER.user_id', ondelete="CASCADE"), primary_key=True, nullable=False)
16
+ role_id = Column(Integer, ForeignKey('ROLE.id', ondelete="SET NULL"))
17
+ active_env_short_name = Column(String, nullable=True)
18
+ active_env_type = Column(Enum(EnvType), nullable=True)
19
+ active_server_id = Column(Integer, ForeignKey('CUSTOM_SERVER.id', ondelete="SET NULL"))
20
+ show_server_page = Column(Boolean, default=True)
21
+ monthly_allocation = Column(Integer, default=0)
22
+
23
+ # Relationships
24
+ user = relationship("User", back_populates="org_user_assocs")
25
+ role = relationship("Role", back_populates="org_user_assocs")
26
+ organization = relationship("Organization", back_populates="org_user_assocs")
27
+
28
+ class OrganizationServer(Base):
29
+ __tablename__ = "ORGANIZATION_SERVER"
30
+
31
+ org_id = Column(Integer, ForeignKey('ORGANIZATION.id', ondelete="CASCADE"), primary_key=True, nullable=False)
32
+ server_id = Column(Integer, ForeignKey('SERVER_CONFIG.id', ondelete="CASCADE"), primary_key=True, nullable=False)
33
+
34
+ class OrganizationAppType(Base):
35
+ __tablename__ = "ORGANIZATION_APP_TYPE"
36
+
37
+ org_id = Column(Integer, ForeignKey('ORGANIZATION.id', ondelete="CASCADE"), primary_key=True, nullable=False)
38
+ app_type_id = Column(Integer, ForeignKey('APP_TYPE.id', ondelete="CASCADE"), primary_key=True, nullable=False)
@@ -0,0 +1,79 @@
1
+ from sqlalchemy import (
2
+ Column, Integer, String, Enum, DateTime, ForeignKey, Index
3
+ )
4
+ import uuid, enum
5
+ from sqlalchemy.dialects.postgresql import JSONB, UUID
6
+ from sqlalchemy.sql import func
7
+ from sqlalchemy.orm import relationship
8
+ from datetime import datetime
9
+ from dataflow.db import Base
10
+
11
+
12
+ class Organization(Base):
13
+ """
14
+ Organization model for the database.
15
+ """
16
+ __tablename__ = "ORGANIZATION"
17
+
18
+ id = Column(Integer, primary_key=True, autoincrement=True)
19
+ uid = Column(UUID(as_uuid=True), default=uuid.uuid4, nullable=False, unique=True)
20
+ name = Column(String(255), nullable=False, unique=True)
21
+ invite_code = Column(String(64), nullable=False, unique=True)
22
+ email_domain = Column(String(255), nullable=False, unique=True)
23
+ spark_enabled_zones = Column(JSONB, default=func.json([])) # List of zone IDs where Spark is enabled
24
+ created_at = Column(DateTime, default=datetime.utcnow)
25
+
26
+ # Association object link
27
+ org_user_assocs = relationship("OrganizationUser", back_populates="organization", cascade="all, delete-orphan")
28
+ custom_servers = relationship("CustomServerConfig")
29
+ onboarding_requests = relationship("UserOnboarding", back_populates="organization", cascade="all, delete-orphan")
30
+ servers = relationship("ServerConfig", secondary="ORGANIZATION_SERVER", back_populates="organizations")
31
+ apps = relationship("AppType", secondary="ORGANIZATION_APP_TYPE", back_populates="organizations")
32
+ roles = relationship("Role", cascade="all, delete-orphan")
33
+ environments = relationship("Environment", back_populates="organization")
34
+
35
+ class OnboardingStatus(enum.Enum):
36
+ pending = 'pending'
37
+ rejected = 'rejected'
38
+ accepted = 'accepted'
39
+
40
+ class OrganizationOnboarding(Base):
41
+ __tablename__ = 'ORGANIZATION_ONBOARDING'
42
+ # This prevents an org from having more than one active ('pending' or 'accepted') application
43
+ # while allowing multiple 'rejected' entries.
44
+ __table_args__ = (
45
+ Index(
46
+ 'idx_pending_org_application',
47
+ 'name',
48
+ unique=True,
49
+ postgresql_where=Column('status').in_([
50
+ OnboardingStatus.pending.value,
51
+ OnboardingStatus.accepted.value
52
+ ])
53
+ ),
54
+ )
55
+
56
+ id = Column(Integer, primary_key=True, autoincrement=True)
57
+
58
+ name = Column(String(255), nullable=False)
59
+ age = Column(Integer, nullable=True)
60
+ domain = Column(String(255), nullable=True)
61
+ no_of_employees = Column(String(50), nullable=True)
62
+ address = Column(String(500), nullable=True)
63
+
64
+ admin_first_name = Column(String(100), nullable=False)
65
+ admin_last_name = Column(String(100), nullable=True)
66
+ admin_designation = Column(String(100), nullable=False)
67
+ admin_email = Column(String(255), nullable=False, unique=True)
68
+ admin_username = Column(String(100), nullable=False, unique=True)
69
+ admin_password = Column(String(255), nullable=False)
70
+
71
+ # New fields
72
+ discovery_source = Column(String(255), nullable=True)
73
+ additional_info = Column(String(1000), nullable=True)
74
+ size_of_data = Column(String(100), nullable=True)
75
+
76
+ user_id = Column(Integer, ForeignKey('USER.user_id'), nullable=False)
77
+ status = Column(Enum(OnboardingStatus), default=OnboardingStatus.pending, nullable=False)
78
+
79
+ user = relationship("User", back_populates="organization_onboarding")
@@ -1,23 +1,25 @@
1
- from sqlalchemy import Column, String, Enum, DateTime, Integer, func, ForeignKey
1
+ from sqlalchemy import Column, String, Enum, DateTime, Integer, func, ForeignKey, UniqueConstraint
2
2
  from sqlalchemy.orm import relationship
3
3
  from dataflow.db import Base
4
4
 
5
5
  class ProjectDetails(Base):
6
6
  __tablename__ = "PROJECT_DETAIL"
7
+ __table_args__ = (UniqueConstraint('org_id', 'slug', name='uq_project_org_slug'),)
7
8
 
8
9
  project_id = Column(Integer, primary_key=True, autoincrement=True)
9
10
  project_name = Column(String, nullable=False)
10
11
  git_url = Column(String)
11
12
  git_branch = Column(String, nullable=True)
12
13
  git_folder = Column(String, nullable=True)
13
- type = Column(String, ForeignKey('RUNTIME_APP_TYPE.name', ondelete="CASCADE"), nullable=False)
14
- slug = Column(String, nullable=False, unique=True)
14
+ type = Column(String, ForeignKey('APP_TYPE.name'), nullable=False)
15
+ slug = Column(String, nullable=False)
15
16
  runtime = Column(String, nullable=False)
16
- py_env = Column(String, nullable=True)
17
+ py_env = Column(Integer, nullable=True)
17
18
  launch_url = Column(String, nullable=True)
18
19
  status = Column(Enum("pending", "created" ,"deployed", "stopped", "failed", name="deployment_status"), default="created")
19
20
  last_deployed = Column(DateTime, nullable=True)
20
21
  created_at = Column(DateTime, nullable=False, server_default=func.now())
21
22
  created_by = Column(String, nullable=False)
23
+ org_id = Column(Integer, ForeignKey('ORGANIZATION.id', ondelete='CASCADE'), nullable=False)
22
24
 
23
- app_type = relationship("AppType")
25
+ app_type = relationship("AppType")
dataflow/models/role.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """models.py"""
2
- from sqlalchemy import Column, Integer, String, Enum
2
+ from sqlalchemy import Column, Integer, String, Enum, ForeignKey, UniqueConstraint
3
3
  from sqlalchemy.orm import relationship
4
4
  from dataflow.db import Base
5
5
  import enum
@@ -11,19 +11,25 @@ class BaseRoleField(enum.Enum):
11
11
 
12
12
  class Role(Base):
13
13
  """
14
- Table Role
14
+ Table ROLE
15
15
  """
16
16
 
17
17
  __tablename__='ROLE'
18
+ __table_args__ = (
19
+ UniqueConstraint('name', 'org_id', name='uq_role_name_org'),
20
+ )
18
21
 
19
22
  id = Column(Integer, primary_key=True, index=True, autoincrement=True, nullable=False)
20
- name = Column(String, unique=True, nullable=False)
23
+ name = Column(String, nullable=False)
24
+ org_id = Column(Integer, ForeignKey('ORGANIZATION.id'))
21
25
  description = Column(String, nullable=True)
22
26
  base_role = Column(Enum(BaseRoleField), nullable=False, default=BaseRoleField.user)
23
27
 
24
- users = relationship("User", back_populates="role_details", cascade="all, delete-orphan")
25
- role_server_assocs = relationship("RoleServer", back_populates="role")
28
+ # Relationships
26
29
  role_zone_assocs = relationship("RoleZone", back_populates="role")
30
+ org_user_assocs = relationship("OrganizationUser", back_populates="role", cascade="all, delete-orphan")
31
+ organization = relationship("Organization", back_populates="roles")
32
+ servers = relationship("CustomServerConfig", secondary="ROLE_SERVER", back_populates="roles")
27
33
 
28
34
  def __repr__(self):
29
35
  return f"<Role(id={self.id}, name='{self.name}', base_role='{self.base_role}')>"
@@ -5,10 +5,7 @@ from dataflow.db import Base
5
5
 
6
6
  class RoleServer(Base):
7
7
  __tablename__ = 'ROLE_SERVER'
8
- __table_args__ = (UniqueConstraint('role_id', 'server_id', name='_role_server_uc'),)
8
+ __table_args__ = (UniqueConstraint('role_id', 'custom_server_id', name='_role_server_uc'),)
9
9
 
10
10
  role_id = Column(Integer, ForeignKey('ROLE.id', ondelete="CASCADE"), nullable=False, primary_key=True)
11
- server_id = Column(Integer, ForeignKey('CUSTOM_SERVER.id', ondelete="CASCADE"), nullable=False, primary_key=True)
12
-
13
- role = relationship("Role", back_populates="role_server_assocs")
14
- server = relationship("CustomServerConfig", back_populates="role_server_assocs")
11
+ custom_server_id = Column(Integer, ForeignKey('CUSTOM_SERVER.id', ondelete="CASCADE"), nullable=False, primary_key=True)
@@ -19,15 +19,19 @@ class ServerConfig(Base):
19
19
  description = Column(Text, nullable=True)
20
20
  kubespawner_override = Column(JSONB, default=func.json({}))
21
21
 
22
+ # Relationships
23
+ organizations = relationship("Organization", secondary="ORGANIZATION_SERVER", back_populates="servers")
22
24
 
23
25
  class CustomServerConfig(Base):
24
26
  __tablename__ = "CUSTOM_SERVER"
25
27
 
26
28
  id = Column(Integer, primary_key=True, autoincrement=True)
27
29
  base_server_id = Column(Integer, ForeignKey(ServerConfig.id, ondelete="CASCADE"), nullable=False)
30
+ org_id = Column(Integer, ForeignKey('ORGANIZATION.id'), nullable=False)
28
31
  display_name = Column(String, nullable=False, unique=True, index=True)
29
32
  description = Column(Text, nullable=True)
30
33
 
31
34
  # Relationship to the server_config table
32
35
  server_config = relationship(ServerConfig)
33
- role_server_assocs = relationship("RoleServer", back_populates="server", cascade="all, delete-orphan")
36
+ organization = relationship("Organization", back_populates="custom_servers")
37
+ roles = relationship("Role", secondary="ROLE_SERVER", back_populates="servers")
dataflow/models/team.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """models.py"""
2
- from sqlalchemy import Column, Integer, String
2
+ from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint
3
3
  from sqlalchemy.orm import relationship
4
4
  from dataflow.db import Base
5
5
 
@@ -9,9 +9,15 @@ class Team(Base):
9
9
  """
10
10
 
11
11
  __tablename__='TEAM'
12
+ __table_args__ = (
13
+ UniqueConstraint('team_name', 'org_id', name='uc_team_name_org_id'),
14
+ )
12
15
 
13
16
  team_id = Column(Integer, primary_key=True, index=True, autoincrement=True, nullable=False)
14
- team_name = Column(String, unique=True, nullable=False)
17
+ team_name = Column(String, nullable=False)
18
+ org_id = Column(Integer, ForeignKey('ORGANIZATION.id', ondelete="CASCADE"), nullable=False)
15
19
  description = Column(String, nullable=True)
16
-
17
- user_team_assocs = relationship("UserTeam", back_populates="team")
20
+
21
+ # relationships
22
+ users = relationship("User", secondary="USER_TEAM", back_populates="teams")
23
+ organization = relationship("Organization")
dataflow/models/user.py CHANGED
@@ -1,31 +1,68 @@
1
1
  """models.py"""
2
- from sqlalchemy import Column, Integer, String, Boolean, LargeBinary, ForeignKey
2
+ from sqlalchemy import Column, Integer, String, Boolean, LargeBinary, Enum, ForeignKey, DateTime, func
3
+ from sqlalchemy.dialects.postgresql import ENUM
4
+ from sqlalchemy import Index
3
5
  from sqlalchemy.orm import relationship
4
6
  from dataflow.db import Base
7
+ import enum
5
8
 
6
9
  class User(Base):
7
10
  """
8
11
  Table USER
9
12
  """
10
-
11
- __tablename__='USER'
13
+ __tablename__ = 'USER'
12
14
 
13
15
  user_id = Column(Integer, primary_key=True, index=True, autoincrement=True, nullable=False)
14
16
  user_name = Column(String, unique=True, nullable=False)
15
17
  first_name = Column(String)
16
18
  last_name = Column(String)
17
19
  email = Column(String, unique=True)
18
- role_id = Column(Integer, ForeignKey('ROLE.id'), nullable=False)
19
20
  image = Column(LargeBinary)
20
21
  image_url = Column(String, nullable=True)
21
22
  active = Column(Boolean, nullable=False, default=True)
22
23
  password = Column(String, nullable=False)
23
- active_env = Column(String)
24
- active_env_type = Column(String, nullable=True)
25
- current_server = Column(String)
26
- show_server_page = Column(Boolean, default = True)
27
- monthly_allocation = Column(Integer, nullable=True, default=0)
24
+ active_org_id = Column(Integer, ForeignKey('ORGANIZATION.id'))
25
+
26
+ # Relationships
27
+ org_user_assocs = relationship("OrganizationUser", back_populates="user", cascade="all, delete-orphan")
28
+ teams = relationship("Team", secondary="USER_TEAM", back_populates="users")
29
+ onboarding_requests = relationship("UserOnboarding", back_populates="user", cascade="all, delete-orphan")
30
+ organization_onboarding = relationship("OrganizationOnboarding", back_populates="user", cascade="all, delete-orphan")
31
+
32
+
33
+ class OnboardingStatus(enum.Enum):
34
+ pending = 'pending'
35
+ rejected = 'rejected'
36
+ accepted = 'accepted'
37
+
38
+ class UserOnboarding(Base):
39
+ """
40
+ SQLAlchemy model for the "USER_ONBOARDING" table.
41
+ This table stores user applications to organizations.
42
+ """
43
+ __tablename__ = "USER_ONBOARDING"
44
+ # This prevents a user from having more than one active ('pending' or 'accepted') application
45
+ # for a given organization, while allowing multiple 'rejected' entries.
46
+ __table_args__ = (
47
+ Index(
48
+ 'idx_pending_user_org_application',
49
+ 'user_id',
50
+ 'org_id',
51
+ unique=True,
52
+ postgresql_where=Column('status').in_([
53
+ OnboardingStatus.pending.value,
54
+ OnboardingStatus.accepted.value
55
+ ])
56
+ ),
57
+ )
28
58
 
29
- role_details = relationship("Role")
59
+ id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
60
+ user_id = Column(Integer, ForeignKey("USER.user_id", ondelete="CASCADE"), nullable=False)
61
+ org_id = Column(Integer, ForeignKey("ORGANIZATION.id", ondelete="CASCADE"), nullable=False)
62
+ status = Column(Enum(OnboardingStatus, name='onboarding_status'), nullable=False, default=OnboardingStatus.pending.value)
63
+ created_at = Column(DateTime, default=func.now(), nullable=False)
64
+ updated_at = Column(DateTime, default=func.now(), onupdate=func.now(), nullable=False)
30
65
 
31
- user_team_assocs = relationship("UserTeam", back_populates="user")
66
+ # Relationships
67
+ user = relationship("User", back_populates="onboarding_requests")
68
+ organization = relationship("Organization", back_populates="onboarding_requests")
@@ -8,7 +8,4 @@ class UserTeam(Base):
8
8
  __table_args__ = (UniqueConstraint('user_id', 'team_id', name='_user_team_uc'),)
9
9
 
10
10
  user_id = Column(Integer, ForeignKey('USER.user_id', ondelete="CASCADE"), nullable=False, primary_key=True)
11
- team_id = Column(Integer, ForeignKey('TEAM.team_id', ondelete="CASCADE"), nullable=False, primary_key=True)
12
-
13
- user = relationship("User", back_populates="user_team_assocs")
14
- team = relationship("Team", back_populates="user_team_assocs")
11
+ team_id = Column(Integer, ForeignKey('TEAM.team_id', ondelete="CASCADE"), nullable=False, primary_key=True)
@@ -14,7 +14,8 @@ class Variable(Base):
14
14
  __tablename__ = 'VARIABLE'
15
15
 
16
16
  id = Column(Integer, primary_key=True, index=True, autoincrement=True, nullable=False)
17
- key = Column(String, nullable=False)
17
+ key = Column(String, index=True, nullable=False)
18
+ org_id = Column(Integer, ForeignKey("ORGANIZATION.id"), index=True, nullable=False)
18
19
  value = Column(Text, nullable=False)
19
20
  type = Column(String, nullable=False)
20
21
  description = Column(Text, nullable=True)
@@ -30,5 +31,5 @@ class Variable(Base):
30
31
 
31
32
  __table_args__ = (
32
33
  CheckConstraint(type.in_(['variable', 'secret']), name='check_variable_type'),
33
- UniqueConstraint('key', 'runtime', 'slug', 'created_by', name='unique_key'),
34
+ UniqueConstraint('key', 'org_id', 'runtime', 'slug', 'created_by', name='unique_key'),
34
35
  )
@@ -117,27 +117,29 @@ class _BaseAccessor(ABC):
117
117
  # These classes implement the logic for building the vault path based on the context.
118
118
  # --------------------------------------------------------------------------
119
119
  class _RuntimeAccessor(_BaseAccessor):
120
- def __init__(self, secret_manager: SecretManager, runtime_env: str, slug: str = None):
120
+ def __init__(self, secret_manager: SecretManager, org_id: str, runtime_env: str, slug: str = None):
121
121
  super().__init__(secret_manager)
122
+ self.org_id = org_id
122
123
  self.runtime_env = runtime_env
123
124
  self.slug = slug
124
125
 
125
126
  def _get_vault_path(self, secret_type: str, key: str) -> str:
126
127
  # Special case for git-ssh in runtime context
127
128
  if secret_type == "git-ssh":
128
- return f"{self.runtime_env}-{secret_type}-{self.slug}"
129
+ return f"{self.org_id}-{self.runtime_env}-{secret_type}-{self.slug}"
129
130
 
130
131
  # Standard format for all other secret types
131
132
  context = self.slug if self.slug else "global"
132
- return f"{self.runtime_env}-{context}-{secret_type}-{key}"
133
+ return f"{self.org_id}-{self.runtime_env}-{context}-{secret_type}-{key}"
133
134
 
134
135
  class _StudioAccessor(_BaseAccessor):
135
- def __init__(self, secret_manager: SecretManager, user_name: str):
136
+ def __init__(self, secret_manager: SecretManager, org_id: str, user_name: str):
136
137
  super().__init__(secret_manager)
138
+ self.org_id = org_id
137
139
  self.user_name = user_name
138
140
 
139
141
  def _get_vault_path(self, secret_type: str, key: str) -> str:
140
- return f"{self.user_name}-{secret_type}-{key}"
142
+ return f"{self.org_id}-{self.user_name}-{secret_type}-{key}"
141
143
 
142
144
  # --------------------------------------------------------------------------
143
145
  # PUBLIC INTERFACE CLASS
@@ -147,10 +149,10 @@ class SecretsService:
147
149
  def __init__(self, secret_manager: SecretManager):
148
150
  self._secret_manager = secret_manager
149
151
 
150
- def runtime(self, env: str, slug: str = None) -> _RuntimeAccessor:
152
+ def runtime(self, org_id: int, env: str, slug: str = None) -> _RuntimeAccessor:
151
153
  """Sets the context to RUNTIME and returns the appropriate accessor."""
152
- return _RuntimeAccessor(self._secret_manager, env, slug)
154
+ return _RuntimeAccessor(self._secret_manager, str(org_id), env, slug)
153
155
 
154
- def studio(self, user: str) -> _StudioAccessor:
156
+ def studio(self, org_id: int, user: str) -> _StudioAccessor:
155
157
  """Sets the context to STUDIO and returns the appropriate accessor."""
156
- return _StudioAccessor(self._secret_manager, user)
158
+ return _StudioAccessor(self._secret_manager, str(org_id), user)
@@ -1,37 +1,60 @@
1
- from fastapi import HTTPException, status
1
+ from fastapi import HTTPException
2
2
  from sqlalchemy.orm import Session
3
- from dataflow.models import user as m_user
4
- from dataflow.models import session as m_session
3
+ from sqlalchemy import and_
4
+ from dataflow.models import (
5
+ user as m_user,
6
+ session as m_session,
7
+ org_associations as m_org_associations,
8
+ role as m_role,
9
+ organization as m_organization
10
+ )
5
11
 
6
12
  def get_user_from_session(session_id: str, db: Session):
7
13
  """
8
- Retrieve a user based on session ID.
9
-
10
- Args:
11
- session_id (str): The unique session identifier
12
- db (Session): Database session
13
-
14
- Returns:
15
- User: User object if found
16
-
17
- Raises:
18
- HTTPException: If session is invalid or user not found
14
+ Retrieve user information based on the session ID.
19
15
  """
20
- session_record = db.query(m_session.Session).filter(m_session.Session.session_id == session_id).first()
21
- if not session_record:
22
- raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid session")
16
+ user = (
17
+ db.query(
18
+ m_user.User,
19
+ m_org_associations.OrganizationUser.role_id,
20
+ m_role.Role.name,
21
+ m_role.Role.base_role,
22
+ m_org_associations.OrganizationUser.active_server_id,
23
+ m_org_associations.OrganizationUser.show_server_page,
24
+ m_org_associations.OrganizationUser.active_env_short_name,
25
+ m_org_associations.OrganizationUser.active_env_type,
26
+ m_org_associations.OrganizationUser.monthly_allocation,
27
+ m_organization.Organization.uid
28
+ )
29
+ .join(m_session.Session, m_session.Session.user_id == m_user.User.user_id)
30
+ .outerjoin(
31
+ m_org_associations.OrganizationUser,
32
+ and_(
33
+ m_org_associations.OrganizationUser.user_id == m_user.User.user_id,
34
+ m_org_associations.OrganizationUser.org_id == m_user.User.active_org_id
35
+ )
36
+ )
37
+ .outerjoin(m_role.Role, m_role.Role.id == m_org_associations.OrganizationUser.role_id)
38
+ .outerjoin(
39
+ m_organization.Organization,
40
+ m_organization.Organization.id == m_user.User.active_org_id # join to Organization
41
+ )
42
+ .filter(m_session.Session.session_id == session_id)
43
+ .first()
44
+ )
23
45
 
24
- user = db.query(m_user.User).filter(m_user.User.user_id == session_record.user_id).first()
25
46
  if not user:
26
- raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found")
27
-
28
- base_role = user.role_details.base_role
29
- role_id = user.role_details.id
30
- role_name = user.role_details.name
31
- user.base_role = base_role
32
- user.role = role_name
33
- user.role_id = role_id
34
-
35
- return user
47
+ raise HTTPException(status_code=401, detail="Invalid session")
36
48
 
49
+ user_obj, role_id, role_name, base_role, active_server_id, show_server_page, active_env_short_name, active_env_type, monthly_allocation, uid = user
50
+ user_obj.role_id = role_id
51
+ user_obj.role = role_name
52
+ user_obj.base_role = base_role
53
+ user_obj.current_server_id = active_server_id
54
+ user_obj.show_server_page = show_server_page
55
+ user_obj.active_env = active_env_short_name
56
+ user_obj.active_env_type = active_env_type
57
+ user_obj.monthly_allocation = monthly_allocation
58
+ user_obj.org_uid = str(uid)
37
59
 
60
+ return user_obj
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dataflow-core
3
- Version: 2.1.15rc1
3
+ Version: 2.1.15rc2
4
4
  Summary: Dataflow core package
5
5
  Author: Dataflow
6
6
  Author-email:
@@ -1,37 +1,38 @@
1
1
  authenticator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  authenticator/dataflowairflowauthenticator.py,sha256=gEdCiL2yJQ7lYvAwbrjcAkccVMfehoMJldw9eU7cc2s,2243
3
- authenticator/dataflowhubauthenticator.py,sha256=wI9S-1pcav_WHUL4ibEn-HLhjOtZebQbqXTkcrXXFAA,13463
3
+ authenticator/dataflowhubauthenticator.py,sha256=drs6M9tp0QFLzmj-eNaa6zefKat9cN0cJvwGLrSeYvQ,12531
4
4
  authenticator/dataflowsupersetauthenticator.py,sha256=NkAmDaIc-ui-qEolu4xz_UY7P_2g8111hwNjPvAOW1Q,2839
5
5
  dataflow/__init__.py,sha256=WTRg8HMpMWSgxYJ9ZGVldx4k07fAbta3mBmZ1hG9mWE,30
6
6
  dataflow/configuration.py,sha256=7To6XwH1eESiYp39eqPcswXWwrdBUdPF6xN6WnazOF0,663
7
7
  dataflow/database_manager.py,sha256=tJHMuOZ9Muskrh9t4uLRlTuFU0VkHAzoHlGP5DORIC4,899
8
- dataflow/dataflow.py,sha256=0kPDIpFgrEcK81QYeLQGb-rQTrAH-83gLpn566yvBGA,14004
8
+ dataflow/dataflow.py,sha256=EzeOMtMCqG0WJ0vld6ArKbKNFAmA9sbjBqVJ8sd2sQw,13026
9
9
  dataflow/db.py,sha256=73ojGqpCTRVTlPszD73Ozhjih_BI2KTHmazqxxL6iWk,3780
10
- dataflow/environment.py,sha256=qiyuRRPpVLVWiYccRHXnGyWMr_ZBPWzixAyDtAzxQYE,28277
11
- dataflow/models/__init__.py,sha256=5Ai-sUHtT1dtc0l4nL1fuwVbx0GcwaGQg_LXqOzWb4c,981
12
- dataflow/models/app_types.py,sha256=yE_ZB13lhpK7AZ7PyBwnQlf0RlIHYs_-vdMKx7_RMlY,379
10
+ dataflow/environment.py,sha256=7XiSojSIOnMOz5YVk-k6w1yQCKbBTTvUxv5cnn6cqJg,28830
11
+ dataflow/models/__init__.py,sha256=UKaiCxJJLXvWOqZlHmDWr9fTwBncKbnyjSIsPWKvlVI,1167
12
+ dataflow/models/app_types.py,sha256=NMHUHBoGnxYHbtlBVppNqQ6NW2T6pSedmQ6-T4t2JAc,640
13
13
  dataflow/models/blacklist_library.py,sha256=B2oi3Z8GcR_glhLAyinFk0W8c9txXvm3uOER6dY-q7I,991
14
- dataflow/models/connection.py,sha256=_VJL3KuIrm8t4lJmtunIL3-AXF9Yvi5wUolzdR3tE0E,1017
15
- dataflow/models/dataflow_zone.py,sha256=yFCvQXos5M1cU7ksbVSO338_RkT3hkdw2wr3kCJ_rec,769
16
- dataflow/models/environment.py,sha256=3W-Pvkuiufjw0MWy9F6uWSd0HDPjRNNJe3TnhpHxcGg,2920
14
+ dataflow/models/connection.py,sha256=Kv0vo4V5z7EBfJUSnXWxKOB-AXPWIte9ttauNdM5lyk,1127
15
+ dataflow/models/dataflow_zone.py,sha256=ScCM7963Rd5G4e4eFQGF0gNeg6abaWEwIgGOqoezfrg,719
16
+ dataflow/models/environment.py,sha256=Pnma45hzDdr5jQ0k6mSAH6eZQGTQWm87AnebovtVBdU,3263
17
17
  dataflow/models/environment_status.py,sha256=lvPDNUsUoTW9D97B07aKqJQHRKp4LvPM28pQDMPH1ac,536
18
- dataflow/models/git_ssh.py,sha256=W15SDypxzGOz_aZkHEnVZ6DIMVsjAsbSIXVIEt2mPYU,694
18
+ dataflow/models/git_ssh.py,sha256=xRkzK8lZ4Fa25zhE3Y9J78BiWv6xtd10YDBNf1XDKXE,797
19
+ dataflow/models/org_associations.py,sha256=Kw1zK-Rg97GBV6U61O5V57sohPTHSchalMDAelo2hUQ,1915
20
+ dataflow/models/organization.py,sha256=SGonZsaGRPu7Z9VLqFE32l68mwLdlki-OLxqdMpHtyU,3353
19
21
  dataflow/models/pinned_projects.py,sha256=I-XMQq7__XJJi2lyOdEvQEfhPRz8D6KHA6Cbavbf05o,606
20
22
  dataflow/models/pod_activity.py,sha256=4NQplUtckS4d2Uc-Iyi8PfgciblHiExbXo_VTMaukB8,751
21
23
  dataflow/models/pod_session_history.py,sha256=-O8DHtH1AtGLntJ1shI6eiciTJGL_vYvG3gfupvr0zY,778
22
- dataflow/models/project_details.py,sha256=iNb95L3UGjcC_Ws6FiUTDANCUFhYWpnsKraS6rmCyRU,1098
24
+ dataflow/models/project_details.py,sha256=pjunEIjgFW66N7kP23cVtRU37LVOnKaqT8K7WBekAVg,1260
23
25
  dataflow/models/recent_project_studio.py,sha256=m12KGCsv453C1ijHjfVD8E7cJ7Og_0N8uc7_9VlfkYw,812
24
26
  dataflow/models/recent_projects.py,sha256=OFd5MSRXVRHs9UbvUNoJBBnh9rgsJ0lwE23wm5_Hc5w,321
25
- dataflow/models/role.py,sha256=0fgLjCx7aETRwCV5SW0-4PQBWzWddWO9a_ObJA14VOY,962
26
- dataflow/models/role_server.py,sha256=mMcfjsGX1cY8hOAOBBmrZgw8ozdfuvjKJoBlR6F0Kdc,689
27
+ dataflow/models/role.py,sha256=LQoEWSc_Pm79IjNC1uFKGFfkhaZkhI3JSq6Sj6Xrm_g,1266
28
+ dataflow/models/role_server.py,sha256=Qz9VqGSpVGvfLz9_lKITUfkiuR7pmVc8SIIfYrDPDso,547
27
29
  dataflow/models/role_zone.py,sha256=uH8JheWyzUWcRECRzYuzdphUAUXeU5zRVA1Bhuz3h9w,772
28
- dataflow/models/server_config.py,sha256=8ocKT8tPen9tedO8BLPEfkWxUEIHaqvA2L-qEhrFND0,1385
30
+ dataflow/models/server_config.py,sha256=Npf4xP7zQ6eXp9kqwM9Owsm8C0e8Qw6jei2YX5k0in0,1646
29
31
  dataflow/models/session.py,sha256=c8TI6qXsM8utzp5vSQtAOXJSbgasnyu-a0qSAvA-rWs,459
30
- dataflow/models/team.py,sha256=fjkqF0N4PSwSYTgHjEQl9wuC7yumd0iOb5nNFePI6q4,488
31
- dataflow/models/user.py,sha256=-XEpulg7UAL-WDMyigPasDS61btna2mKtJPsanWA4lw,1108
32
- dataflow/models/user_environment.py,sha256=yI9NutULcLiwlycuEin6ROe6o1Sjdv_sgw2MEkJFeYg,568
33
- dataflow/models/user_team.py,sha256=r_fmKvf6JuGgiiI9TXWjVG2QZ3WOvDrOwYWVQ3r8oWo,659
34
- dataflow/models/variables.py,sha256=thWqX9KbR2x0oBI-bVcW5TV1eKf0TYNxgGmZWTUs2g4,1414
32
+ dataflow/models/team.py,sha256=u5VKOxi-0wGgxssIhXr1Ydn3X9IEAHNWxz029KaVm5g,780
33
+ dataflow/models/user.py,sha256=XSd3PuqMRzm4qglNaUF0S-9RjH_f016eZX4I0iDtq2Q,2856
34
+ dataflow/models/user_team.py,sha256=vD6BsiMfx1iIMQiT8fpwhsQ8AGuPuBO83F22Cy7j-nU,524
35
+ dataflow/models/variables.py,sha256=v6a1r5OgmKOAlyVdmOFM6Axre7DEBEsgrF4yTV5h3U0,1524
35
36
  dataflow/schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
37
  dataflow/schemas/connection.py,sha256=ut2sqz06yOjmFKzHry92FEt7DN09Bj30GYse35__Cuw,2467
37
38
  dataflow/schemas/git_ssh.py,sha256=N1O7HM6ZbygIBZn2rKvNR0e7IM3ZJMAH6aJtjaghDr0,1283
@@ -42,17 +43,17 @@ dataflow/scripts/update_environment.sh,sha256=2dtn2xlNi6frpig-sqlGE1_IKRbbkqYOCp
42
43
  dataflow/secrets_manager/__init__.py,sha256=idGqIDtYl0De2WIK9Obl-N7SDPSYtVM0D-wXfZjCiy4,559
43
44
  dataflow/secrets_manager/factory.py,sha256=LblshkGG9q2C3RHYp0QykianUtpOOQz7sBdlerutyWY,2479
44
45
  dataflow/secrets_manager/interface.py,sha256=HhrKpQrprWIbDsVfU_qc59OXmSIuHXv106OXv6-Epqc,506
45
- dataflow/secrets_manager/service.py,sha256=SSWgTXJTAwVPqMIc76cB2hR6nghNVOoMpIN9M0i7Su0,7241
46
+ dataflow/secrets_manager/service.py,sha256=A4PgiSS1bzlzXPi_GvsiepChULqAa5yONGkaU-d5MLM,7419
46
47
  dataflow/secrets_manager/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
48
  dataflow/secrets_manager/providers/aws_manager.py,sha256=16peXyKeuAjv2RVTMUjrzArPYENK9Zu7jREWVgMfScA,8671
48
49
  dataflow/secrets_manager/providers/azure_manager.py,sha256=sWOz-7ALnLt6vyM3lt14GBpzpmDnlH3hkdqtuApqkgU,9430
49
50
  dataflow/secrets_manager/providers/gcp_manager.py,sha256=AJgotHZRQraxtmfJX1Z8u2Gcr7KJLRJTN_qbth3A5Xk,13738
50
51
  dataflow/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
52
  dataflow/utils/exceptions.py,sha256=8GRFoYZ5dPGQckVm2znaHpPi0ZAs69fK-RGKukEsapk,4432
52
- dataflow/utils/get_current_user.py,sha256=4nSO3SPVMZhW-MsIgxR3f9ZzrFaIZIuyrM6hvfyE7PQ,1202
53
+ dataflow/utils/get_current_user.py,sha256=iPyUpEpuxitdmFwvaPiRWIlII8JaaGoxUhQcmNWi0vI,2319
53
54
  dataflow/utils/logger.py,sha256=7BFrOq5Oiqn8P4XZbgJzMP5O07d2fpdECbbfsjrUuHw,1213
54
- dataflow_core-2.1.15rc1.dist-info/METADATA,sha256=aRoxdVVfP0yY6qPtPuSH_RGHqxAQ13HCTjLq0UxRnR0,443
55
- dataflow_core-2.1.15rc1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
56
- dataflow_core-2.1.15rc1.dist-info/entry_points.txt,sha256=ppj_EIbYrJJwCPg1kfdsZk5q1N-Ejfis1neYrnjhO8o,117
57
- dataflow_core-2.1.15rc1.dist-info/top_level.txt,sha256=SZsUOpSCK9ntUy-3Tusxzf5A2e8ebwD8vouPb1dPt_8,23
58
- dataflow_core-2.1.15rc1.dist-info/RECORD,,
55
+ dataflow_core-2.1.15rc2.dist-info/METADATA,sha256=t8vhx-r4NQ9rhkLblo_9sZRH1nvOnaYH7UpkqBPUaCw,443
56
+ dataflow_core-2.1.15rc2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
57
+ dataflow_core-2.1.15rc2.dist-info/entry_points.txt,sha256=ppj_EIbYrJJwCPg1kfdsZk5q1N-Ejfis1neYrnjhO8o,117
58
+ dataflow_core-2.1.15rc2.dist-info/top_level.txt,sha256=SZsUOpSCK9ntUy-3Tusxzf5A2e8ebwD8vouPb1dPt_8,23
59
+ dataflow_core-2.1.15rc2.dist-info/RECORD,,
@@ -1,16 +0,0 @@
1
- from sqlalchemy import Column, Integer, String, DateTime
2
- from sqlalchemy.sql.schema import ForeignKey
3
- from sqlalchemy.sql.expression import func
4
- from dataflow.db import Base
5
-
6
- class UserEnvironment(Base):
7
- """
8
- Table USER_ENVIRONMENT
9
- """
10
-
11
- __tablename__ = 'USER_ENVIRONMENT'
12
-
13
- id = Column(Integer, primary_key=True, index=True, autoincrement=True)
14
- user_id = Column(Integer, ForeignKey('USER.user_id', ondelete="CASCADE"), nullable=False)
15
- env_name = Column(String)
16
- timestamp = Column(DateTime, server_default=func.now(), onupdate=func.now())