superset-showtime 0.4.9__py3-none-any.whl → 0.5.1__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/__init__.py CHANGED
@@ -4,7 +4,7 @@
4
4
  Circus tent emoji state tracking for Apache Superset ephemeral environments.
5
5
  """
6
6
 
7
- __version__ = "0.4.9"
7
+ __version__ = "0.5.1"
8
8
  __author__ = "Maxime Beauchemin"
9
9
  __email__ = "maximebeauchemin@gmail.com"
10
10
 
showtime/cli.py CHANGED
@@ -319,8 +319,10 @@ def list(
319
319
  # Create table with full terminal width
320
320
  table = Table(title="🎪 Environment List", expand=True)
321
321
  table.add_column("PR", style="cyan", min_width=6)
322
+ table.add_column("Type", style="white", min_width=8)
322
323
  table.add_column("Status", style="white", min_width=12)
323
324
  table.add_column("SHA", style="green", min_width=11)
325
+ table.add_column("Created", style="dim white", min_width=12)
324
326
  table.add_column("Superset URL", style="blue", min_width=25)
325
327
  table.add_column("AWS Logs", style="dim blue", min_width=15)
326
328
  table.add_column("TTL", style="yellow", min_width=6)
@@ -328,9 +330,23 @@ def list(
328
330
 
329
331
  status_emoji = STATUS_DISPLAY
330
332
 
331
- for env in sorted(filtered_envs, key=lambda e: e["pr_number"]):
333
+ # Sort by PR number, then by show type (active first, then building, then orphaned)
334
+ type_priority = {"active": 1, "building": 2, "orphaned": 3}
335
+ sorted_envs = sorted(filtered_envs, key=lambda e: (e["pr_number"], type_priority.get(e["show"].get("show_type", "orphaned"), 3)))
336
+
337
+ for env in sorted_envs:
332
338
  show_data = env["show"]
333
339
  pr_number = env["pr_number"]
340
+
341
+ # Show type with appropriate styling (using single-width chars for alignment)
342
+ show_type = show_data.get("show_type", "orphaned")
343
+ if show_type == "active":
344
+ type_display = "* active"
345
+ elif show_type == "building":
346
+ type_display = "# building"
347
+ else:
348
+ type_display = "! orphaned"
349
+
334
350
  # Make Superset URL clickable and show full URL
335
351
  if show_data["ip"]:
336
352
  full_url = f"http://{show_data['ip']}:8080"
@@ -348,10 +364,22 @@ def list(
348
364
  pr_url = f"https://github.com/apache/superset/pull/{pr_number}"
349
365
  clickable_pr = f"[link={pr_url}]{pr_number}[/link]"
350
366
 
367
+ # Format creation time for display
368
+ created_display = show_data.get("created_at", "-")
369
+ if created_display and created_display != "-":
370
+ # Convert 2025-08-25T05-18 to more readable format
371
+ try:
372
+ parts = created_display.replace("T", " ").replace("-", ":")
373
+ created_display = parts[-8:] # Show just HH:MM:SS
374
+ except:
375
+ pass # Keep original if parsing fails
376
+
351
377
  table.add_row(
352
378
  clickable_pr,
379
+ type_display,
353
380
  f"{status_emoji.get(show_data['status'], '❓')} {show_data['status']}",
354
381
  show_data["sha"],
382
+ created_display,
355
383
  superset_url,
356
384
  aws_logs_link,
357
385
  show_data["ttl"],
@@ -615,6 +643,102 @@ def setup_labels(
615
643
  p(f"🎪 [bold red]Error setting up labels:[/bold red] {e}")
616
644
 
617
645
 
646
+ @app.command()
647
+ def aws_cleanup(
648
+ dry_run: bool = typer.Option(False, "--dry-run", help="Show what would be cleaned"),
649
+ force: bool = typer.Option(False, "--force", help="Delete all showtime AWS resources"),
650
+ ) -> None:
651
+ """🧹 Clean up orphaned AWS resources without GitHub labels"""
652
+ try:
653
+ from .core.aws import AWSInterface
654
+
655
+ aws = AWSInterface()
656
+
657
+ p("🔍 [bold blue]Scanning for orphaned AWS resources...[/bold blue]")
658
+
659
+ # 1. Get all GitHub PRs with circus labels
660
+ github_services = set()
661
+ try:
662
+ all_pr_numbers = PullRequest.find_all_with_environments()
663
+ p(f"📋 Found {len(all_pr_numbers)} PRs with circus labels:")
664
+
665
+ for pr_number in all_pr_numbers:
666
+ pr = PullRequest.from_id(pr_number)
667
+ p(f" 🎪 PR #{pr_number}: {len(pr.shows)} shows, {len(pr.circus_labels)} circus labels")
668
+
669
+ for show in pr.shows:
670
+ service_name = show.ecs_service_name
671
+ github_services.add(service_name)
672
+ p(f" 📝 Expected service: {service_name}")
673
+
674
+ # Show labels for debugging
675
+ if not pr.shows:
676
+ p(f" ⚠️ No shows found, labels: {pr.circus_labels[:3]}...") # First 3 labels
677
+
678
+ except Exception as e:
679
+ p(f"⚠️ GitHub scan failed: {e}")
680
+ github_services = set()
681
+
682
+ # 2. Get all AWS ECS services matching showtime pattern
683
+ p("\n☁️ [bold blue]Scanning AWS ECS services...[/bold blue]")
684
+ try:
685
+ aws_services = aws.find_showtime_services()
686
+ p(f"🔍 Found {len(aws_services)} AWS services with pr-* pattern")
687
+
688
+ for service in aws_services:
689
+ p(f" ☁️ AWS: {service}")
690
+ except Exception as e:
691
+ p(f"❌ AWS scan failed: {e}")
692
+ return
693
+
694
+ # 3. Find orphaned services
695
+ orphaned = [service for service in aws_services if service not in github_services]
696
+
697
+ if not orphaned:
698
+ p("\n✅ [bold green]No orphaned AWS resources found![/bold green]")
699
+ return
700
+
701
+ p(f"\n🚨 [bold red]Found {len(orphaned)} orphaned AWS resources:[/bold red]")
702
+ for service in orphaned:
703
+ p(f" 💰 {service} (consuming resources)")
704
+
705
+ if dry_run:
706
+ p(f"\n🎪 [bold yellow]DRY RUN[/bold yellow] - Would delete {len(orphaned)} services")
707
+ return
708
+
709
+ if not force:
710
+ confirm = typer.confirm(f"Delete {len(orphaned)} orphaned AWS services?")
711
+ if not confirm:
712
+ p("🎪 Cancelled")
713
+ return
714
+
715
+ # 4. Delete orphaned resources
716
+ deleted_count = 0
717
+ for service in orphaned:
718
+ p(f"🗑️ Deleting {service}...")
719
+ try:
720
+ # Extract PR number for delete_environment call
721
+ pr_match = service.replace("pr-", "").replace("-service", "")
722
+ parts = pr_match.split("-")
723
+ if len(parts) >= 2:
724
+ pr_number = int(parts[0])
725
+ success = aws.delete_environment(service, pr_number)
726
+ if success:
727
+ p(f"✅ Deleted {service}")
728
+ deleted_count += 1
729
+ else:
730
+ p(f"❌ Failed to delete {service}")
731
+ else:
732
+ p(f"❌ Invalid service name format: {service}")
733
+ except Exception as e:
734
+ p(f"❌ Error deleting {service}: {e}")
735
+
736
+ p(f"\n🎪 ✅ Cleanup complete: deleted {deleted_count}/{len(orphaned)} services")
737
+
738
+ except Exception as e:
739
+ p(f"❌ AWS cleanup failed: {e}")
740
+
741
+
618
742
  @app.command()
619
743
  def cleanup(
620
744
  dry_run: bool = typer.Option(False, "--dry-run", help="Show what would be cleaned"),
showtime/core/aws.py CHANGED
@@ -175,36 +175,62 @@ class AWSInterface:
175
175
 
176
176
  def delete_environment(self, service_name: str, pr_number: int) -> bool:
177
177
  """
178
- Delete ephemeral environment - replicates cleanup GHA logic
178
+ Delete ephemeral environment with proper verification
179
179
 
180
180
  Steps:
181
- 1. Check if ECS service exists and is active
182
- 2. Delete ECS service with --force
181
+ 1. Check if ECS service exists
182
+ 2. Delete ECS service with force and wait for completion
183
183
  3. Delete ECR image tag
184
+ 4. Verify deletion completed
184
185
  """
185
186
  try:
186
- # Step 1: Check if service exists and is active
187
- if not self._service_exists(service_name):
188
- return True # Already deleted
189
-
190
- # Step 2: Delete ECS service (force delete)
191
- self.ecs_client.delete_service(cluster=self.cluster, service=service_name, force=True)
187
+ ecs_service_name = f"{service_name}-service" if not service_name.endswith("-service") else service_name
188
+ print(f"🗑️ Deleting ECS service: {ecs_service_name}")
189
+
190
+ # Step 1: Check if service exists
191
+ if not self._service_exists(ecs_service_name):
192
+ print(f"✅ Service {ecs_service_name} already deleted")
193
+ return True
194
+
195
+ # Step 2: Delete ECS service (force delete) and wait
196
+ print(f"☁️ Initiating ECS service deletion...")
197
+ delete_response = self.ecs_client.delete_service(
198
+ cluster=self.cluster,
199
+ service=ecs_service_name,
200
+ force=True
201
+ )
202
+ print(f"🔄 Delete initiated: {delete_response.get('service', {}).get('status', 'unknown')}")
203
+
204
+ # Step 3: Wait for deletion to complete (crucial!)
205
+ print(f"⏳ Waiting for service deletion to complete...")
206
+ deletion_success = self._wait_for_service_deletion(ecs_service_name, timeout_minutes=10)
207
+
208
+ if not deletion_success:
209
+ print(f"⚠️ Service deletion timeout - service may still exist")
210
+ return False
192
211
 
193
- # Step 3: Delete ECR image tag
194
- # Extract SHA from service name: pr-1234-abc123f → abc123f
195
- sha = service_name.split("-")[-1]
196
- image_tag = f"pr-{pr_number}-{sha}"
212
+ # Step 4: Delete ECR image tag
213
+ print(f"🐳 Cleaning up Docker image...")
214
+ # Fix SHA extraction: pr-34831-ac533ec-service → ac533ec
215
+ # Remove "pr-" prefix and "-service" suffix, then get SHA (last part)
216
+ base_name = service_name.replace("pr-", "").replace("-service", "")
217
+ parts = base_name.split("-")
218
+ sha = parts[-1] if len(parts) > 1 else base_name # Last part is SHA
219
+ image_tag = f"pr-{pr_number}-{sha}-ci"
197
220
 
198
221
  try:
199
222
  self.ecr_client.batch_delete_image(
200
223
  repositoryName=self.repository, imageIds=[{"imageTag": image_tag}]
201
224
  )
225
+ print(f"✅ Deleted ECR image: {image_tag}")
202
226
  except self.ecr_client.exceptions.ImageNotFoundException:
203
- pass # Image already deleted
227
+ print(f"ℹ️ ECR image {image_tag} already deleted")
204
228
 
229
+ print(f"✅ Environment {service_name} fully deleted")
205
230
  return True
206
231
 
207
232
  except Exception as e:
233
+ print(f"❌ AWS deletion failed: {e}")
208
234
  raise AWSError(
209
235
  message=str(e), operation="delete_environment", resource=service_name
210
236
  ) from e
@@ -670,6 +696,28 @@ class AWSInterface:
670
696
  print(f"❌ Failed to find expired services: {e}")
671
697
  return []
672
698
 
699
+ def find_showtime_services(self) -> List[str]:
700
+ """Find all ECS services managed by showtime (pr-* pattern)"""
701
+ try:
702
+ # List all services in cluster
703
+ response = self.ecs_client.list_services(cluster=self.cluster)
704
+
705
+ if not response.get("serviceArns"):
706
+ return []
707
+
708
+ # Extract service names and filter for showtime pattern
709
+ showtime_services = []
710
+ for service_arn in response["serviceArns"]:
711
+ service_name = service_arn.split("/")[-1] # Extract name from ARN
712
+ if service_name.startswith("pr-") and "-service" in service_name:
713
+ showtime_services.append(service_name)
714
+
715
+ return sorted(showtime_services)
716
+
717
+ except Exception as e:
718
+ print(f"❌ Failed to find showtime services: {e}")
719
+ return []
720
+
673
721
  def _find_pr_services(self, pr_number: int) -> List[Dict[str, Any]]:
674
722
  """Find all ECS services for a specific PR"""
675
723
  try:
@@ -356,55 +356,93 @@ class PullRequest:
356
356
  all_environments = []
357
357
  for pr_number in pr_numbers:
358
358
  pr = cls.from_id(pr_number)
359
- if pr.current_show:
360
- status = pr.get_status()
361
- status["pr_number"] = pr_number
362
- all_environments.append(status)
359
+ # Show ALL environments, not just current_show
360
+ for show in pr.shows:
361
+ # Determine show type based on pointer presence
362
+ show_type = "orphaned" # Default
363
+
364
+ # Check for active pointer
365
+ if any(label == f"🎪 🎯 {show.sha}" for label in pr.labels):
366
+ show_type = "active"
367
+ # Check for building pointer
368
+ elif any(label == f"🎪 🏗️ {show.sha}" for label in pr.labels):
369
+ show_type = "building"
370
+ # No pointer = orphaned
371
+
372
+ environment_data = {
373
+ "pr_number": pr_number,
374
+ "status": "active", # Keep for compatibility
375
+ "show": {
376
+ "sha": show.sha,
377
+ "status": show.status,
378
+ "ip": show.ip,
379
+ "ttl": show.ttl,
380
+ "requested_by": show.requested_by,
381
+ "created_at": show.created_at,
382
+ "aws_service_name": show.aws_service_name,
383
+ "show_type": show_type, # New field for display
384
+ },
385
+ }
386
+ all_environments.append(environment_data)
363
387
 
364
388
  return all_environments
365
389
 
366
390
  def _determine_action(self, target_sha: str) -> str:
367
- """Determine what sync action is needed"""
391
+ """Determine what sync action is needed based on target SHA state"""
392
+ target_sha_short = target_sha[:7] # Ensure we're working with short SHA
393
+
394
+ # Get the specific show for the target SHA
395
+ target_show = self.get_show_by_sha(target_sha_short)
396
+
368
397
  # Check for explicit trigger labels
369
398
  trigger_labels = [label for label in self.labels if "showtime-trigger-" in label]
370
399
 
371
400
  if trigger_labels:
372
401
  for trigger in trigger_labels:
373
402
  if "showtime-trigger-start" in trigger:
374
- if self.current_show and self.current_show.status == "failed":
375
- return "create_environment" # Replace failed environment
376
- elif self.current_show and self.current_show.needs_update(target_sha):
377
- return "rolling_update"
378
- elif self.current_show:
379
- return "no_action" # Same commit, healthy environment
403
+ if not target_show or target_show.status == "failed":
404
+ return "create_environment" # New SHA or failed SHA
405
+ elif target_show.status in ["building", "built", "deploying"]:
406
+ return "no_action" # Target SHA already in progress
407
+ elif target_show.status == "running":
408
+ return "create_environment" # Force rebuild with trigger
380
409
  else:
381
- return "create_environment"
410
+ return "create_environment" # Default for unknown states
382
411
  elif "showtime-trigger-stop" in trigger:
383
412
  return "destroy_environment"
384
413
 
385
- # No explicit triggers - check for auto-sync or creation
386
- if self.current_show and self.current_show.status != "failed" and self.current_show.needs_update(target_sha):
387
- return "auto_sync"
388
- elif not self.current_show or self.current_show.status == "failed":
389
- # No environment exists OR failed environment - allow creation without trigger (for CLI start)
414
+ # No explicit triggers - check target SHA state
415
+ if not target_show:
416
+ # Target SHA doesn't exist - create it
417
+ return "create_environment"
418
+ elif target_show.status == "failed":
419
+ # Target SHA failed - rebuild it
390
420
  return "create_environment"
421
+ elif target_show.status in ["building", "built", "deploying"]:
422
+ # Target SHA in progress - wait
423
+ return "no_action"
424
+ elif target_show.status == "running":
425
+ # Target SHA already running - no action needed
426
+ return "no_action"
391
427
 
392
428
  return "no_action"
393
429
 
394
430
  def _atomic_claim(self, target_sha: str, action: str, dry_run: bool = False) -> bool:
395
- """Atomically claim this PR for the current job"""
396
- # 1. Validate current state allows this action
431
+ """Atomically claim this PR for the current job based on target SHA state"""
432
+ target_sha_short = target_sha[:7]
433
+ target_show = self.get_show_by_sha(target_sha_short)
434
+
435
+ # 1. Validate current state allows this action for target SHA
397
436
  if action in ["create_environment", "rolling_update", "auto_sync"]:
398
- if self.current_show and self.current_show.status in [
437
+ if target_show and target_show.status in [
399
438
  "building",
400
439
  "built",
401
440
  "deploying",
402
441
  ]:
403
- return False # Another job active
442
+ return False # Target SHA already in progress
404
443
 
405
- # For rolling updates, running environments are OK to update
406
- if action in ["rolling_update", "auto_sync"] and self.current_show and self.current_show.status == "running":
407
- return True # Allow rolling updates on running environments
444
+ # Allow actions on failed, running, or non-existent target SHAs
445
+ return True
408
446
 
409
447
  if dry_run:
410
448
  print(f"🎪 [DRY-RUN] Would atomically claim PR for {action}")
@@ -412,17 +450,31 @@ class PullRequest:
412
450
 
413
451
  # 2. Remove trigger labels (atomic operation)
414
452
  trigger_labels = [label for label in self.labels if "showtime-trigger-" in label]
415
- for trigger_label in trigger_labels:
416
- get_github().remove_label(self.pr_number, trigger_label)
453
+ if trigger_labels:
454
+ print(f"🏷️ Removing trigger labels: {trigger_labels}")
455
+ for trigger_label in trigger_labels:
456
+ get_github().remove_label(self.pr_number, trigger_label)
457
+ else:
458
+ print("🏷️ No trigger labels to remove")
417
459
 
418
460
  # 3. Set building state immediately (claim the PR)
419
461
  if action in ["create_environment", "rolling_update", "auto_sync"]:
420
462
  building_show = self._create_new_show(target_sha)
421
463
  building_show.status = "building"
464
+
422
465
  # Update labels to reflect building state
466
+ print(f"🏷️ Removing existing circus labels...")
423
467
  get_github().remove_circus_labels(self.pr_number)
424
- for label in building_show.to_circus_labels():
425
- get_github().add_label(self.pr_number, label)
468
+
469
+ new_labels = building_show.to_circus_labels()
470
+ print(f"🏷️ Creating new labels: {new_labels}")
471
+ for label in new_labels:
472
+ try:
473
+ get_github().add_label(self.pr_number, label)
474
+ print(f" ✅ Added: {label}")
475
+ except Exception as e:
476
+ print(f" ❌ Failed to add {label}: {e}")
477
+ raise
426
478
 
427
479
  return True
428
480
 
showtime/core/show.py CHANGED
@@ -175,7 +175,7 @@ class Show:
175
175
  "--platform",
176
176
  "linux/amd64",
177
177
  "--target",
178
- "ci",
178
+ "dev",
179
179
  "--build-arg",
180
180
  "INCLUDE_CHROMIUM=false",
181
181
  "--build-arg",
@@ -202,16 +202,14 @@ class Show:
202
202
  ])
203
203
  print("🐳 Local environment: Using cache-from only (no export)")
204
204
 
205
- # Add --load only when building for native architecture or explicitly requested
206
- # Intel Mac/Linux can load linux/amd64, Apple Silicon cannot
207
- native_x86 = platform.machine() in ("x86_64", "AMD64")
205
+ # Add --load only when explicitly requested for local testing
208
206
  force_load = os.getenv("DOCKER_LOAD", "false").lower() == "true"
209
207
 
210
- if native_x86 or force_load:
208
+ if force_load:
211
209
  cmd.append("--load")
212
- print("🐳 Will load image to local Docker daemon (native x86_64 platform)")
210
+ print("🐳 Will load image to local Docker daemon (DOCKER_LOAD=true)")
213
211
  else:
214
- print("🐳 Cross-platform build - pushing to registry only (no local load)")
212
+ print("🐳 Push-only build (no local load) - faster for CI/deployment")
215
213
 
216
214
  # Add build context path last
217
215
  cmd.append(".")
@@ -270,8 +268,10 @@ class Show:
270
268
  elif emoji == "🤡": # User (clown!)
271
269
  show_data["requested_by"] = value
272
270
 
273
- # Only return Show if we found relevant labels for this SHA
274
- if any(label.endswith(f" {sha}") for label in labels if "🎯" in label or "🏗️" in label):
271
+ # Return Show if we found any status labels for this SHA
272
+ # For list purposes, we want to show ALL environments, even orphaned ones
273
+ has_status = any(label.startswith(f"🎪 {sha} 🚦 ") for label in labels)
274
+ if has_status:
275
275
  return cls(**show_data) # type: ignore[arg-type]
276
276
 
277
277
  return None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: superset-showtime
3
- Version: 0.4.9
3
+ Version: 0.5.1
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,16 @@
1
+ showtime/__init__.py,sha256=5ryBVT5km-Ec4J3VGcfmM-qk9YlhGYz1UuGdv8d9vZg,448
2
+ showtime/__main__.py,sha256=EVaDaTX69yIhCzChg99vqvFSCN4ELstEt7Mpb9FMZX8,109
3
+ showtime/cli.py,sha256=cQB5kH-XWFX3MhsxRaChVdjVe_I_7kBVyFY4fQrHPWA,30542
4
+ showtime/core/__init__.py,sha256=54hbdFNGrzuNMBdraezfjT8Zi6g221pKlJ9mREnKwCw,34
5
+ showtime/core/aws.py,sha256=uTjJAvEBQMyTccS93WZeNPhfeKQhJgOQQ0BJdnQjvCU,35007
6
+ showtime/core/emojis.py,sha256=MHEDuPIdfNiop4zbNLuviz3eY05QiftYSHHCVbkfKhw,2129
7
+ showtime/core/github.py,sha256=uETvKDO2Yhpqg3fxLtrKaCuZR3b-1LVmgnf5aLcqrAQ,9988
8
+ showtime/core/github_messages.py,sha256=MfgwCukrEsWWesMsuL8saciDgP4nS-gijzu8DXr-Alg,7450
9
+ showtime/core/label_colors.py,sha256=efhbFnz_3nqEnEqmgyF6_hZbxtCu_fmb68BIIUpSsnk,3895
10
+ showtime/core/pull_request.py,sha256=PnDTvC57YDNxz2XwhZi0Zf5FHzeYqJyfdlCmlI85GNo,24071
11
+ showtime/core/show.py,sha256=P2LCVtzG6P2HXR9_oYX3OZcDeQJpngee4sw-iv62vac,9714
12
+ showtime/data/ecs-task-definition.json,sha256=2acmqoF-3CxaBJP_VDkMMpG_U2RI4VPk1JvFOprMFyc,2098
13
+ superset_showtime-0.5.1.dist-info/METADATA,sha256=JjLoKU2jo9fmxNwFW34JDRTwZMlTaqpDgcP38c9akms,12052
14
+ superset_showtime-0.5.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
+ superset_showtime-0.5.1.dist-info/entry_points.txt,sha256=rDW7oZ57mqyBUS4N_3_R7bZNGVHB-104jwmY-hHC_ck,85
16
+ superset_showtime-0.5.1.dist-info/RECORD,,
@@ -1,16 +0,0 @@
1
- showtime/__init__.py,sha256=lTJs8tOZFpuWaI6-1gqI-aev4BAX-KGCMG3TPglSKxQ,448
2
- showtime/__main__.py,sha256=EVaDaTX69yIhCzChg99vqvFSCN4ELstEt7Mpb9FMZX8,109
3
- showtime/cli.py,sha256=faFM6pe3gz49_1KrzUeri7dQffqz4WP92JmGxPaIOC0,25249
4
- showtime/core/__init__.py,sha256=54hbdFNGrzuNMBdraezfjT8Zi6g221pKlJ9mREnKwCw,34
5
- showtime/core/aws.py,sha256=REeZ6_1C9f6mBchBAGa1MeDJeZIwir4IJ92HLRcK5ok,32636
6
- showtime/core/emojis.py,sha256=MHEDuPIdfNiop4zbNLuviz3eY05QiftYSHHCVbkfKhw,2129
7
- showtime/core/github.py,sha256=uETvKDO2Yhpqg3fxLtrKaCuZR3b-1LVmgnf5aLcqrAQ,9988
8
- showtime/core/github_messages.py,sha256=MfgwCukrEsWWesMsuL8saciDgP4nS-gijzu8DXr-Alg,7450
9
- showtime/core/label_colors.py,sha256=efhbFnz_3nqEnEqmgyF6_hZbxtCu_fmb68BIIUpSsnk,3895
10
- showtime/core/pull_request.py,sha256=EMeceF2NgqNyL_cCNv4345_2Cb6l0cAXb8lNV9pu8hc,21924
11
- showtime/core/show.py,sha256=-nMRShKWTjXGVuxuxrc0WK6l8ON-8iYm5QA8uvGoMOk,9806
12
- showtime/data/ecs-task-definition.json,sha256=2acmqoF-3CxaBJP_VDkMMpG_U2RI4VPk1JvFOprMFyc,2098
13
- superset_showtime-0.4.9.dist-info/METADATA,sha256=a-Mvygwwxhx3DLMVUUpFi8np70nF6RR1VSj-1fB-VLQ,12052
14
- superset_showtime-0.4.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
- superset_showtime-0.4.9.dist-info/entry_points.txt,sha256=rDW7oZ57mqyBUS4N_3_R7bZNGVHB-104jwmY-hHC_ck,85
16
- superset_showtime-0.4.9.dist-info/RECORD,,