superset-showtime 0.6.4__py3-none-any.whl → 0.6.7__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 superset-showtime might be problematic. Click here for more details.

showtime/core/show.py CHANGED
@@ -8,6 +8,8 @@ from dataclasses import dataclass
8
8
  from datetime import datetime
9
9
  from typing import List, Optional
10
10
 
11
+ from .constants import DEFAULT_TTL
12
+
11
13
 
12
14
  # Import interfaces for singleton access
13
15
  # Note: These will be imported when the module loads, creating singletons
@@ -28,7 +30,7 @@ class Show:
28
30
  status: str # building, built, deploying, running, updating, failed
29
31
  ip: Optional[str] = None # Environment IP address
30
32
  created_at: Optional[str] = None # ISO timestamp
31
- ttl: str = "24h" # 24h, 48h, close, etc.
33
+ ttl: str = DEFAULT_TTL # 24h, 48h, close, etc.
32
34
  requested_by: Optional[str] = None # GitHub username
33
35
 
34
36
  @property
@@ -80,26 +82,38 @@ class Show:
80
82
  """Check if environment needs update to latest SHA"""
81
83
  return self.sha != latest_sha[:7]
82
84
 
85
+ @property
86
+ def created_datetime(self) -> Optional[datetime]:
87
+ """Parse created_at timestamp into datetime object (UTC)"""
88
+ from .date_utils import parse_circus_time
89
+
90
+ if self.created_at is None:
91
+ return None
92
+ return parse_circus_time(self.created_at)
93
+
83
94
  def is_expired(self, max_age_hours: int) -> bool:
84
95
  """Check if this environment is expired based on age"""
85
- if not self.created_at:
96
+ from .date_utils import is_expired
97
+
98
+ if self.created_at is None:
86
99
  return False
100
+ return is_expired(self.created_at, max_age_hours)
87
101
 
88
- try:
89
- from datetime import datetime, timedelta
102
+ def age_display(self) -> str:
103
+ """Get human-readable age of this environment"""
104
+ from .date_utils import age_display
90
105
 
91
- created_time = datetime.fromisoformat(self.created_at.replace("-", ":"))
92
- expiry_time = created_time + timedelta(hours=max_age_hours)
93
- return datetime.now() > expiry_time
94
- except (ValueError, AttributeError):
95
- return False # If we can't parse, assume not expired
106
+ if self.created_at is None:
107
+ return "unknown"
108
+ return age_display(self.created_at)
96
109
 
97
110
  def to_circus_labels(self) -> List[str]:
98
111
  """Convert show state to circus tent emoji labels (per-SHA format)"""
112
+ from .date_utils import format_utc_now
99
113
  from .emojis import CIRCUS_PREFIX, MEANING_TO_EMOJI
100
114
 
101
115
  if not self.created_at:
102
- self.created_at = datetime.utcnow().strftime("%Y-%m-%dT%H-%M")
116
+ self.created_at = format_utc_now()
103
117
 
104
118
  labels = [
105
119
  f"{CIRCUS_PREFIX} {self.sha} {MEANING_TO_EMOJI['status']} {self.status}", # SHA-first status
@@ -142,21 +156,20 @@ class Show:
142
156
  # Mock successful deployment for dry-run
143
157
  self.ip = "52.1.2.3"
144
158
 
145
- def stop(self, dry_run_github: bool = False, dry_run_aws: bool = False) -> None:
159
+ def stop(self, dry_run_github: bool = False, dry_run_aws: bool = False) -> bool:
146
160
  """Stop this environment (cleanup AWS resources)
147
161
 
148
- Raises:
149
- Exception: On cleanup failure
162
+ Returns:
163
+ True if successful, False otherwise
150
164
  """
151
165
  github, aws = get_interfaces()
152
166
 
153
167
  # Delete AWS resources (pure technical work)
154
168
  if not dry_run_aws:
155
- success = aws.delete_environment(self.aws_service_name, self.pr_number)
156
- if not success:
157
- raise Exception(f"Failed to delete AWS service: {self.aws_service_name}")
169
+ result = aws.delete_environment(self.aws_service_name, self.pr_number)
170
+ return bool(result)
158
171
 
159
- # No comments - PullRequest handles that!
172
+ return True # Dry run is always "successful"
160
173
 
161
174
  def _build_docker_image(self) -> None:
162
175
  """Build Docker image for this environment"""
@@ -0,0 +1,137 @@
1
+ """
2
+ 🎪 Sync State - Typed state management for sync operations
3
+
4
+ Provides structured state tracking with proper typing for analysis and debugging.
5
+ """
6
+
7
+ from dataclasses import dataclass
8
+ from enum import Enum
9
+ from typing import List, Optional
10
+
11
+
12
+ class ActionNeeded(Enum):
13
+ """Actions that sync can take"""
14
+
15
+ NO_ACTION = "no_action"
16
+ CREATE_ENVIRONMENT = "create_environment"
17
+ ROLLING_UPDATE = "rolling_update"
18
+ AUTO_SYNC = "auto_sync"
19
+ DESTROY_ENVIRONMENT = "destroy_environment"
20
+ BLOCKED = "blocked"
21
+
22
+
23
+ class AuthStatus(Enum):
24
+ """Authorization check results"""
25
+
26
+ AUTHORIZED = "authorized"
27
+ DENIED_INSUFFICIENT_PERMS = "denied_insufficient_perms"
28
+ DENIED_404 = "denied_404"
29
+ ALLOWED_NO_ACTOR = "allowed_no_actor"
30
+ SKIPPED_NOT_ACTIONS = "skipped_not_actions"
31
+ ERROR = "error"
32
+
33
+
34
+ class BlockedReason(Enum):
35
+ """Why an operation is blocked"""
36
+
37
+ EXISTING_BLOCKED_LABEL = "existing_blocked_label"
38
+ AUTHORIZATION_FAILED = "authorization_failed"
39
+ NOT_BLOCKED = "not_blocked"
40
+
41
+
42
+ @dataclass
43
+ class SyncState:
44
+ """Complete state information for a sync operation"""
45
+
46
+ # Core sync decision
47
+ action_needed: ActionNeeded
48
+ build_needed: bool
49
+ sync_needed: bool
50
+ target_sha: str
51
+
52
+ # Authorization info
53
+ github_actor: str
54
+ is_github_actions: bool
55
+ permission_level: str
56
+ auth_status: AuthStatus
57
+
58
+ # Blocking info
59
+ blocked_reason: BlockedReason = BlockedReason.NOT_BLOCKED
60
+
61
+ # Context info
62
+ trigger_labels: Optional[List[str]] = None
63
+ target_show_status: Optional[str] = None
64
+ has_previous_shows: bool = False
65
+ action_reason: str = ""
66
+
67
+ # Error details (if any)
68
+ auth_error: Optional[str] = None
69
+
70
+ def __post_init__(self) -> None:
71
+ """Initialize default values"""
72
+ if self.trigger_labels is None:
73
+ self.trigger_labels = []
74
+
75
+ @property
76
+ def is_blocked(self) -> bool:
77
+ """Check if operation is blocked"""
78
+ return self.action_needed == ActionNeeded.BLOCKED
79
+
80
+ @property
81
+ def is_authorized(self) -> bool:
82
+ """Check if actor is authorized"""
83
+ return self.auth_status == AuthStatus.AUTHORIZED
84
+
85
+ def to_gha_stdout(self, pr_number: int) -> str:
86
+ """Generate GitHub Actions compatible stdout with k=v pairs"""
87
+ lines = [
88
+ f"build_needed={str(self.build_needed).lower()}",
89
+ f"sync_needed={str(self.sync_needed).lower()}",
90
+ f"pr_number={pr_number}",
91
+ f"target_sha={self.target_sha}",
92
+ f"action_needed={self.action_needed.value}",
93
+ f"github_actor={self.github_actor}",
94
+ f"permission_level={self.permission_level}",
95
+ f"auth_status={self.auth_status.value}",
96
+ ]
97
+
98
+ # Add blocking info if relevant
99
+ if self.is_blocked:
100
+ lines.append(f"blocked_reason={self.blocked_reason.value}")
101
+
102
+ # Add context info for debugging
103
+ if self.trigger_labels:
104
+ lines.append(f"trigger_labels={','.join(self.trigger_labels)}")
105
+
106
+ if self.target_show_status:
107
+ lines.append(f"target_show_status={self.target_show_status}")
108
+
109
+ lines.append(f"has_previous_shows={str(self.has_previous_shows).lower()}")
110
+
111
+ if self.action_reason:
112
+ lines.append(f"action_reason={self.action_reason}")
113
+
114
+ # Add error info if present
115
+ if self.auth_error:
116
+ lines.append(f"auth_error={self.auth_error}")
117
+
118
+ return "\n".join(lines)
119
+
120
+ def to_debug_summary(self) -> str:
121
+ """Generate human-readable debug summary"""
122
+ status = "🚫 BLOCKED" if self.is_blocked else "✅ AUTHORIZED"
123
+
124
+ summary = [
125
+ f"🎪 Sync State Summary: {status}",
126
+ f" Action: {self.action_needed.value} ({self.action_reason})",
127
+ f" Actor: {self.github_actor} ({self.permission_level})",
128
+ f" Auth: {self.auth_status.value}",
129
+ ]
130
+
131
+ if self.is_blocked:
132
+ summary.append(f" Blocked: {self.blocked_reason.value}")
133
+
134
+ if self.trigger_labels:
135
+ summary.append(f" Triggers: {', '.join(self.trigger_labels)}")
136
+
137
+ return "\n".join(summary)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: superset-showtime
3
- Version: 0.6.4
3
+ Version: 0.6.7
4
4
  Summary: 🎪 Apache Superset ephemeral environment management with circus tent emoji state tracking
5
5
  Project-URL: Homepage, https://github.com/apache/superset-showtime
6
6
  Project-URL: Documentation, https://superset-showtime.readthedocs.io/
@@ -0,0 +1,21 @@
1
+ showtime/__init__.py,sha256=2Ky3eSk7FJq4kHOp0cH41haCJ3X8gZ5WpzxUqKXRnNM,448
2
+ showtime/__main__.py,sha256=EVaDaTX69yIhCzChg99vqvFSCN4ELstEt7Mpb9FMZX8,109
3
+ showtime/cli.py,sha256=uGqtmFHXTZd8vMWuoGZ6BLVWzuY34hMx-GMCkgvqvKk,34369
4
+ showtime/core/__init__.py,sha256=54hbdFNGrzuNMBdraezfjT8Zi6g221pKlJ9mREnKwCw,34
5
+ showtime/core/aws.py,sha256=7ifPmYryeuLR2c7RtGGzge8Ny9H6ZL40mDESiM1_fi8,34065
6
+ showtime/core/constants.py,sha256=ssHiswRorqp2CnsZOebz2zqGsnDlMy9aeTQy8gju9fY,290
7
+ showtime/core/date_utils.py,sha256=iQGQ1DfOwkWbdSTRM7CZdBhiXfpv26khobln5jg1d7k,2112
8
+ showtime/core/emojis.py,sha256=arK0N5Q5FLkvOkci-lacb3WS56LTvY8NjYRqt_lhP9s,2188
9
+ showtime/core/git_validation.py,sha256=2V9BSEjubGG4EHKZjcOUGYDWddNpB9uBZ_EVdU00C60,7203
10
+ showtime/core/github.py,sha256=klUxIzUZRE7iQhG36A0lrtgnt6sTkmUjFazaXGgMlCY,12940
11
+ showtime/core/github_messages.py,sha256=LDYAbVHMEwJaldeJy0ddKe91oRV3RVsUNAXo-RU5oDs,7490
12
+ showtime/core/label_colors.py,sha256=gSe7EIMl4YjWkIgKHUvuaRSwgEB_B-NYQBxFFlF8Z3s,4065
13
+ showtime/core/pull_request.py,sha256=vFlv6pTR3xI5jSODEaQh_af7CUA5kf1StQqQhmzVczI,44964
14
+ showtime/core/service_name.py,sha256=ZgYzlgnMdMqqatCMVarSyzv9Za9njxDSfIvgYd0LUIY,3126
15
+ showtime/core/show.py,sha256=VzjOeUG2ra2tdDIzO1EHkZiEt-kMznRk9O2PQ7VuE4I,10224
16
+ showtime/core/sync_state.py,sha256=oe1lTWWPzfvEAzLBAowdsSJZIdmm5NN-RewzpYTj8zE,4147
17
+ showtime/data/ecs-task-definition.json,sha256=d-NLkIhvr4C6AnwDfDIwUTx-6KFMH9wRkt6pVCbqZY4,2365
18
+ superset_showtime-0.6.7.dist-info/METADATA,sha256=_2odjOOHSF_gh7E__iGf2oa4p7a0XV-Ow3qsgW2DVEw,12052
19
+ superset_showtime-0.6.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
20
+ superset_showtime-0.6.7.dist-info/entry_points.txt,sha256=rDW7oZ57mqyBUS4N_3_R7bZNGVHB-104jwmY-hHC_ck,85
21
+ superset_showtime-0.6.7.dist-info/RECORD,,
@@ -1,17 +0,0 @@
1
- showtime/__init__.py,sha256=O8PG7VEz2VYlezufbPc7zRxajuNxLRaW7XTXnOYSki0,448
2
- showtime/__main__.py,sha256=EVaDaTX69yIhCzChg99vqvFSCN4ELstEt7Mpb9FMZX8,109
3
- showtime/cli.py,sha256=TLv9NaqPyewKJi9uCTZKWBijGelqunmsoSo7cyKajV4,31640
4
- showtime/core/__init__.py,sha256=54hbdFNGrzuNMBdraezfjT8Zi6g221pKlJ9mREnKwCw,34
5
- showtime/core/aws.py,sha256=uTjJAvEBQMyTccS93WZeNPhfeKQhJgOQQ0BJdnQjvCU,35007
6
- showtime/core/emojis.py,sha256=arK0N5Q5FLkvOkci-lacb3WS56LTvY8NjYRqt_lhP9s,2188
7
- showtime/core/git_validation.py,sha256=3dmSGpMDplDAmKWHUyoUEPgt3__8oTuBZxbfuhocT00,6831
8
- showtime/core/github.py,sha256=mSOqRLy2KMDhWUS37V2gJ-CQdeBpEqunBRKL10v5hxU,12268
9
- showtime/core/github_messages.py,sha256=MfgwCukrEsWWesMsuL8saciDgP4nS-gijzu8DXr-Alg,7450
10
- showtime/core/label_colors.py,sha256=gSe7EIMl4YjWkIgKHUvuaRSwgEB_B-NYQBxFFlF8Z3s,4065
11
- showtime/core/pull_request.py,sha256=DCrTYz3Fu2Oh9FRgYTwncvzX7zy4Wa0BZsBwYa3nTpg,32669
12
- showtime/core/show.py,sha256=sOgZvGXwdcNDsidF1F_XwPXlSeTb8-Zeqhqb8w1pqAM,9973
13
- showtime/data/ecs-task-definition.json,sha256=d-NLkIhvr4C6AnwDfDIwUTx-6KFMH9wRkt6pVCbqZY4,2365
14
- superset_showtime-0.6.4.dist-info/METADATA,sha256=SqFxH-mmg3jpLnOp2Gkl2AHJjewmga-nNtSe4HHk8bw,12052
15
- superset_showtime-0.6.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
- superset_showtime-0.6.4.dist-info/entry_points.txt,sha256=rDW7oZ57mqyBUS4N_3_R7bZNGVHB-104jwmY-hHC_ck,85
17
- superset_showtime-0.6.4.dist-info/RECORD,,