charisma-cli 0.1.2__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.
@@ -0,0 +1,134 @@
1
+ # Dependencies
2
+ node_modules/
3
+ npm-debug.log*
4
+ yarn-debug.log*
5
+ yarn-error.log*
6
+
7
+ # Environment variables
8
+ .env
9
+ .env.local
10
+ .env.*.local
11
+
12
+ # IDE
13
+ .vscode/*
14
+ !.vscode/settings.json
15
+ !.vscode/tasks.json
16
+ !.vscode/launch.json
17
+ !.vscode/extensions.json
18
+ .idea/
19
+ *.swp
20
+ *.swo
21
+ *~
22
+
23
+ # OS
24
+ .DS_Store
25
+ Thumbs.db
26
+
27
+ # Build outputs
28
+ dist/
29
+ build/
30
+ *.log
31
+
32
+ # Local pipeline secrets (do not commit)
33
+ .env.pipeline
34
+
35
+ # Database
36
+ *.sqlite
37
+ *.db
38
+
39
+ # Test coverage
40
+ coverage/
41
+ .nyc_output/
42
+
43
+ # Temporary files
44
+ tmp/
45
+ temp/
46
+ *.tmp
47
+
48
+ # Confluence sync metadata (optional - can be committed)
49
+ # .amazonq/knowledge-base/.confluence-sync.json
50
+
51
+ # Nuxt build outputs
52
+ .nuxt/
53
+ .output/
54
+ dist/
55
+
56
+ # Python cache
57
+ *.pyc
58
+ */__pycache__/
59
+
60
+ # Python virtual environments
61
+ .venv/
62
+ venv/
63
+
64
+ # Hypothesys cache
65
+ .hypothesis/
66
+
67
+ # Kiro hooks (auto-generated)
68
+ .kiro/hooks/
69
+ .kiro/settings/*
70
+ .kiro/settings/mcp.json
71
+
72
+ # Kiro spec config (local)
73
+ .kiro/specs/**/.config.kiro
74
+
75
+ # Coverage
76
+ .coverage
77
+
78
+ # AWS SAM build artifacts
79
+ .aws-sam/
80
+
81
+ # Zip files in repo root (test artifacts, allure exports, etc.)
82
+ *.zip
83
+
84
+ # Playwright test results
85
+ frontend/test-results/
86
+ frontend/playwright-report/
87
+ frontend/e2e-results/
88
+ test-results/
89
+
90
+ # E2E auth state (contains secrets)
91
+ frontend/e2e/.auth/
92
+
93
+ # E2E test environment (contains secrets)
94
+ frontend/.env.e2e
95
+
96
+
97
+ test-reporting.code-workspace
98
+
99
+
100
+ .vscode/extensions.json
101
+ Aurora.session.sql
102
+ Charisma DB.session.sql
103
+
104
+
105
+ # API integration test setup scripts (contains sensitive token generation)
106
+ backend/api_integration_test_setup_scripts/
107
+
108
+ # Local test/debug scripts
109
+ scripts/test-ingestion-token.py
110
+
111
+ # vs code settings
112
+ .vscode/settings.json
113
+ backend/integration_test_report.xml
114
+ backend/unit_test_report.xml
115
+ frontend/app/pages/proto.vue
116
+ scripts/connect-aurora.ps1
117
+
118
+ # mock test result files
119
+ test-data-junit-debug*
120
+
121
+ # scripts for creating mock data
122
+ scripts/ingest-test-run/*
123
+
124
+ # --- ltm-power ---
125
+ ltm/*
126
+ ltm/store/*.jsonl
127
+ ltm/runtime/*
128
+ ltm/reports/*
129
+ ltm/snapshots/*
130
+ .kiro/steering/ltm-memory-format.md
131
+ .kiro/steering/ltm-operations.md
132
+ backend/coverage.json
133
+ */__pycache__/*
134
+ allure-results/*
@@ -0,0 +1,98 @@
1
+ Metadata-Version: 2.5
2
+ Name: charisma-cli
3
+ Version: 0.1.2
4
+ Summary: CLI tool that watches allure-results and streams test results + attachments to Charisma.
5
+ Author: Charisma Team
6
+ License-Expression: MIT
7
+ Classifier: Development Status :: 2 - Pre-Alpha
8
+ Classifier: Environment :: Console
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Software Development :: Testing
16
+ Requires-Python: >=3.10
17
+ Requires-Dist: click<9.0,>=8.0
18
+ Requires-Dist: httpx<1.0,>=0.24
19
+ Requires-Dist: watchdog<5.0,>=3.0
20
+ Provides-Extra: dev
21
+ Requires-Dist: hypothesis; extra == 'dev'
22
+ Requires-Dist: pytest<10.0,>=7.0; extra == 'dev'
23
+ Requires-Dist: respx; extra == 'dev'
24
+ Description-Content-Type: text/markdown
25
+
26
+ # charisma-cli
27
+
28
+ CLI tool that watches an `allure-results` directory during test execution and streams results + attachments to the Charisma ingestion API in real time.
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ pip install charisma-cli
34
+ ```
35
+
36
+ No token, no index URL — works on any laptop and in any CI. The package is published to public PyPI; the Charisma repo itself stays private (only this client wheel is public).
37
+
38
+ For local development from a checkout of the repo:
39
+
40
+ ```bash
41
+ pip install -e packages/charisma-cli
42
+ ```
43
+
44
+ <details>
45
+ <summary>Alternative: install from the GitHub Packages registry (PyOCI)</summary>
46
+
47
+ Also published to GitHub Packages via [PyOCI](https://pyoci.com) (requires a GitHub token with `read:packages`) — useful for a build not yet on PyPI:
48
+
49
+ ```bash
50
+ pip install charisma-cli \
51
+ --index-url "https://__token__:$GITHUB_TOKEN@pyoci.com/ghcr.io/align-QCOE/"
52
+ ```
53
+
54
+ </details>
55
+
56
+ ## Publishing (maintainers)
57
+
58
+ Releases publish to public PyPI automatically from the merge-queue workflow using a **PyPI API token**.
59
+
60
+ > Trusted publishing (OIDC) is not used because this repo runs on **GitHub Enterprise Server**, whose OIDC issuer PyPI does not trust. A stored API token is required instead.
61
+
62
+ One-time setup (done once by a PyPI project owner):
63
+
64
+ 1. Sign in to [PyPI](https://pypi.org) with the team-owned account (2FA enabled).
65
+ 2. Go to **Account settings → API tokens → Add API token**. Scope it to the `charisma-cli` project once the project exists; for the very first publish use an account-scoped token, then re-scope it to the project afterward.
66
+ 3. Copy the token (starts with `pypi-`).
67
+ 4. In the GitHub Enterprise repo, add it as an Actions secret named **`PYPI_API_TOKEN`** (org-level secret recommended so both packages share it).
68
+ 5. Bump `version` in `pyproject.toml` and merge — the workflow builds and publishes on the next release.
69
+
70
+ Rotate the token periodically and store it in the team secret manager.
71
+
72
+ ## Usage
73
+
74
+ ```bash
75
+ export CHARISMA_ENDPOINT=https://charisma.company.com
76
+ export CHARISMA_TOKEN=<your-service-token>
77
+ export CHARISMA_PROJECT_ID=my-project
78
+
79
+ charismactl watch --results allure-results -- uv run pytest -n 4 tests/
80
+ ```
81
+
82
+ ## Environment Variables
83
+
84
+ | Variable | Required | Description |
85
+ | --------------------- | -------- | -------------------------------------------------- |
86
+ | `CHARISMA_ENDPOINT` | Yes | Base URL of the Charisma API |
87
+ | `CHARISMA_TOKEN` | Yes | Service or CI bearer token |
88
+ | `CHARISMA_PROJECT_ID` | Yes | Project alias for result routing |
89
+ | `CHARISMA_RESULTS` | No | Path to allure-results (default: `allure-results`) |
90
+
91
+ ## Flags
92
+
93
+ | Flag | Description |
94
+ | ---------------- | -------------------------------------------- |
95
+ | `--results PATH` | Allure results directory (overrides env var) |
96
+ | `--project ID` | Project alias (overrides env var) |
97
+ | `--silent` | Don't fail pipeline if upload errors occur |
98
+ | `--skip-too-big` | Skip result files larger than 2MB |
@@ -0,0 +1,73 @@
1
+ # charisma-cli
2
+
3
+ CLI tool that watches an `allure-results` directory during test execution and streams results + attachments to the Charisma ingestion API in real time.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install charisma-cli
9
+ ```
10
+
11
+ No token, no index URL — works on any laptop and in any CI. The package is published to public PyPI; the Charisma repo itself stays private (only this client wheel is public).
12
+
13
+ For local development from a checkout of the repo:
14
+
15
+ ```bash
16
+ pip install -e packages/charisma-cli
17
+ ```
18
+
19
+ <details>
20
+ <summary>Alternative: install from the GitHub Packages registry (PyOCI)</summary>
21
+
22
+ Also published to GitHub Packages via [PyOCI](https://pyoci.com) (requires a GitHub token with `read:packages`) — useful for a build not yet on PyPI:
23
+
24
+ ```bash
25
+ pip install charisma-cli \
26
+ --index-url "https://__token__:$GITHUB_TOKEN@pyoci.com/ghcr.io/align-QCOE/"
27
+ ```
28
+
29
+ </details>
30
+
31
+ ## Publishing (maintainers)
32
+
33
+ Releases publish to public PyPI automatically from the merge-queue workflow using a **PyPI API token**.
34
+
35
+ > Trusted publishing (OIDC) is not used because this repo runs on **GitHub Enterprise Server**, whose OIDC issuer PyPI does not trust. A stored API token is required instead.
36
+
37
+ One-time setup (done once by a PyPI project owner):
38
+
39
+ 1. Sign in to [PyPI](https://pypi.org) with the team-owned account (2FA enabled).
40
+ 2. Go to **Account settings → API tokens → Add API token**. Scope it to the `charisma-cli` project once the project exists; for the very first publish use an account-scoped token, then re-scope it to the project afterward.
41
+ 3. Copy the token (starts with `pypi-`).
42
+ 4. In the GitHub Enterprise repo, add it as an Actions secret named **`PYPI_API_TOKEN`** (org-level secret recommended so both packages share it).
43
+ 5. Bump `version` in `pyproject.toml` and merge — the workflow builds and publishes on the next release.
44
+
45
+ Rotate the token periodically and store it in the team secret manager.
46
+
47
+ ## Usage
48
+
49
+ ```bash
50
+ export CHARISMA_ENDPOINT=https://charisma.company.com
51
+ export CHARISMA_TOKEN=<your-service-token>
52
+ export CHARISMA_PROJECT_ID=my-project
53
+
54
+ charismactl watch --results allure-results -- uv run pytest -n 4 tests/
55
+ ```
56
+
57
+ ## Environment Variables
58
+
59
+ | Variable | Required | Description |
60
+ | --------------------- | -------- | -------------------------------------------------- |
61
+ | `CHARISMA_ENDPOINT` | Yes | Base URL of the Charisma API |
62
+ | `CHARISMA_TOKEN` | Yes | Service or CI bearer token |
63
+ | `CHARISMA_PROJECT_ID` | Yes | Project alias for result routing |
64
+ | `CHARISMA_RESULTS` | No | Path to allure-results (default: `allure-results`) |
65
+
66
+ ## Flags
67
+
68
+ | Flag | Description |
69
+ | ---------------- | -------------------------------------------- |
70
+ | `--results PATH` | Allure results directory (overrides env var) |
71
+ | `--project ID` | Project alias (overrides env var) |
72
+ | `--silent` | Don't fail pipeline if upload errors occur |
73
+ | `--skip-too-big` | Skip result files larger than 2MB |
@@ -0,0 +1,54 @@
1
+ # Build: hatchling
2
+ [build-system]
3
+ requires = ["hatchling"]
4
+ build-backend = "hatchling.build"
5
+
6
+ [project]
7
+ name = "charisma-cli"
8
+ version = "0.1.2"
9
+ description = "CLI tool that watches allure-results and streams test results + attachments to Charisma."
10
+ readme = "README.md"
11
+ license = "MIT"
12
+ requires-python = ">=3.10"
13
+ authors = [
14
+ { name = "Charisma Team" },
15
+ ]
16
+ classifiers = [
17
+ "Development Status :: 2 - Pre-Alpha",
18
+ "Environment :: Console",
19
+ "Intended Audience :: Developers",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Topic :: Software Development :: Testing",
26
+ ]
27
+ dependencies = [
28
+ "click>=8.0,<9.0",
29
+ "httpx>=0.24,<1.0",
30
+ "watchdog>=3.0,<5.0",
31
+ ]
32
+
33
+ [project.scripts]
34
+ charismactl = "charisma_cli.main:cli"
35
+
36
+ [project.optional-dependencies]
37
+ dev = [
38
+ "pytest>=7.0,<10.0",
39
+ "respx",
40
+ "hypothesis",
41
+ ]
42
+
43
+ [tool.hatch.build.targets.wheel]
44
+ packages = ["src/charisma_cli"]
45
+
46
+ [tool.pytest.ini_options]
47
+ testpaths = ["tests"]
48
+
49
+ [tool.ruff]
50
+ line-length = 120
51
+ target-version = "py310"
52
+
53
+ [tool.ruff.lint]
54
+ select = ["E", "F", "W", "I"]
@@ -0,0 +1,3 @@
1
+ """charisma-cli: Stream allure results to Charisma in real time."""
2
+
3
+ __version__ = "0.1.2"
@@ -0,0 +1,86 @@
1
+ """Configuration dataclass with env var + CLI flag resolution."""
2
+
3
+ import os
4
+ from dataclasses import dataclass
5
+
6
+ import click
7
+
8
+
9
+ @dataclass
10
+ class Config:
11
+ """Resolved configuration for charismactl.
12
+
13
+ Fields are populated via from_env() which merges CLI flag overrides
14
+ with environment variable fallbacks.
15
+ """
16
+
17
+ endpoint: str
18
+ token: str
19
+ project: str
20
+ results_dir: str
21
+ silent: bool
22
+ skip_too_big: bool
23
+ drain_timeout: int
24
+ build_id: str | None
25
+ commit_sha: str | None
26
+ branch: str | None
27
+
28
+ @classmethod
29
+ def from_env(
30
+ cls,
31
+ *,
32
+ endpoint: str | None = None,
33
+ token: str | None = None,
34
+ project: str | None = None,
35
+ results: str | None = None,
36
+ silent: bool = False,
37
+ skip_too_big: bool = False,
38
+ drain_timeout: int = 30,
39
+ ) -> "Config":
40
+ """Build Config by resolving CLI flags over environment variables.
41
+
42
+ Resolution order: CLI flag (if not None) > env var > default.
43
+
44
+ Args:
45
+ endpoint: --endpoint flag override
46
+ token: --token flag override
47
+ project: --project flag override
48
+ results: --results flag override
49
+ silent: --silent flag
50
+ skip_too_big: --skip-too-big flag
51
+ drain_timeout: drain timeout in seconds
52
+
53
+ Returns:
54
+ Fully resolved Config instance.
55
+
56
+ Raises:
57
+ click.UsageError: If required fields (endpoint, token) are missing.
58
+ """
59
+ resolved_endpoint = (endpoint or os.getenv("CHARISMA_ENDPOINT", "")).strip()
60
+ resolved_token = (token or os.getenv("CHARISMA_TOKEN", "")).strip()
61
+ resolved_project = (project or os.getenv("CHARISMA_PROJECT_ID", "")).strip()
62
+ resolved_results = results or os.getenv("CHARISMA_RESULTS", "allure-results")
63
+
64
+ if not resolved_endpoint:
65
+ raise click.UsageError(
66
+ "Missing required configuration: endpoint. "
67
+ "Set CHARISMA_ENDPOINT or pass --endpoint."
68
+ )
69
+ if not resolved_token:
70
+ raise click.UsageError(
71
+ "Missing required configuration: token. "
72
+ "Set CHARISMA_TOKEN or pass --token."
73
+ )
74
+
75
+ return cls(
76
+ endpoint=resolved_endpoint,
77
+ token=resolved_token,
78
+ project=resolved_project,
79
+ results_dir=resolved_results,
80
+ silent=silent,
81
+ skip_too_big=skip_too_big,
82
+ drain_timeout=drain_timeout,
83
+ build_id=os.getenv("BUILD_ID"),
84
+ commit_sha=os.getenv("COMMIT_SHA"),
85
+ branch=os.getenv("BRANCH"),
86
+ )
@@ -0,0 +1,206 @@
1
+ """CLI entry point for charismactl."""
2
+
3
+ import signal
4
+ import sys
5
+ from pathlib import Path
6
+ from queue import PriorityQueue
7
+
8
+ import click
9
+
10
+ from charisma_cli import __version__
11
+ from charisma_cli.config import Config
12
+ from charisma_cli.models import FileEvent
13
+ from charisma_cli.subprocess_mgr import SubprocessManager
14
+ from charisma_cli.uploader import Uploader
15
+ from charisma_cli.watcher import ResultsWatcher, classify_file
16
+
17
+
18
+ @click.group()
19
+ @click.version_option(version=__version__, prog_name="charismactl")
20
+ def cli() -> None:
21
+ """Stream allure test results to Charisma in real time."""
22
+
23
+
24
+ def _resolve_config(
25
+ endpoint: str | None,
26
+ token: str | None,
27
+ project: str,
28
+ results: str,
29
+ silent: bool,
30
+ skip_too_big: bool,
31
+ ) -> Config:
32
+ """Resolve CLI config from flags + env vars. Re-raises UsageError on failure."""
33
+ return Config.from_env(
34
+ endpoint=endpoint,
35
+ token=token,
36
+ project=project,
37
+ results=results,
38
+ silent=silent,
39
+ skip_too_big=skip_too_big,
40
+ )
41
+
42
+
43
+ def _open_launch_or_exit(uploader: Uploader, config: Config) -> str:
44
+ """Open a launch, handling failure based on silent mode.
45
+
46
+ Returns:
47
+ The launch ID on success.
48
+
49
+ Exits:
50
+ sys.exit(1) if non-silent and launch open fails.
51
+ Returns empty string if silent and launch fails (caller must handle).
52
+ """
53
+ launch_id = uploader.open_launch()
54
+ if launch_id is None:
55
+ if config.silent:
56
+ return ""
57
+ click.echo("Error: Failed to open launch. Exiting.", err=True)
58
+ sys.exit(1)
59
+ return launch_id
60
+
61
+
62
+ def _print_summary(uploader: Uploader) -> None:
63
+ """Print the upload summary to stdout."""
64
+ summary = uploader.summary
65
+ click.echo(
66
+ f"Summary: results_sent={summary.results_sent}, "
67
+ f"results_failed={summary.results_failed}, "
68
+ f"attachments_sent={summary.attachments_sent}, "
69
+ f"attachments_skipped={summary.attachments_skipped}, "
70
+ f"containers_sent={summary.containers_sent}"
71
+ )
72
+
73
+
74
+ @cli.command()
75
+ @click.option("--endpoint", envvar="CHARISMA_ENDPOINT", default=None, help="Charisma API endpoint.")
76
+ @click.option("--token", envvar="CHARISMA_TOKEN", default=None, help="Bearer token.")
77
+ @click.option("--results", envvar="CHARISMA_RESULTS", default="allure-results", help="Allure results directory.")
78
+ @click.option("--project", envvar="CHARISMA_PROJECT_ID", required=True, help="Project alias.")
79
+ @click.option("--silent", is_flag=True, default=False, help="Don't fail if upload errors occur.")
80
+ @click.option("--skip-too-big", is_flag=True, default=False, help="Skip result files larger than 2MB.")
81
+ @click.argument("command", nargs=-1, required=True)
82
+ def watch(
83
+ endpoint: str | None,
84
+ token: str | None,
85
+ results: str,
86
+ project: str,
87
+ silent: bool,
88
+ skip_too_big: bool,
89
+ command: tuple[str, ...],
90
+ ) -> None:
91
+ """Watch allure-results and stream to Charisma while running COMMAND.
92
+
93
+ Usage: charismactl watch --project my-project -- pytest -n 4 tests/
94
+ """
95
+ config = _resolve_config(endpoint, token, project, results, silent, skip_too_big)
96
+
97
+ queue: PriorityQueue[FileEvent] = PriorityQueue()
98
+ uploader = Uploader(config, queue)
99
+ uploader.configure_logging()
100
+
101
+ # Open launch (Uploader owns the client)
102
+ launch_id = _open_launch_or_exit(uploader, config)
103
+ if not launch_id:
104
+ # Silent degradation — run subprocess without uploads
105
+ mgr = SubprocessManager()
106
+ mgr.spawn(command)
107
+ sys.exit(mgr.wait())
108
+
109
+ # Start watcher + uploader consumer
110
+ watcher = ResultsWatcher(config.results_dir, queue, config)
111
+ watcher.start()
112
+ uploader.start()
113
+
114
+ # Spawn subprocess with signal forwarding
115
+ mgr = SubprocessManager()
116
+
117
+ def _forward_signal(sig: int, _frame: object) -> None:
118
+ mgr.forward_signal(sig)
119
+
120
+ try:
121
+ signal.signal(signal.SIGTERM, _forward_signal)
122
+ except (OSError, ValueError):
123
+ pass
124
+ try:
125
+ signal.signal(signal.SIGINT, _forward_signal)
126
+ except (OSError, ValueError):
127
+ pass
128
+
129
+ mgr.spawn(command)
130
+ exit_code = mgr.wait()
131
+
132
+ # After subprocess exits (crash or normal): flush debouncing files and scan
133
+ # for anything the watcher missed entirely. This prevents data loss when
134
+ # parallel test workers write results in bursts just before crash.
135
+ watcher.flush_pending()
136
+ watcher.final_scan()
137
+
138
+ # Drain and close — stop watcher AFTER drain so all files are processed
139
+ uploader.drain(timeout=config.drain_timeout)
140
+ watcher.stop()
141
+ uploader.stop()
142
+
143
+ uploader.ensure_client()
144
+ uploader.close_launch()
145
+ if uploader._client is not None:
146
+ uploader._client.close()
147
+ uploader._client = None
148
+
149
+ _print_summary(uploader)
150
+ sys.exit(exit_code)
151
+
152
+
153
+ @cli.command()
154
+ @click.option("--endpoint", envvar="CHARISMA_ENDPOINT", default=None, help="Charisma API endpoint.")
155
+ @click.option("--token", envvar="CHARISMA_TOKEN", default=None, help="Bearer token.")
156
+ @click.option("--results", envvar="CHARISMA_RESULTS", default="allure-results", help="Allure results directory.")
157
+ @click.option("--project", envvar="CHARISMA_PROJECT_ID", required=True, help="Project alias.")
158
+ @click.option("--silent", is_flag=True, default=False, help="Don't fail if upload errors occur.")
159
+ @click.option("--skip-too-big", is_flag=True, default=False, help="Skip result files larger than 2MB.")
160
+ def upload(
161
+ endpoint: str | None,
162
+ token: str | None,
163
+ results: str,
164
+ project: str,
165
+ silent: bool,
166
+ skip_too_big: bool,
167
+ ) -> None:
168
+ """Upload completed allure-results to Charisma (post-execution).
169
+
170
+ Usage: charismactl upload --project my-project --results allure-results
171
+ """
172
+ config = _resolve_config(endpoint, token, project, results, silent, skip_too_big)
173
+
174
+ queue: PriorityQueue[FileEvent] = PriorityQueue()
175
+ uploader = Uploader(config, queue)
176
+ uploader.configure_logging()
177
+
178
+ # Open launch
179
+ launch_id = _open_launch_or_exit(uploader, config)
180
+ if not launch_id:
181
+ click.echo("Warning: Failed to open launch. No results uploaded.", err=True)
182
+ sys.exit(0)
183
+
184
+ # Scan existing results directory
185
+ results_path = Path(config.results_dir)
186
+ if not results_path.exists():
187
+ click.echo(f"Error: Results directory '{config.results_dir}' does not exist.", err=True)
188
+ sys.exit(1)
189
+
190
+ for filepath in sorted(results_path.iterdir()):
191
+ if filepath.is_file():
192
+ category = classify_file(filepath.name)
193
+ if category is not None:
194
+ queue.put(FileEvent(path=filepath, category=category))
195
+
196
+ # Process and drain
197
+ uploader.start()
198
+ uploader.drain(timeout=config.drain_timeout)
199
+ uploader.stop()
200
+
201
+ # Close launch
202
+ uploader.ensure_client()
203
+ uploader.close_launch()
204
+ uploader.stop()
205
+
206
+ _print_summary(uploader)