superset-showtime 0.4.2__tar.gz → 0.4.5__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.5}/PKG-INFO +1 -1
  2. {superset_showtime-0.4.2 → superset_showtime-0.4.5}/showtime/__init__.py +1 -1
  3. {superset_showtime-0.4.2 → superset_showtime-0.4.5}/showtime/core/pull_request.py +16 -6
  4. {superset_showtime-0.4.2 → superset_showtime-0.4.5}/showtime/core/show.py +7 -4
  5. superset_showtime-0.4.5/tests/unit/test_label_transitions.py +284 -0
  6. {superset_showtime-0.4.2 → superset_showtime-0.4.5}/.claude/settings.local.json +0 -0
  7. {superset_showtime-0.4.2 → superset_showtime-0.4.5}/.gitignore +0 -0
  8. {superset_showtime-0.4.2 → superset_showtime-0.4.5}/.pre-commit-config.yaml +0 -0
  9. {superset_showtime-0.4.2 → superset_showtime-0.4.5}/CLAUDE.md +0 -0
  10. {superset_showtime-0.4.2 → superset_showtime-0.4.5}/Makefile +0 -0
  11. {superset_showtime-0.4.2 → superset_showtime-0.4.5}/README.md +0 -0
  12. {superset_showtime-0.4.2 → superset_showtime-0.4.5}/dev-setup.sh +0 -0
  13. {superset_showtime-0.4.2 → superset_showtime-0.4.5}/pypi-push.sh +0 -0
  14. {superset_showtime-0.4.2 → superset_showtime-0.4.5}/pyproject.toml +0 -0
  15. {superset_showtime-0.4.2 → superset_showtime-0.4.5}/requirements-dev.txt +0 -0
  16. {superset_showtime-0.4.2 → superset_showtime-0.4.5}/requirements.txt +0 -0
  17. {superset_showtime-0.4.2 → superset_showtime-0.4.5}/showtime/__main__.py +0 -0
  18. {superset_showtime-0.4.2 → superset_showtime-0.4.5}/showtime/cli.py +0 -0
  19. {superset_showtime-0.4.2 → superset_showtime-0.4.5}/showtime/core/__init__.py +0 -0
  20. {superset_showtime-0.4.2 → superset_showtime-0.4.5}/showtime/core/aws.py +0 -0
  21. {superset_showtime-0.4.2 → superset_showtime-0.4.5}/showtime/core/emojis.py +0 -0
  22. {superset_showtime-0.4.2 → superset_showtime-0.4.5}/showtime/core/github.py +0 -0
  23. {superset_showtime-0.4.2 → superset_showtime-0.4.5}/showtime/core/github_messages.py +0 -0
  24. {superset_showtime-0.4.2 → superset_showtime-0.4.5}/showtime/core/label_colors.py +0 -0
  25. {superset_showtime-0.4.2 → superset_showtime-0.4.5}/showtime/data/ecs-task-definition.json +0 -0
  26. {superset_showtime-0.4.2 → superset_showtime-0.4.5}/tests/__init__.py +0 -0
  27. {superset_showtime-0.4.2 → superset_showtime-0.4.5}/tests/unit/__init__.py +0 -0
  28. {superset_showtime-0.4.2 → superset_showtime-0.4.5}/tests/unit/test_pull_request.py +0 -0
  29. {superset_showtime-0.4.2 → superset_showtime-0.4.5}/tests/unit/test_show.py +0 -0
  30. {superset_showtime-0.4.2 → superset_showtime-0.4.5}/uv.lock +0 -0
  31. {superset_showtime-0.4.2 → superset_showtime-0.4.5}/workflows-reference/showtime-cleanup.yml +0 -0
  32. {superset_showtime-0.4.2 → superset_showtime-0.4.5}/workflows-reference/showtime-trigger.yml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: superset-showtime
3
- Version: 0.4.2
3
+ Version: 0.4.5
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.5"
8
8
  __author__ = "Maxime Beauchemin"
9
9
  __email__ = "maximebeauchemin@gmail.com"
10
10
 
@@ -507,6 +507,9 @@ class PullRequest:
507
507
  if dry_run:
508
508
  return
509
509
 
510
+ # Refresh labels to get current state (atomic claim may have changed them)
511
+ self.refresh_labels()
512
+
510
513
  # First, remove any existing status labels for this SHA to ensure clean transitions
511
514
  sha_status_labels = [
512
515
  label for label in self.labels
@@ -515,22 +518,29 @@ class PullRequest:
515
518
  for old_status_label in sha_status_labels:
516
519
  get_github().remove_label(self.pr_number, old_status_label)
517
520
 
518
- # Now do normal differential updates
519
- current_labels = {label for label in self.labels if label.startswith("🎪")}
521
+ # Now do normal differential updates - only for this SHA
522
+ current_sha_labels = {
523
+ label for label in self.labels
524
+ if label.startswith("🎪") and (
525
+ label.startswith(f"🎪 {show.sha} ") or # SHA-first format: 🎪 abc123f 📅 ...
526
+ label.startswith(f"🎪 🎯 {show.sha}") or # Pointer format: 🎪 🎯 abc123f
527
+ label.startswith(f"🎪 🏗️ {show.sha}") # Building pointer: 🎪 🏗️ abc123f
528
+ )
529
+ }
520
530
  desired_labels = set(show.to_circus_labels())
521
531
 
522
532
  # Remove the status labels we already cleaned up from the differential
523
- current_labels = current_labels - set(sha_status_labels)
533
+ current_sha_labels = current_sha_labels - set(sha_status_labels)
524
534
 
525
535
  # Only add labels that don't exist
526
- labels_to_add = desired_labels - current_labels
536
+ labels_to_add = desired_labels - current_sha_labels
527
537
  for label in labels_to_add:
528
538
  get_github().add_label(self.pr_number, label)
529
539
 
530
540
  # Only remove labels that shouldn't exist (excluding status labels already handled)
531
- labels_to_remove = current_labels - desired_labels
541
+ labels_to_remove = current_sha_labels - desired_labels
532
542
  for label in labels_to_remove:
533
543
  get_github().remove_label(self.pr_number, label)
534
544
 
535
- # Refresh our label cache
545
+ # Final refresh to update cache with all changes
536
546
  self.refresh_labels()
@@ -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
@@ -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(
@@ -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