superset-showtime 0.4.2__tar.gz → 0.4.8__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.

Potentially problematic release.


This version of superset-showtime might be problematic. Click here for more details.

Files changed (32) hide show
  1. {superset_showtime-0.4.2 → superset_showtime-0.4.8}/.claude/settings.local.json +2 -1
  2. {superset_showtime-0.4.2 → superset_showtime-0.4.8}/PKG-INFO +1 -1
  3. {superset_showtime-0.4.2 → superset_showtime-0.4.8}/showtime/__init__.py +1 -1
  4. {superset_showtime-0.4.2 → superset_showtime-0.4.8}/showtime/core/pull_request.py +34 -7
  5. {superset_showtime-0.4.2 → superset_showtime-0.4.8}/showtime/core/show.py +10 -7
  6. {superset_showtime-0.4.2 → superset_showtime-0.4.8}/showtime/data/ecs-task-definition.json +0 -4
  7. superset_showtime-0.4.8/tests/unit/test_label_transitions.py +284 -0
  8. {superset_showtime-0.4.2 → superset_showtime-0.4.8}/.gitignore +0 -0
  9. {superset_showtime-0.4.2 → superset_showtime-0.4.8}/.pre-commit-config.yaml +0 -0
  10. {superset_showtime-0.4.2 → superset_showtime-0.4.8}/CLAUDE.md +0 -0
  11. {superset_showtime-0.4.2 → superset_showtime-0.4.8}/Makefile +0 -0
  12. {superset_showtime-0.4.2 → superset_showtime-0.4.8}/README.md +0 -0
  13. {superset_showtime-0.4.2 → superset_showtime-0.4.8}/dev-setup.sh +0 -0
  14. {superset_showtime-0.4.2 → superset_showtime-0.4.8}/pypi-push.sh +0 -0
  15. {superset_showtime-0.4.2 → superset_showtime-0.4.8}/pyproject.toml +0 -0
  16. {superset_showtime-0.4.2 → superset_showtime-0.4.8}/requirements-dev.txt +0 -0
  17. {superset_showtime-0.4.2 → superset_showtime-0.4.8}/requirements.txt +0 -0
  18. {superset_showtime-0.4.2 → superset_showtime-0.4.8}/showtime/__main__.py +0 -0
  19. {superset_showtime-0.4.2 → superset_showtime-0.4.8}/showtime/cli.py +0 -0
  20. {superset_showtime-0.4.2 → superset_showtime-0.4.8}/showtime/core/__init__.py +0 -0
  21. {superset_showtime-0.4.2 → superset_showtime-0.4.8}/showtime/core/aws.py +0 -0
  22. {superset_showtime-0.4.2 → superset_showtime-0.4.8}/showtime/core/emojis.py +0 -0
  23. {superset_showtime-0.4.2 → superset_showtime-0.4.8}/showtime/core/github.py +0 -0
  24. {superset_showtime-0.4.2 → superset_showtime-0.4.8}/showtime/core/github_messages.py +0 -0
  25. {superset_showtime-0.4.2 → superset_showtime-0.4.8}/showtime/core/label_colors.py +0 -0
  26. {superset_showtime-0.4.2 → superset_showtime-0.4.8}/tests/__init__.py +0 -0
  27. {superset_showtime-0.4.2 → superset_showtime-0.4.8}/tests/unit/__init__.py +0 -0
  28. {superset_showtime-0.4.2 → superset_showtime-0.4.8}/tests/unit/test_pull_request.py +0 -0
  29. {superset_showtime-0.4.2 → superset_showtime-0.4.8}/tests/unit/test_show.py +0 -0
  30. {superset_showtime-0.4.2 → superset_showtime-0.4.8}/uv.lock +0 -0
  31. {superset_showtime-0.4.2 → superset_showtime-0.4.8}/workflows-reference/showtime-cleanup.yml +0 -0
  32. {superset_showtime-0.4.2 → superset_showtime-0.4.8}/workflows-reference/showtime-trigger.yml +0 -0
@@ -41,7 +41,8 @@
41
41
  "Bash(make:*)",
42
42
  "Bash(showtime status:*)",
43
43
  "Bash(showtime sync:*)",
44
- "Bash(AWS_PROFILE=\"\" showtime sync 34831 --dry-run-aws --dry-run-github)"
44
+ "Bash(AWS_PROFILE=\"\" showtime sync 34831 --dry-run-aws --dry-run-github)",
45
+ "Bash(git stash:*)"
45
46
  ],
46
47
  "deny": [],
47
48
  "ask": []
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: superset-showtime
3
- Version: 0.4.2
3
+ Version: 0.4.8
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/
@@ -4,7 +4,7 @@
4
4
  Circus tent emoji state tracking for Apache Superset ephemeral environments.
5
5
  """
6
6
 
7
- __version__ = "0.4.2"
7
+ __version__ = "0.4.8"
8
8
  __author__ = "Maxime Beauchemin"
9
9
  __email__ = "maximebeauchemin@gmail.com"
10
10
 
@@ -4,6 +4,7 @@
4
4
  Handles atomic transactions, trigger processing, and environment orchestration.
5
5
  """
6
6
 
7
+ import os
7
8
  from dataclasses import dataclass
8
9
  from datetime import datetime
9
10
  from typing import Any, List, Optional
@@ -244,6 +245,9 @@ class PullRequest:
244
245
  show.status = "running"
245
246
  print(f"✅ Deployment completed - environment running at {show.ip}:8080")
246
247
  self._update_show_labels(show, dry_run_github)
248
+
249
+ # Show AWS console URLs for monitoring
250
+ self._show_service_urls(show)
247
251
 
248
252
  self._post_success_comment(show, dry_run_github)
249
253
  return SyncResult(success=True, action_taken="create_environment", show=show)
@@ -273,6 +277,9 @@ class PullRequest:
273
277
  new_show.status = "running"
274
278
  print(f"✅ Rolling update completed - new environment at {new_show.ip}:8080")
275
279
  self._update_show_labels(new_show, dry_run_github)
280
+
281
+ # Show AWS console URLs for monitoring
282
+ self._show_service_urls(new_show)
276
283
 
277
284
  self._post_rolling_success_comment(old_show, new_show, dry_run_github)
278
285
  return SyncResult(success=True, action_taken=action_needed, show=new_show)
@@ -424,7 +431,7 @@ class PullRequest:
424
431
  status="building",
425
432
  created_at=datetime.utcnow().strftime("%Y-%m-%dT%H-%M"),
426
433
  ttl="24h",
427
- requested_by="github_actor", # TODO: Get from context
434
+ requested_by=os.getenv("GITHUB_ACTOR", "unknown"),
428
435
  )
429
436
 
430
437
  def _post_building_comment(self, show: Show, dry_run: bool = False) -> None:
@@ -507,6 +514,9 @@ class PullRequest:
507
514
  if dry_run:
508
515
  return
509
516
 
517
+ # Refresh labels to get current state (atomic claim may have changed them)
518
+ self.refresh_labels()
519
+
510
520
  # First, remove any existing status labels for this SHA to ensure clean transitions
511
521
  sha_status_labels = [
512
522
  label for label in self.labels
@@ -515,22 +525,39 @@ class PullRequest:
515
525
  for old_status_label in sha_status_labels:
516
526
  get_github().remove_label(self.pr_number, old_status_label)
517
527
 
518
- # Now do normal differential updates
519
- current_labels = {label for label in self.labels if label.startswith("🎪")}
528
+ # Now do normal differential updates - only for this SHA
529
+ current_sha_labels = {
530
+ label for label in self.labels
531
+ if label.startswith("🎪") and (
532
+ label.startswith(f"🎪 {show.sha} ") or # SHA-first format: 🎪 abc123f 📅 ...
533
+ label.startswith(f"🎪 🎯 {show.sha}") or # Pointer format: 🎪 🎯 abc123f
534
+ label.startswith(f"🎪 🏗️ {show.sha}") # Building pointer: 🎪 🏗️ abc123f
535
+ )
536
+ }
520
537
  desired_labels = set(show.to_circus_labels())
521
538
 
522
539
  # Remove the status labels we already cleaned up from the differential
523
- current_labels = current_labels - set(sha_status_labels)
540
+ current_sha_labels = current_sha_labels - set(sha_status_labels)
524
541
 
525
542
  # Only add labels that don't exist
526
- labels_to_add = desired_labels - current_labels
543
+ labels_to_add = desired_labels - current_sha_labels
527
544
  for label in labels_to_add:
528
545
  get_github().add_label(self.pr_number, label)
529
546
 
530
547
  # Only remove labels that shouldn't exist (excluding status labels already handled)
531
- labels_to_remove = current_labels - desired_labels
548
+ labels_to_remove = current_sha_labels - desired_labels
532
549
  for label in labels_to_remove:
533
550
  get_github().remove_label(self.pr_number, label)
534
551
 
535
- # Refresh our label cache
552
+ # Final refresh to update cache with all changes
536
553
  self.refresh_labels()
554
+
555
+ def _show_service_urls(self, show: Show) -> None:
556
+ """Show AWS console URLs for monitoring deployment"""
557
+ from .github_messages import get_aws_console_urls
558
+
559
+ urls = get_aws_console_urls(show.ecs_service_name)
560
+ print(f"\n🎪 Monitor deployment progress:")
561
+ print(f"📝 Logs: {urls['logs']}")
562
+ print(f"📊 Service: {urls['service']}")
563
+ print("")
@@ -166,10 +166,10 @@ class Show:
166
166
  # Detect if running in CI environment
167
167
  is_ci = bool(os.getenv("GITHUB_ACTIONS") or os.getenv("CI"))
168
168
 
169
- # Base command
169
+ # Build command without final path
170
170
  cmd = [
171
171
  "docker",
172
- "buildx",
172
+ "buildx",
173
173
  "build",
174
174
  "--push",
175
175
  "--platform",
@@ -182,7 +182,6 @@ class Show:
182
182
  "LOAD_EXAMPLES_DUCKDB=true",
183
183
  "-t",
184
184
  tag,
185
- ".",
186
185
  ]
187
186
 
188
187
  # Add caching based on environment
@@ -190,16 +189,16 @@ class Show:
190
189
  # Full registry caching in CI (Docker driver supports it)
191
190
  cmd.extend([
192
191
  "--cache-from",
193
- "type=registry,ref=apache/superset-cache:3.10-slim-bookworm",
192
+ "type=registry,ref=apache/superset-cache:showtime",
194
193
  "--cache-to",
195
- "type=registry,mode=max,ref=apache/superset-cache:3.10-slim-bookworm",
194
+ "type=registry,mode=max,ref=apache/superset-cache:showtime",
196
195
  ])
197
196
  print("🐳 CI environment: Using full registry caching")
198
197
  else:
199
198
  # Local build: cache-from only (no cache export)
200
199
  cmd.extend([
201
200
  "--cache-from",
202
- "type=registry,ref=apache/superset-cache:3.10-slim-bookworm",
201
+ "type=registry,ref=apache/superset-cache:showtime",
203
202
  ])
204
203
  print("🐳 Local environment: Using cache-from only (no export)")
205
204
 
@@ -209,12 +208,16 @@ class Show:
209
208
  force_load = os.getenv("DOCKER_LOAD", "false").lower() == "true"
210
209
 
211
210
  if native_x86 or force_load:
212
- cmd.insert(-1, "--load") # Insert before the "." argument
211
+ cmd.append("--load")
213
212
  print("🐳 Will load image to local Docker daemon (native x86_64 platform)")
214
213
  else:
215
214
  print("🐳 Cross-platform build - pushing to registry only (no local load)")
216
215
 
216
+ # Add build context path last
217
+ cmd.append(".")
218
+
217
219
  print(f"🐳 Building Docker image: {tag}")
220
+ print(f"🐳 Command: {' '.join(cmd)}")
218
221
 
219
222
  # Stream output in real-time
220
223
  process = subprocess.Popen(
@@ -43,10 +43,6 @@
43
43
  {
44
44
  "name": "PYTHONUNBUFFERED",
45
45
  "value": "1"
46
- },
47
- {
48
- "name": "PYTHONPATH",
49
- "value": "/app/docker/pythonpath_dev"
50
46
  }
51
47
  ],
52
48
  "mountPoints": [],
@@ -0,0 +1,284 @@
1
+ """
2
+ Tests for circus tent label state transitions and cleanup
3
+ """
4
+
5
+ from unittest.mock import Mock, call, patch
6
+
7
+ from showtime.core.pull_request import PullRequest
8
+ from showtime.core.show import Show
9
+
10
+
11
+ @patch('showtime.core.pull_request.get_github')
12
+ def test_status_transition_building_to_running(mock_get_github):
13
+ """Test clean transition from building to running state"""
14
+ mock_github = Mock()
15
+ mock_get_github.return_value = mock_github
16
+
17
+ # Start with building state
18
+ pr = PullRequest(1234, [
19
+ "🎪 abc123f 🚦 building",
20
+ "🎪 🎯 abc123f",
21
+ "🎪 abc123f 📅 2024-01-15T14-30",
22
+ ])
23
+
24
+ # Transition to running
25
+ show = Show(
26
+ pr_number=1234,
27
+ sha="abc123f",
28
+ status="running", # New status
29
+ created_at="2024-01-15T14-30",
30
+ ip="52.1.2.3" # Added during deployment
31
+ )
32
+
33
+ with patch.object(pr, 'refresh_labels'):
34
+ pr._update_show_labels(show, dry_run=False)
35
+
36
+ # Should remove old building status
37
+ remove_calls = [call.args[1] for call in mock_github.remove_label.call_args_list]
38
+ assert "🎪 abc123f 🚦 building" in remove_calls
39
+
40
+ # Should add running status and IP
41
+ add_calls = [call.args[1] for call in mock_github.add_label.call_args_list]
42
+ assert "🎪 abc123f 🚦 running" in add_calls
43
+ assert "🎪 abc123f 🌐 52.1.2.3:8080" in add_calls
44
+
45
+
46
+ @patch('showtime.core.pull_request.get_github')
47
+ def test_status_transition_building_to_failed(mock_get_github):
48
+ """Test transition from building to failed state (Docker failure)"""
49
+ mock_github = Mock()
50
+ mock_get_github.return_value = mock_github
51
+
52
+ # Start with building state
53
+ pr = PullRequest(1234, [
54
+ "🎪 abc123f 🚦 building",
55
+ "🎪 🏗️ abc123f", # Building pointer
56
+ "🎪 abc123f 📅 2024-01-15T14-30",
57
+ ])
58
+
59
+ # Docker build fails, transition to failed
60
+ show = Show(
61
+ pr_number=1234,
62
+ sha="abc123f",
63
+ status="failed", # Build failed
64
+ created_at="2024-01-15T14-30",
65
+ )
66
+
67
+ with patch.object(pr, 'refresh_labels'):
68
+ pr._update_show_labels(show, dry_run=False)
69
+
70
+ # Should remove building status
71
+ remove_calls = [call.args[1] for call in mock_github.remove_label.call_args_list]
72
+ assert "🎪 abc123f 🚦 building" in remove_calls
73
+
74
+ # Should add failed status
75
+ add_calls = [call.args[1] for call in mock_github.add_label.call_args_list]
76
+ assert "🎪 abc123f 🚦 failed" in add_calls
77
+
78
+
79
+ @patch('showtime.core.pull_request.get_github')
80
+ def test_multiple_orphaned_status_cleanup(mock_get_github):
81
+ """Test cleanup of multiple orphaned status labels (the bug scenario)"""
82
+ mock_github = Mock()
83
+ mock_get_github.return_value = mock_github
84
+
85
+ # PR with multiple orphaned status labels from previous failed transitions
86
+ pr = PullRequest(1234, [
87
+ "🎪 abc123f 🚦 building", # Old status 1
88
+ "🎪 abc123f 🚦 failed", # Old status 2
89
+ "🎪 abc123f 🚦 deploying", # Old status 3
90
+ "🎪 🎯 abc123f", # Pointer
91
+ "🎪 abc123f 📅 2024-01-15T14-30",
92
+ "🎪 abc123f 🤡 maxime",
93
+ ])
94
+
95
+ # Clean transition to running
96
+ show = Show(
97
+ pr_number=1234,
98
+ sha="abc123f",
99
+ status="running",
100
+ created_at="2024-01-15T14-30",
101
+ requested_by="maxime",
102
+ ip="52.1.2.3"
103
+ )
104
+
105
+ with patch.object(pr, 'refresh_labels'):
106
+ pr._update_show_labels(show, dry_run=False)
107
+
108
+ # Should remove ALL orphaned status labels
109
+ remove_calls = [call.args[1] for call in mock_github.remove_label.call_args_list]
110
+ assert "🎪 abc123f 🚦 building" in remove_calls
111
+ assert "🎪 abc123f 🚦 failed" in remove_calls
112
+ assert "🎪 abc123f 🚦 deploying" in remove_calls
113
+
114
+ # Should add only the new running status
115
+ add_calls = [call.args[1] for call in mock_github.add_label.call_args_list]
116
+ running_status_calls = [call for call in add_calls if "🚦 running" in call]
117
+ assert len(running_status_calls) == 1
118
+ assert "🎪 abc123f 🚦 running" in add_calls
119
+
120
+
121
+ @patch('showtime.core.pull_request.get_github')
122
+ def test_status_transition_with_concurrent_changes(mock_get_github):
123
+ """Test status transition doesn't interfere with other SHA labels"""
124
+ mock_github = Mock()
125
+ mock_get_github.return_value = mock_github
126
+
127
+ # PR with two environments - one transitioning, one stable
128
+ pr = PullRequest(1234, [
129
+ "🎪 abc123f 🚦 building", # Transitioning environment
130
+ "🎪 def456a 🚦 running", # Stable environment
131
+ "🎪 🎯 abc123f", # Active pointer
132
+ "🎪 🏗️ def456a", # Building pointer
133
+ "🎪 abc123f 📅 2024-01-15T14-30",
134
+ "🎪 def456a 📅 2024-01-15T15-00",
135
+ ])
136
+
137
+ # Transition abc123f to running (should not affect def456a)
138
+ show = Show(
139
+ pr_number=1234,
140
+ sha="abc123f",
141
+ status="running",
142
+ created_at="2024-01-15T14-30",
143
+ ip="52.1.2.3"
144
+ )
145
+
146
+ with patch.object(pr, 'refresh_labels'):
147
+ pr._update_show_labels(show, dry_run=False)
148
+
149
+ # Should only remove abc123f building status
150
+ remove_calls = [call.args[1] for call in mock_github.remove_label.call_args_list]
151
+ assert "🎪 abc123f 🚦 building" in remove_calls
152
+
153
+ # Should NOT remove def456a status
154
+ assert "🎪 def456a 🚦 running" not in remove_calls
155
+
156
+ # Should add abc123f running status
157
+ add_calls = [call.args[1] for call in mock_github.add_label.call_args_list]
158
+ assert "🎪 abc123f 🚦 running" in add_calls
159
+
160
+
161
+ @patch('showtime.core.pull_request.get_github')
162
+ def test_status_replacement_preserves_other_labels(mock_get_github):
163
+ """Test that status replacement preserves non-status labels"""
164
+ mock_github = Mock()
165
+ mock_get_github.return_value = mock_github
166
+
167
+ # PR with various label types
168
+ pr = PullRequest(1234, [
169
+ "🎪 abc123f 🚦 building", # Status - should be replaced
170
+ "🎪 🎯 abc123f", # Pointer - should stay
171
+ "🎪 abc123f 📅 2024-01-15T14-30", # Timestamp - should stay
172
+ "🎪 abc123f ⌛ 24h", # TTL - should stay
173
+ "🎪 abc123f 🤡 maxime", # User - should stay
174
+ "bug", # Non-circus - should stay
175
+ "enhancement", # Non-circus - should stay
176
+ ])
177
+
178
+ # Same show, just status change
179
+ show = Show(
180
+ pr_number=1234,
181
+ sha="abc123f",
182
+ status="failed", # Status changed
183
+ created_at="2024-01-15T14-30",
184
+ ttl="24h",
185
+ requested_by="maxime"
186
+ )
187
+
188
+ with patch.object(pr, 'refresh_labels'):
189
+ pr._update_show_labels(show, dry_run=False)
190
+
191
+ # Should remove only building status
192
+ remove_calls = [call.args[1] for call in mock_github.remove_label.call_args_list]
193
+ assert "🎪 abc123f 🚦 building" in remove_calls
194
+ assert len([call for call in remove_calls if "🎪 abc123f" in call]) == 1
195
+
196
+ # Should add only failed status (other labels already exist)
197
+ add_calls = [call.args[1] for call in mock_github.add_label.call_args_list]
198
+ assert "🎪 abc123f 🚦 failed" in add_calls
199
+
200
+
201
+ @patch('showtime.core.pull_request.get_github')
202
+ def test_status_transition_dry_run_mode(mock_get_github):
203
+ """Test that dry run mode doesn't make GitHub API calls"""
204
+ mock_github = Mock()
205
+ mock_get_github.return_value = mock_github
206
+
207
+ pr = PullRequest(1234, [
208
+ "🎪 abc123f 🚦 building",
209
+ "🎪 🎯 abc123f",
210
+ ])
211
+
212
+ show = Show(pr_number=1234, sha="abc123f", status="running")
213
+
214
+ # Dry run should not make any API calls
215
+ pr._update_show_labels(show, dry_run=True)
216
+
217
+ mock_github.remove_label.assert_not_called()
218
+ mock_github.add_label.assert_not_called()
219
+
220
+
221
+ @patch('showtime.core.pull_request.get_github')
222
+ def test_no_status_labels_to_clean(mock_get_github):
223
+ """Test behavior when no existing status labels exist"""
224
+ mock_github = Mock()
225
+ mock_get_github.return_value = mock_github
226
+
227
+ # PR with no status labels
228
+ pr = PullRequest(1234, [
229
+ "🎪 🎯 abc123f", # Just pointer
230
+ "bug", "enhancement"
231
+ ])
232
+
233
+ # Add first status
234
+ show = Show(
235
+ pr_number=1234,
236
+ sha="abc123f",
237
+ status="building",
238
+ created_at="2024-01-15T14-30"
239
+ )
240
+
241
+ with patch.object(pr, 'refresh_labels'):
242
+ pr._update_show_labels(show, dry_run=False)
243
+
244
+ # Should not try to remove any status labels
245
+ remove_calls = [call.args[1] for call in mock_github.remove_label.call_args_list]
246
+ status_removes = [call for call in remove_calls if "🚦" in call]
247
+ assert len(status_removes) == 0
248
+
249
+ # Should add new status and timestamp
250
+ add_calls = [call.args[1] for call in mock_github.add_label.call_args_list]
251
+ assert "🎪 abc123f 🚦 building" in add_calls
252
+
253
+
254
+ def test_status_label_identification_edge_cases():
255
+ """Test edge cases in status label identification"""
256
+ from showtime.core.pull_request import PullRequest
257
+
258
+ # Test various malformed labels that should not be treated as status
259
+ labels = [
260
+ "🎪 abc123f 🚦 running", # Valid status
261
+ "🎪 abc123f🚦building", # No spaces - invalid
262
+ "🎪 abc123f 🚦", # No status value - invalid
263
+ "🎪 🚦 building abc123f", # Wrong order - invalid
264
+ "🎪 abc123 🚦 failed", # Wrong SHA length - invalid
265
+ "🎪 abc123f 🚦 weird-status", # Valid format, weird status
266
+ "🎪 def456a 🚦 running", # Different SHA - should not match
267
+ ]
268
+
269
+ pr = PullRequest(1234, labels)
270
+
271
+ # Test that status filtering only matches the correct SHA
272
+ sha_status_labels = [
273
+ label for label in labels
274
+ if label.startswith("🎪 abc123f 🚦 ")
275
+ ]
276
+
277
+ # Should match exactly 2: running and weird-status
278
+ assert len(sha_status_labels) == 2
279
+ assert "🎪 abc123f 🚦 running" in sha_status_labels
280
+ assert "🎪 abc123f 🚦 weird-status" in sha_status_labels
281
+
282
+ # Should not match other SHAs or malformed labels
283
+ assert "🎪 def456a 🚦 running" not in sha_status_labels
284
+ assert "🎪 abc123f🚦building" not in sha_status_labels