dataflow-core 2.1.28__tar.gz → 2.1.29__tar.gz

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 (98) hide show
  1. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/PKG-INFO +1 -1
  2. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/authenticator/dataflowhubauthenticator.py +60 -59
  3. dataflow_core-2.1.29/authenticator/utils/verify_email_domain.py +67 -0
  4. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/models/__init__.py +5 -1
  5. dataflow_core-2.1.29/dataflow/models/active_server_view.py +27 -0
  6. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/models/app_types.py +2 -0
  7. dataflow_core-2.1.29/dataflow/models/container.py +54 -0
  8. dataflow_core-2.1.29/dataflow/models/inference_service.py +59 -0
  9. dataflow_core-2.1.29/dataflow/models/onboarding_project.py +54 -0
  10. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/models/org_associations.py +15 -0
  11. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/models/organization.py +2 -1
  12. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/models/project_details.py +1 -0
  13. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/models/recent_project_studio.py +15 -1
  14. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/models/role.py +1 -0
  15. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/models/server_config.py +3 -1
  16. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/models/user.py +8 -0
  17. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/secrets_manager/providers/gcp_manager.py +3 -0
  18. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/utils/get_current_user.py +4 -2
  19. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow_core.egg-info/PKG-INFO +1 -1
  20. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow_core.egg-info/SOURCES.txt +15 -0
  21. dataflow_core-2.1.29/dfmigration/versions/012_app_type_public_and_project_description.py +24 -0
  22. dataflow_core-2.1.29/dfmigration/versions/013_add_container_table.py +52 -0
  23. dataflow_core-2.1.29/dfmigration/versions/014_active_server_view_can_spawn.py +56 -0
  24. dataflow_core-2.1.29/dfmigration/versions/015_add_agent_enabled_org.py +27 -0
  25. dataflow_core-2.1.29/dfmigration/versions/016_org_server_visibility_controls.py +32 -0
  26. dataflow_core-2.1.29/dfmigration/versions/017_add_created_at_to_user.py +32 -0
  27. dataflow_core-2.1.29/dfmigration/versions/018_add_inference_service_table.py +67 -0
  28. dataflow_core-2.1.29/dfmigration/versions/019_user_onboarding_demo_selection.py +110 -0
  29. dataflow_core-2.1.29/dfmigration/versions/020_create_onboarding_project_table.py +129 -0
  30. dataflow_core-2.1.29/dfmigration/versions/__init__.py +0 -0
  31. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/setup.py +1 -1
  32. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/README.md +0 -0
  33. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/authenticator/__init__.py +0 -0
  34. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/authenticator/dataflowairflowauthenticator.py +0 -0
  35. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/authenticator/dataflowsupersetauthenticator.py +0 -0
  36. {dataflow_core-2.1.28/dataflow/schemas → dataflow_core-2.1.29/authenticator/utils}/__init__.py +0 -0
  37. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/__init__.py +0 -0
  38. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/configuration.py +0 -0
  39. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/database_manager.py +0 -0
  40. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/dataflow.py +0 -0
  41. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/db.py +0 -0
  42. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/environment.py +0 -0
  43. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/models/blacklist_library.py +0 -0
  44. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/models/connection.py +0 -0
  45. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/models/connection_type.py +0 -0
  46. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/models/dataflow_setting.py +0 -0
  47. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/models/dataflow_zone.py +0 -0
  48. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/models/environment.py +0 -0
  49. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/models/environment_status.py +0 -0
  50. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/models/git_ssh.py +0 -0
  51. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/models/otp.py +0 -0
  52. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/models/pinned_projects.py +0 -0
  53. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/models/pod_activity.py +0 -0
  54. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/models/pod_session_history.py +0 -0
  55. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/models/recent_projects.py +0 -0
  56. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/models/role_server.py +0 -0
  57. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/models/role_zone.py +0 -0
  58. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/models/session.py +0 -0
  59. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/models/subscription.py +0 -0
  60. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/models/team.py +0 -0
  61. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/models/user_team.py +0 -0
  62. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/models/variables.py +0 -0
  63. {dataflow_core-2.1.28/dataflow/secrets_manager/providers → dataflow_core-2.1.29/dataflow/schemas}/__init__.py +0 -0
  64. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/schemas/connection.py +0 -0
  65. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/schemas/git_ssh.py +0 -0
  66. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/schemas/secret.py +0 -0
  67. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/scripts/clone_environment.sh +0 -0
  68. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/scripts/create_environment.sh +0 -0
  69. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/secrets_manager/__init__.py +0 -0
  70. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/secrets_manager/factory.py +0 -0
  71. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/secrets_manager/interface.py +0 -0
  72. {dataflow_core-2.1.28/dataflow/utils → dataflow_core-2.1.29/dataflow/secrets_manager/providers}/__init__.py +0 -0
  73. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/secrets_manager/providers/aws_manager.py +0 -0
  74. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/secrets_manager/providers/azure_manager.py +0 -0
  75. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/secrets_manager/service.py +0 -0
  76. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/secrets_manager/utils.py +0 -0
  77. {dataflow_core-2.1.28/dfmigration → dataflow_core-2.1.29/dataflow/utils}/__init__.py +0 -0
  78. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/utils/blocked_domains.py +0 -0
  79. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/utils/exceptions.py +0 -0
  80. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow/utils/logger.py +0 -0
  81. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow_core.egg-info/dependency_links.txt +0 -0
  82. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow_core.egg-info/entry_points.txt +0 -0
  83. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow_core.egg-info/requires.txt +0 -0
  84. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dataflow_core.egg-info/top_level.txt +0 -0
  85. {dataflow_core-2.1.28/dfmigration/versions → dataflow_core-2.1.29/dfmigration}/__init__.py +0 -0
  86. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dfmigration/env.py +0 -0
  87. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dfmigration/versions/001_initial_baseline_migration.py +0 -0
  88. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dfmigration/versions/002_user_onboarding_migration.py +0 -0
  89. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dfmigration/versions/003_runtime_visibility.py +0 -0
  90. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dfmigration/versions/004_runtime_custom_image.py +0 -0
  91. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dfmigration/versions/005_session_table_migration.py +0 -0
  92. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dfmigration/versions/006_super_admin_migration.py +0 -0
  93. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dfmigration/versions/007_show_walkthrough_and_org_email_migration.py +0 -0
  94. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dfmigration/versions/008_costing_migration.py +0 -0
  95. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dfmigration/versions/009_session_migration.py +0 -0
  96. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dfmigration/versions/010_add_environment_job_table.py +0 -0
  97. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/dfmigration/versions/011_connection_type_migration.py +0 -0
  98. {dataflow_core-2.1.28 → dataflow_core-2.1.29}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dataflow-core
3
- Version: 2.1.28
3
+ Version: 2.1.29
4
4
  Summary: Dataflow core package
5
5
  Author: Dataflow
6
6
  Author-email:
@@ -12,6 +12,8 @@ from dataflow.configuration import ConfigurationManager
12
12
  import asyncio
13
13
  import httpx
14
14
 
15
+ from .utils.verify_email_domain import is_disposable_email
16
+
15
17
  class DataflowBaseAuthenticator(Authenticator):
16
18
  """Base Authenticator to handle Dataflow authentication and session management.
17
19
  Provides methods to authenticate users via Dataflow credentials, manage sessions.
@@ -177,7 +179,7 @@ class DataflowBaseAuthenticator(Authenticator):
177
179
  except Exception as e:
178
180
  self.log.warning(f"Error sending user creation email for {email}: {str(e)}")
179
181
 
180
- def create_new_user(self, email, first_name=None, last_name=None):
182
+ def create_new_user(self, email, first_name=None, last_name=None, handler=None):
181
183
 
182
184
  """Create a new user with Applicant role
183
185
 
@@ -185,6 +187,7 @@ class DataflowBaseAuthenticator(Authenticator):
185
187
  email (str): User's email address
186
188
  first_name (str): User's first name
187
189
  last_name (str): User's last name
190
+ handler: optional request handler to show a login_error page when signup is refused
188
191
 
189
192
  Returns:
190
193
  new_user (m_user.User|None): Created user object or None if creation failed
@@ -193,6 +196,16 @@ class DataflowBaseAuthenticator(Authenticator):
193
196
  try:
194
197
  # Normalize email to lowercase for consistency
195
198
  email = email.lower()
199
+
200
+ # Disallow disposable / temporary email addresses at signup
201
+ if is_disposable_email(email):
202
+ self.log.warning(f"Rejected signup for disposable email: {email}")
203
+ if handler is not None:
204
+ try:
205
+ self.login_error(handler, "Disposable or temporary email addresses cannot be used. Please use a personal or work email.", "Invalid Email")
206
+ except Exception as e:
207
+ self.log.warning(f"Failed to render disposable email error page: {e}")
208
+ return None
196
209
 
197
210
  username = self.extract_username_from_email(email)
198
211
  username = re.sub(r'[^a-z0-9]', '', username.lower())
@@ -229,6 +242,22 @@ class DataflowBaseAuthenticator(Authenticator):
229
242
  self.db.add(new_user)
230
243
  self.db.commit()
231
244
  self.db.refresh(new_user)
245
+
246
+ # Create initial USER_ONBOARDING entry with demo_launch_initialized = False
247
+ try:
248
+ if hasattr(m_user, "UserOnboarding"):
249
+ onboarding = m_user.UserOnboarding(
250
+ user_id=new_user.user_id,
251
+ demo_launch_initialized=False
252
+ )
253
+ self.db.add(onboarding)
254
+ self.db.commit()
255
+ else:
256
+ self.log.warning("UserOnboarding model not found in m_user; skipping onboarding entry creation")
257
+
258
+ except Exception as e:
259
+ self.log.warning(f"Failed to create USER_ONBOARDING entry for user {username}: {e}")
260
+ self.db.rollback()
232
261
 
233
262
  self.log.info(f"Created new user: {username} with email: {email}")
234
263
 
@@ -276,6 +305,33 @@ class DataflowBaseAuthenticator(Authenticator):
276
305
  # return False
277
306
 
278
307
  return True
308
+
309
+ def login_error(self, handler, message, title="Authentication Failed"):
310
+ """Custom error handler with simple centered design"""
311
+ html = f"""
312
+ <!DOCTYPE html>
313
+ <html>
314
+ <head>
315
+ <meta charset="UTF-8">
316
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
317
+ <title>{title}</title>
318
+ <link href='https://fonts.googleapis.com/css?family=Lato:400,600' rel='stylesheet'>
319
+ </head>
320
+ <body style="margin: 0; padding: 20px; font-family: 'Lato', Arial, sans-serif; background-color: #f8fafc; min-height: 100vh; display: flex; align-items: center; justify-content: center;">
321
+ <div style="background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); max-width: 450px; width: 100%; padding: 40px; text-align: center;">
322
+ <div style="width: 60px; height: 60px; background-color: #ffebee; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 20px; font-size: 30px; color: #d32f2f;">!</div>
323
+
324
+ <h1 style="font-size: 24px; font-weight: 600; color: #121926; margin: 0 0 12px 0;">{title}</h1>
325
+
326
+ <p style="font-size: 15px; color: #697586; line-height: 1.5; margin: 0 0 24px 0;">{message}</p>
327
+
328
+ <a href="/hub/login" style="display: inline-block; padding: 12px 32px; background-color: #30baba; color: white; text-decoration: none; border-radius: 6px; font-size: 14px; font-weight: 600; transition: background-color 0.3s;">Try Again</a>
329
+ </div>
330
+ </body>
331
+ </html>
332
+ """
333
+ handler.set_status(403)
334
+ handler.finish(html)
279
335
 
280
336
  async def authenticate_dataflow(self, handler, data):
281
337
 
@@ -328,7 +384,7 @@ class DataflowBaseAuthenticator(Authenticator):
328
384
  self.log.info(f"OTP validated and deleted for user: {user_email}")
329
385
  if not user:
330
386
  try:
331
- db_user = self.create_new_user(user_email)
387
+ db_user = self.create_new_user(user_email, handler=handler)
332
388
  if not db_user:
333
389
  self.log.error(f"Failed to create new user for email: {user_email}")
334
390
  self.login_error(handler, "Authentication error occurred. Please try again.")
@@ -389,35 +445,6 @@ class DataflowGoogleAuthenticator(DataflowBaseAuthenticator, GoogleOAuthenticato
389
445
  f"oauth_callback_url={self.oauth_callback_url}, "
390
446
  f"enable_dataflow_auth={self.enable_dataflow_auth}")
391
447
 
392
- def login_error(self, handler, message, title="Authentication Failed"):
393
- """Custom error handler with simple centered design"""
394
- html = f"""
395
- <!DOCTYPE html>
396
- <html>
397
- <head>
398
- <meta charset="UTF-8">
399
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
400
- <title>{title}</title>
401
- <link href='https://fonts.googleapis.com/css?family=Lato:400,600' rel='stylesheet'>
402
- </head>
403
- <body style="margin: 0; padding: 20px; font-family: 'Lato', Arial, sans-serif; background-color: #f8fafc; min-height: 100vh; display: flex; align-items: center; justify-content: center;">
404
- <div style="background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); max-width: 450px; width: 100%; padding: 40px; text-align: center;">
405
- <div style="width: 60px; height: 60px; background-color: #ffebee; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 20px; font-size: 30px; color: #d32f2f;">!</div>
406
-
407
- <h1 style="font-size: 24px; font-weight: 600; color: #121926; margin: 0 0 12px 0;">{title}</h1>
408
-
409
- <p style="font-size: 15px; color: #697586; line-height: 1.5; margin: 0 0 24px 0;">{message}</p>
410
-
411
- <a href="/hub/login" style="display: inline-block; padding: 12px 32px; background-color: #30baba; color: white; text-decoration: none; border-radius: 6px; font-size: 14px; font-weight: 600; transition: background-color 0.3s;">Try Again</a>
412
- </div>
413
- </body>
414
- </html>
415
- """
416
- handler.set_status(403)
417
- handler.finish(html)
418
-
419
-
420
-
421
448
  async def authenticate(self, handler, data):
422
449
 
423
450
  """Authenticate user using Google OAuth with Dataflow integration.
@@ -474,7 +501,7 @@ class DataflowGoogleAuthenticator(DataflowBaseAuthenticator, GoogleOAuthenticato
474
501
  self.log.info(f"Creating user with first_name='{first_name}', last_name='{last_name}' from Google data: {user_info}")
475
502
 
476
503
  try:
477
- db_user = self.create_new_user(email, first_name, last_name)
504
+ db_user = self.create_new_user(email, first_name, last_name, handler)
478
505
  if not db_user:
479
506
  self.log.error(f"Failed to create new user for email: {email}")
480
507
  self.login_error(handler, "Authentication error occurred. Please try again.")
@@ -543,32 +570,6 @@ class DataflowAzureAuthenticator(DataflowBaseAuthenticator, AzureAdOAuthenticato
543
570
  f"oauth_callback_url={self.oauth_callback_url}, "
544
571
  f"enable_dataflow_auth={self.enable_dataflow_auth}")
545
572
 
546
- def login_error(self, handler, message, title="Authentication Failed"):
547
- """Custom error handler with simple centered design"""
548
- html = f"""
549
- <!DOCTYPE html>
550
- <html>
551
- <head>
552
- <meta charset="UTF-8">
553
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
554
- <title>{title}</title>
555
- <link href='https://fonts.googleapis.com/css?family=Lato:400,600' rel='stylesheet'>
556
- </head>
557
- <body style="margin: 0; padding: 20px; font-family: 'Lato', Arial, sans-serif; background-color: #f8fafc; min-height: 100vh; display: flex; align-items: center; justify-content: center;">
558
- <div style="background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); max-width: 450px; width: 100%; padding: 40px; text-align: center;">
559
- <div style="width: 60px; height: 60px; background-color: #ffebee; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 20px; font-size: 30px; color: #d32f2f;">!</div>
560
-
561
- <h1 style="font-size: 24px; font-weight: 600; color: #121926; margin: 0 0 12px 0;">{title}</h1>
562
-
563
- <p style="font-size: 15px; color: #697586; line-height: 1.5; margin: 0 0 24px 0;">{message}</p>
564
-
565
- <a href="/hub/login" style="display: inline-block; padding: 12px 32px; background-color: #30baba; color: white; text-decoration: none; border-radius: 6px; font-size: 14px; font-weight: 600; transition: background-color 0.3s;">Try Again</a>
566
- </div>
567
- </body>
568
- </html>
569
- """
570
- handler.set_status(403)
571
- handler.finish(html)
572
573
 
573
574
  async def authenticate(self, handler, data):
574
575
  """Authenticate user using Azure AD OAuth with Dataflow integration.
@@ -626,7 +627,7 @@ class DataflowAzureAuthenticator(DataflowBaseAuthenticator, AzureAdOAuthenticato
626
627
  self.log.info(f"Creating user with first_name='{first_name}', last_name='{last_name}' from Azure data: {user_info}")
627
628
 
628
629
  try:
629
- db_user = self.create_new_user(email, first_name, last_name)
630
+ db_user = self.create_new_user(email, first_name, last_name, handler)
630
631
  if not db_user:
631
632
  self.log.error(f"Failed to create new user for email: {email}")
632
633
  self.login_error(handler, "Authentication error occurred. Please try again.")
@@ -0,0 +1,67 @@
1
+ """Simple cached-file disposable email checker.
2
+
3
+ Behavior:
4
+ - Read domains from a local cache file (`CACHE`).
5
+ - Cache contents in memory and reload only when the file modification time changes.
6
+ - If the cache file is missing or unreadable, treat all emails as valid (returns False).
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ from typing import Set, Optional
12
+
13
+ CACHE = '/dataflow/app/constants/domains.txt'
14
+
15
+ _domains: Set[str] = set()
16
+ _last_mtime: Optional[float] = None
17
+
18
+
19
+ def _load_cache(path: str) -> bool:
20
+ """Load domains from `path` into the in-memory set.
21
+
22
+ Returns True if the cache file existed and was loaded (or already up-to-date),
23
+ False if the file did not exist or could not be read.
24
+ """
25
+ global _domains, _last_mtime
26
+ try:
27
+ if not os.path.exists(path):
28
+ _domains = set()
29
+ _last_mtime = None
30
+ return False
31
+
32
+ mtime = os.path.getmtime(path)
33
+ if _last_mtime is None or mtime != _last_mtime:
34
+ # (re)load file
35
+ domains: Set[str] = set()
36
+ with open(path, 'r') as fh:
37
+ for ln in fh:
38
+ ln = ln.strip()
39
+ if not ln or ln.startswith('#'):
40
+ continue
41
+ domains.add(ln.lower())
42
+ _domains = domains
43
+ _last_mtime = mtime
44
+ return True
45
+ except Exception:
46
+ # On any error treat as missing so callers can decide (we return False)
47
+ _domains = set()
48
+ _last_mtime = None
49
+ return False
50
+
51
+
52
+ def is_disposable_email(email: str) -> bool:
53
+ """Check whether `email` belongs to a disposable domain.
54
+
55
+ - If the cache file is missing/unreadable: return True (treat as disposable).
56
+ - If email is invalid (no '@'): return False.
57
+ """
58
+ if not email or '@' not in email:
59
+ return False
60
+
61
+ cache_ok = _load_cache(CACHE)
62
+ if not cache_ok:
63
+ # No reliable domain list available allow all
64
+ return False
65
+
66
+ domain = email.split('@', 1)[1].lower()
67
+ return domain in _domains
@@ -18,7 +18,7 @@ from .environment_status import EnvironmentStatus
18
18
  from .user_team import UserTeam
19
19
  from .role_server import RoleServer
20
20
  from .variables import Variable
21
- from .recent_project_studio import RecentProjectStudio
21
+ from .recent_project_studio import RecentProjectStudio, AppLaunchStatus
22
22
  from .connection import Connection
23
23
  from .git_ssh import GitSSH
24
24
  from .pod_activity import PodActivity
@@ -29,3 +29,7 @@ from .dataflow_setting import DataflowSetting
29
29
  from .subscription import Subscription, SubscriptionServer, Price, OrganizationCreditTransaction, OrganizationSubscription
30
30
  from .otp import UserOtp
31
31
  from .connection_type import ConnectionType
32
+ from .container import Container, ContainerStatus
33
+ from .active_server_view import ActiveServerView
34
+ from .inference_service import InferenceService, InferenceStatus
35
+ from .onboarding_project import OnboardingProject
@@ -0,0 +1,27 @@
1
+ """active_server_view.py"""
2
+ from sqlalchemy import Column, Integer, String, Boolean, ForeignKey, DateTime, func
3
+ from dataflow.db import Base
4
+
5
+
6
+ class ActiveServerView(Base):
7
+ """Table ACTIVE_SERVER_VIEW
8
+
9
+ Tracks which user's server the current user is viewing/accessing.
10
+ When a user switches to viewing a shared server, this record is upserted.
11
+ When viewing their own server, the record is deleted.
12
+
13
+ Attributes:
14
+ id (int): Primary key.
15
+ user_name (str): The logged-in user (the viewer). Unique — one active view per user.
16
+ viewing_user_name (str): The server owner whose server is being viewed.
17
+ can_spawn (bool): Whether the viewer has start/stop (spawn) permission on the viewed server.
18
+ updated_at (datetime): When this was last changed.
19
+ """
20
+
21
+ __tablename__ = 'ACTIVE_SERVER_VIEW'
22
+
23
+ id = Column(Integer, primary_key=True, autoincrement=True)
24
+ user_name = Column(String, ForeignKey('USER.user_name', ondelete='CASCADE'), unique=True, nullable=False, index=True)
25
+ viewing_user_name = Column(String, ForeignKey('USER.user_name', ondelete='CASCADE'), nullable=False)
26
+ can_spawn = Column(Boolean, nullable=False, default=True, server_default='true')
27
+ updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
@@ -11,6 +11,7 @@ class AppType(Base):
11
11
  name (str): Unique name identifier for the app type.
12
12
  display_name (str): Human-readable name for the app type.
13
13
  code_based (bool): Indicates if the app type is code-based.
14
+ is_public_allowed (bool): Indicates if the app type is allowed for public use.
14
15
  studio (bool): Indicates if the app type is associated with Dataflow Studio.
15
16
  runtime (bool): Indicates if the app type is a runtime application.
16
17
  organizations (list): Relationship to organizations using this app type.
@@ -25,6 +26,7 @@ class AppType(Base):
25
26
  name = Column(String, unique=True, nullable=False)
26
27
  display_name = Column(String, nullable=False)
27
28
  code_based = Column(Boolean, nullable=False)
29
+ is_public_allowed = Column(Boolean, nullable=False, default=True, server_default='true')
28
30
  studio = Column(Boolean, nullable=False, default=False, server_default='false')
29
31
  runtime = Column(Boolean, nullable=False, default=False, server_default='false')
30
32
 
@@ -0,0 +1,54 @@
1
+ from sqlalchemy import Column, Enum, Integer, String, ForeignKey, UniqueConstraint, Text
2
+ from dataflow.db import Base
3
+ import enum
4
+
5
+ class ContainerStatus(enum.Enum):
6
+ """Enum-like class for container deployment status."""
7
+ PENDING = "PENDING"
8
+ DEPLOYED = "DEPLOYED"
9
+ FAILED = "FAILED"
10
+ DELETING = "DELETING"
11
+ STOPPED = "STOPPED"
12
+
13
+ class Container(Base):
14
+ """TABLE 'CONTAINER'.
15
+
16
+ Attributes:
17
+ id (int): Primary key for the container.
18
+ name (str): Unique name identifier for the container.
19
+ image (str): The container image to deploy.
20
+ port (int): The port to expose.
21
+ env_vars (str): JSON string of environment variables.
22
+ command (str): JSON string of container command override.
23
+ cpu_limit (str): CPU resource limit (e.g., '1', '500m').
24
+ memory_limit (str): Memory resource limit (e.g., '2Gi', '512Mi').
25
+ status (ContainerStatus): Deployment status of the container.
26
+ url (str): URL where the container is accessible after deployment.
27
+
28
+ Relationships:
29
+ created_by (str): Foreign key referencing the USER table.
30
+ org_id (int): Foreign key referencing the ORGANIZATION table.
31
+
32
+ Constraints:
33
+ UniqueConstraint: Ensures unique combination of name and org_id.
34
+ """
35
+
36
+ __tablename__ = "CONTAINER"
37
+
38
+ id = Column(Integer, primary_key=True, autoincrement=True, unique=True)
39
+ name = Column(String, nullable=False)
40
+ slug = Column(String, nullable=False)
41
+ image = Column(String, nullable=False)
42
+ port = Column(Integer, nullable=True)
43
+ env_vars = Column(Text, nullable=True)
44
+ command = Column(Text, nullable=True)
45
+ cpu_limit = Column(String, nullable=True, default="1", server_default="1")
46
+ memory_limit = Column(String, nullable=True, default="2Gi", server_default="2Gi")
47
+ created_by = Column(String, ForeignKey('USER.user_name', ondelete="CASCADE"), nullable=False)
48
+ org_id = Column(Integer, ForeignKey("ORGANIZATION.id"), index=True, nullable=False)
49
+ status = Column(Enum(ContainerStatus), default=ContainerStatus.PENDING, server_default="PENDING", nullable=False)
50
+ url = Column(String, nullable=True)
51
+
52
+ __table_args__ = (
53
+ UniqueConstraint(slug, org_id, name='name_org_id_unique'),
54
+ )
@@ -0,0 +1,59 @@
1
+ from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint, Enum
2
+ from dataflow.db import Base
3
+ import enum
4
+
5
+ class InferenceStatus(enum.Enum):
6
+ """Enum-like class for inference service status."""
7
+ PENDING = "PENDING"
8
+ RUNNING = "RUNNING"
9
+ SUCCEEDED = "SUCCEEDED"
10
+ FAILED = "FAILED"
11
+
12
+ class InferenceService(Base):
13
+ """TABLE 'INFERENCE_SERVICE'
14
+
15
+ Attributes:
16
+ id (int): Primary key for the model.
17
+ slug (str): Unique identifier for the model.
18
+ model_name (str): Name of the model.
19
+ model_version (str): Version of the model.
20
+ description (str): Description of the model.
21
+ runtime (str): Runtime in which the model is deployed.
22
+ org_id (int): Foreign key referencing the ORGANIZATION table.
23
+ storage_uri (str): URI where the model artifacts are stored.
24
+ service_url (str): User-facing URL to access the inference service.
25
+ concurrency (int): Number of concurrent requests the model can handle.
26
+ min_replicas (int): Minimum number of replicas for the model deployment.
27
+ max_replicas (int): Maximum number of replicas for the model deployment.
28
+ cpu_limit (str): CPU resource limit for the model deployment
29
+ memory_limit (str): Memory resource limit for the model deployment
30
+ gpu_type (str): Type of GPU to use for the model deployment (if any)
31
+ gpu_count (str): Number of GPUs to use for the model deployment (if any)
32
+ status (InferenceStatus): Deployment status of the model inference service.
33
+
34
+ Constraints:
35
+ UniqueConstraint: Ensures unique combination of slug and runtime.
36
+ """
37
+ __tablename__ = "INFERENCE_SERVICE"
38
+
39
+ id = Column(Integer, primary_key=True, autoincrement=True, unique=True)
40
+ slug = Column(String, nullable=False)
41
+ model_name = Column(String, nullable=False)
42
+ model_version = Column(String, nullable=False)
43
+ description = Column(String, nullable=True)
44
+ runtime = Column(String, nullable=False)
45
+ org_id = Column(Integer, ForeignKey("ORGANIZATION.id"), index=True, nullable=False)
46
+ storage_uri = Column(String, nullable=False)
47
+ service_url = Column(String, nullable=True)
48
+ concurrency = Column(Integer, nullable=True, default=80)
49
+ min_replicas = Column(Integer, nullable=True, default=1)
50
+ max_replicas = Column(Integer, nullable=True, default=1)
51
+ cpu_limit = Column(String, nullable=True, default="500m", server_default="500m")
52
+ memory_limit = Column(String, nullable=True, default="2Gi", server_default="2Gi")
53
+ gpu_type = Column(String, nullable=True)
54
+ gpu_count = Column(String, nullable=True, default="0", server_default="0")
55
+ status = Column(Enum(InferenceStatus, name="inference_status"), default=InferenceStatus.PENDING, server_default="PENDING", nullable=False)
56
+
57
+ __table_args__ = (
58
+ UniqueConstraint('slug', 'runtime', name='slug_runtime_unique'),
59
+ )
@@ -0,0 +1,54 @@
1
+ from sqlalchemy import Column, String, Integer, DateTime, func
2
+ from sqlalchemy.dialects.postgresql import JSONB, ARRAY
3
+ from dataflow.db import Base
4
+
5
+
6
+ class OnboardingProject(Base):
7
+
8
+ """Represents an onboarding project template.
9
+
10
+ Attributes:
11
+ project_id (int): Primary key.
12
+ name (str): Human-friendly name for the onboarding project.
13
+ description (str): Optional description.
14
+ apps (dict): JSON map of app name -> relative path in project.
15
+ path_to_clone (str): Base path where the project is cloned inside Dataflow.
16
+ git_url (str): Repository URL used to clone onboarding project content.
17
+ git_branch (str): Branch to checkout for onboarding project content.
18
+ display_order (int): Card ordering priority in onboarding selection UI.
19
+ connections (dict): JSON map of connection id -> relative path in project.
20
+ variables (dict): JSON map of variable key -> value.
21
+ secrets (dict): JSON map of secret key -> value.
22
+ guide_steps (list[dict]): Ordered guide metadata with route, label and hint per step.
23
+ steps (list[str]): Ordered short onboarding journey steps.
24
+ created_at (datetime): Creation timestamp.
25
+ created_by (str): User who created the template.
26
+ """
27
+
28
+ __tablename__ = "ONBOARDING_PROJECT"
29
+
30
+ project_id = Column(Integer, primary_key=True, autoincrement=True)
31
+ name = Column(String, nullable=False, unique=True)
32
+ description = Column(String, nullable=True)
33
+ path_to_clone = Column(String, nullable=False)
34
+ git_url = Column(String, nullable=True)
35
+ git_branch = Column(String, nullable=True)
36
+ display_order = Column(Integer, nullable=False, server_default='100')
37
+
38
+ # Key-value configuration for onboarding resources
39
+ # apps example: {"streamlit": "Demo 1 - Flight Delay Analysis/streamlit"}
40
+ apps = Column(JSONB, nullable=True, server_default='{}')
41
+ # connections example: {"conn_id": "relative/path/to/db.sqlite"}
42
+ connections = Column(JSONB, nullable=True, server_default='{}')
43
+ variables = Column(JSONB, nullable=True, server_default='{}')
44
+ secrets = Column(JSONB, nullable=True, server_default='{}')
45
+ config_step = Column(ARRAY(String), nullable=True, server_default='{}')
46
+ environment = Column(String, nullable=True)
47
+ guide_steps = Column(JSONB, nullable=True, server_default='[]')
48
+ steps = Column(ARRAY(String), nullable=True, server_default='{}')
49
+
50
+ created_at = Column(DateTime, nullable=False, server_default=func.now())
51
+ created_by = Column(String, nullable=False)
52
+
53
+ def __repr__(self):
54
+ return f"<OnboardingProject(id={self.project_id}, name={self.name})>"
@@ -57,6 +57,21 @@ class OrganizationAppType(Base):
57
57
  org_id = Column(Integer, ForeignKey('ORGANIZATION.id', ondelete="CASCADE"), primary_key=True, nullable=False)
58
58
  app_type_id = Column(Integer, ForeignKey('APP_TYPE.id', ondelete="CASCADE"), primary_key=True, nullable=False)
59
59
 
60
+
61
+ class OrganizationServer(Base):
62
+
63
+ """TABLE 'ORGANIZATION_SERVER'
64
+
65
+ Attributes:
66
+ org_id (int): Foreign key referencing the ORGANIZATION table, also part of the primary key.
67
+ server_id (int): Foreign key referencing the SERVER_CONFIG table, also part of the primary key.
68
+ """
69
+
70
+ __tablename__ = "ORGANIZATION_SERVER"
71
+
72
+ org_id = Column(Integer, ForeignKey('ORGANIZATION.id', ondelete="CASCADE"), primary_key=True, nullable=False)
73
+ server_id = Column(Integer, ForeignKey('SERVER_CONFIG.id', ondelete="CASCADE"), primary_key=True, nullable=False)
74
+
60
75
  # class OrganizationSubscription(Base):
61
76
 
62
77
  # """TABLE 'ORGANIZATION_SUBSCRIPTION'
@@ -42,6 +42,7 @@ class Organization(Base):
42
42
  email_domain = Column(String(255), nullable=False)
43
43
  spark_enabled_zones = Column(JSONB, default=func.json([]), server_default=text("'[]'::jsonb")) # List of zone IDs where Spark is enabled
44
44
  agent_enabled = Column(Boolean, default=False, server_default='false')
45
+ agent_enabled_org = Column(Boolean, default=False, server_default='false')
45
46
  created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), server_default=func.now())
46
47
  allow_auto_user_onboarding = Column(Boolean, nullable=False, default=True, server_default="true")
47
48
  active = Column(Boolean, nullable=False, default=True, server_default="true")
@@ -54,7 +55,7 @@ class Organization(Base):
54
55
  # subscriptions = relationship("Subscription", secondary="ORGANIZATION_SUBSCRIPTION", back_populates="organizations")
55
56
  onboarding_requests = relationship("UserOnboarding", back_populates="organization", cascade="all, delete-orphan")
56
57
  invited_users = relationship("InvitedUser", back_populates="organization", cascade="all, delete-orphan")
57
- # servers = relationship("ServerConfig", secondary="ORGANIZATION_SERVER", back_populates="organizations")
58
+ servers = relationship("ServerConfig", secondary="ORGANIZATION_SERVER", back_populates="organizations")
58
59
  apps = relationship("AppType", secondary="ORGANIZATION_APP_TYPE", back_populates="organizations")
59
60
  roles = relationship("Role", cascade="all, delete-orphan")
60
61
  environments = relationship("Environment", back_populates="organization")
@@ -50,6 +50,7 @@ class ProjectDetails(Base):
50
50
  git_folder = Column(String, nullable=True)
51
51
  type = Column(String, ForeignKey('APP_TYPE.name'), nullable=False)
52
52
  slug = Column(String, nullable=False)
53
+ description = Column(String, nullable=True)
53
54
  runtime = Column(String, nullable=False)
54
55
  py_env = Column(Integer, nullable=True)
55
56
  launch_url = Column(String, nullable=True)
@@ -32,4 +32,18 @@ class RecentProjectStudio(Base):
32
32
 
33
33
  __table_args__ = (
34
34
  UniqueConstraint(user_name, project_path, app_name, name='user_name_project_path_app_name_unique'),
35
- )
35
+ )
36
+
37
+
38
+ class AppLaunchStatus(Base):
39
+ """TABLE 'APP_LAUNCH_STATUS'
40
+
41
+ Tracks the last launch attempt for each application.
42
+ """
43
+
44
+ __tablename__ = 'APP_LAUNCH_STATUS'
45
+
46
+ id = Column(Integer, primary_key=True, index=True, autoincrement=True)
47
+ app_name = Column(String, nullable=False, unique=True, index=True)
48
+ launch_status = Column(Boolean, nullable=False, default=False, server_default='false')
49
+ last_launch_time = Column(DateTime, server_default=func.now(), nullable=False)
@@ -12,6 +12,7 @@ class BaseRoleField(enum.Enum):
12
12
  user = "user"
13
13
  applicant = "applicant"
14
14
  ops = "ops"
15
+ viewer = "viewer"
15
16
 
16
17
  class Role(Base):
17
18
  """
@@ -35,10 +35,12 @@ class ServerConfig(Base):
35
35
  cpu = Column(String, nullable=False)
36
36
  gpu = Column(String)
37
37
  default = Column(Boolean, default=False, server_default='false')
38
+ available_to_all_orgs = Column(Boolean, default=True, server_default='true', nullable=False)
38
39
  tags = Column(JSONB, default=func.json([]), server_default=text("'[]'::jsonb"))
39
40
  description = Column(Text, nullable=True)
40
41
  kubespawner_override = Column(JSONB, default=func.json({}), server_default=text("'{}'::jsonb"))
41
42
 
42
43
  # Relationships
43
44
  subscriptions = relationship("Subscription", secondary="SUBSCRIPTION_SERVER", back_populates="servers")
44
- roles = relationship("Role", secondary="ROLE_SERVER", back_populates="servers")
45
+ roles = relationship("Role", secondary="ROLE_SERVER", back_populates="servers")
46
+ organizations = relationship("Organization", secondary="ORGANIZATION_SERVER", back_populates="servers")
@@ -21,6 +21,7 @@ class User(Base):
21
21
  active (bool): Flag indicating whether the user is active.
22
22
  password (str): Hashed password for the user.
23
23
  active_org_id (int): Foreign key referencing the active organization of the user.
24
+ created_at (datetime): Timestamp of when the user record was created.
24
25
 
25
26
  Relationships:
26
27
  org_user_assocs: Relationship to OrganizationUser model.
@@ -43,6 +44,7 @@ class User(Base):
43
44
  password = Column(String, nullable=True)
44
45
  active_org_id = Column(Integer, ForeignKey('ORGANIZATION.id'))
45
46
  is_super_admin = Column(Boolean, nullable=False, default=False, server_default='false')
47
+ created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), nullable=False, server_default=func.now())
46
48
 
47
49
  # Relationships
48
50
  org_user_assocs = relationship("OrganizationUser", back_populates="user", cascade="all, delete-orphan")
@@ -97,6 +99,12 @@ class UserOnboarding(Base):
97
99
  user_id = Column(Integer, ForeignKey("USER.user_id", ondelete="CASCADE"), nullable=False)
98
100
  org_id = Column(Integer, ForeignKey("ORGANIZATION.id", ondelete="CASCADE"), nullable=False)
99
101
  status = Column(Enum(OnboardingStatus, name='onboarding_status'), nullable=False, default=OnboardingStatus.pending.value, server_default='pending')
102
+ selected_role = Column(String, nullable=True)
103
+ selected_demo_project = Column(String, nullable=True)
104
+ skipped = Column(Boolean, nullable=False, default=False, server_default='false')
105
+ demo_launch_initialized = Column(Boolean, nullable=False, default=False, server_default='false')
106
+ demo_current_step_id = Column(String, nullable=True)
107
+ demo_current_step_index = Column(Integer, nullable=True)
100
108
  created_at = Column(DateTime, default=func.now(), nullable=False, server_default=func.now())
101
109
  updated_at = Column(DateTime, default=func.now(), onupdate=func.now(), nullable=False, server_default=func.now())
102
110
  # Relationships
@@ -233,6 +233,9 @@ class GCPSecretsManager(SecretManager):
233
233
  if version.name == new_version_name:
234
234
  continue
235
235
 
236
+ if version.state.name == "DESTROYED":
237
+ continue
238
+
236
239
  # Disable the version before destroying
237
240
  if version.state.name != "DISABLED":
238
241
  self.client.disable_secret_version(