threatzone 1.0.0__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.
Files changed (97) hide show
  1. threatzone-1.0.0/.actrc +18 -0
  2. threatzone-1.0.0/.github/act-events/pull_request.json +21 -0
  3. threatzone-1.0.0/.github/act-events/push.json +24 -0
  4. threatzone-1.0.0/.github/act-events/release.json +15 -0
  5. threatzone-1.0.0/.github/act-events/workflow_dispatch.json +11 -0
  6. threatzone-1.0.0/.github/workflows/README.md +68 -0
  7. threatzone-1.0.0/.github/workflows/ci.yml +155 -0
  8. threatzone-1.0.0/.github/workflows/publish.yml +199 -0
  9. threatzone-1.0.0/.gitignore +82 -0
  10. threatzone-1.0.0/.python-version +1 -0
  11. threatzone-1.0.0/LICENSE +21 -0
  12. threatzone-1.0.0/PKG-INFO +213 -0
  13. threatzone-1.0.0/README.md +177 -0
  14. threatzone-1.0.0/docs/RECIPES.md +633 -0
  15. threatzone-1.0.0/docs/TYPES.md +244 -0
  16. threatzone-1.0.0/examples/README.md +39 -0
  17. threatzone-1.0.0/examples/recipe_01_submit_and_wait.py +58 -0
  18. threatzone-1.0.0/examples/recipe_02_url_analysis.py +59 -0
  19. threatzone-1.0.0/examples/recipe_03_get_malicious_indicators.py +65 -0
  20. threatzone-1.0.0/examples/recipe_04_download_all_artifacts.py +55 -0
  21. threatzone-1.0.0/examples/recipe_05_search_by_sha256.py +51 -0
  22. threatzone-1.0.0/examples/recipe_06_export_network_iocs.py +70 -0
  23. threatzone-1.0.0/examples/recipe_07_handle_report_unavailable.py +54 -0
  24. threatzone-1.0.0/examples/recipe_08_paginate_submissions.py +50 -0
  25. threatzone-1.0.0/examples/recipe_09_discriminate_errors_by_code.py +72 -0
  26. threatzone-1.0.0/examples/recipe_10_async_client.py +60 -0
  27. threatzone-1.0.0/examples/recipe_11_daily_summary.py +65 -0
  28. threatzone-1.0.0/examples/recipe_12_mitre_cross_reference.py +73 -0
  29. threatzone-1.0.0/pyproject.toml +90 -0
  30. threatzone-1.0.0/scripts/act-ci.sh +32 -0
  31. threatzone-1.0.0/scripts/act-publish.sh +39 -0
  32. threatzone-1.0.0/scripts/sdk-smoke-real-api.py +102 -0
  33. threatzone-1.0.0/src/threatzone/__init__.py +177 -0
  34. threatzone-1.0.0/src/threatzone/_async_client.py +1161 -0
  35. threatzone-1.0.0/src/threatzone/_client.py +391 -0
  36. threatzone-1.0.0/src/threatzone/_config.py +79 -0
  37. threatzone-1.0.0/src/threatzone/_constants.py +16 -0
  38. threatzone-1.0.0/src/threatzone/_exceptions.py +256 -0
  39. threatzone-1.0.0/src/threatzone/_streaming.py +209 -0
  40. threatzone-1.0.0/src/threatzone/_sync_client.py +1158 -0
  41. threatzone-1.0.0/src/threatzone/py.typed +0 -0
  42. threatzone-1.0.0/src/threatzone/testing/__init__.py +24 -0
  43. threatzone-1.0.0/src/threatzone/testing/_responses.py +833 -0
  44. threatzone-1.0.0/src/threatzone/testing/fake_api.py +1232 -0
  45. threatzone-1.0.0/src/threatzone/testing/routes.py +154 -0
  46. threatzone-1.0.0/src/threatzone/testing/scenarios.py +131 -0
  47. threatzone-1.0.0/src/threatzone/testing/state.py +129 -0
  48. threatzone-1.0.0/src/threatzone/types/__init__.py +244 -0
  49. threatzone-1.0.0/src/threatzone/types/behaviours.py +40 -0
  50. threatzone-1.0.0/src/threatzone/types/cdr.py +34 -0
  51. threatzone-1.0.0/src/threatzone/types/common.py +86 -0
  52. threatzone-1.0.0/src/threatzone/types/config.py +52 -0
  53. threatzone-1.0.0/src/threatzone/types/downloads.py +16 -0
  54. threatzone-1.0.0/src/threatzone/types/eml.py +56 -0
  55. threatzone-1.0.0/src/threatzone/types/errors.py +44 -0
  56. threatzone-1.0.0/src/threatzone/types/indicators.py +228 -0
  57. threatzone-1.0.0/src/threatzone/types/me.py +117 -0
  58. threatzone-1.0.0/src/threatzone/types/mitre.py +17 -0
  59. threatzone-1.0.0/src/threatzone/types/network.py +151 -0
  60. threatzone-1.0.0/src/threatzone/types/processes.py +94 -0
  61. threatzone-1.0.0/src/threatzone/types/signature_check.py +29 -0
  62. threatzone-1.0.0/src/threatzone/types/static_scan.py +34 -0
  63. threatzone-1.0.0/src/threatzone/types/submissions.py +124 -0
  64. threatzone-1.0.0/src/threatzone/types/syscalls.py +17 -0
  65. threatzone-1.0.0/src/threatzone/types/url_analysis.py +153 -0
  66. threatzone-1.0.0/tests/__init__.py +1 -0
  67. threatzone-1.0.0/tests/conftest.py +859 -0
  68. threatzone-1.0.0/tests/integration/__init__.py +6 -0
  69. threatzone-1.0.0/tests/integration/conftest.py +61 -0
  70. threatzone-1.0.0/tests/integration/flows/__init__.py +1 -0
  71. threatzone-1.0.0/tests/integration/flows/test_flow_access_control.py +70 -0
  72. threatzone-1.0.0/tests/integration/flows/test_flow_cdr_lifecycle.py +40 -0
  73. threatzone-1.0.0/tests/integration/flows/test_flow_dynamic_lifecycle.py +76 -0
  74. threatzone-1.0.0/tests/integration/flows/test_flow_static_only.py +54 -0
  75. threatzone-1.0.0/tests/integration/flows/test_flow_url_lifecycle.py +52 -0
  76. threatzone-1.0.0/tests/integration/recipes/__init__.py +1 -0
  77. threatzone-1.0.0/tests/integration/recipes/test_recipe_01_submit_and_wait.py +80 -0
  78. threatzone-1.0.0/tests/integration/recipes/test_recipe_02_url_analysis.py +63 -0
  79. threatzone-1.0.0/tests/integration/recipes/test_recipe_03_malicious_indicators.py +71 -0
  80. threatzone-1.0.0/tests/integration/recipes/test_recipe_04_download_artifacts.py +63 -0
  81. threatzone-1.0.0/tests/integration/recipes/test_recipe_05_search_by_sha256.py +38 -0
  82. threatzone-1.0.0/tests/integration/recipes/test_recipe_06_export_network_iocs.py +75 -0
  83. threatzone-1.0.0/tests/integration/recipes/test_recipe_07_report_unavailable.py +76 -0
  84. threatzone-1.0.0/tests/integration/recipes/test_recipe_08_pagination.py +63 -0
  85. threatzone-1.0.0/tests/integration/recipes/test_recipe_09_error_discrimination.py +75 -0
  86. threatzone-1.0.0/tests/integration/recipes/test_recipe_10_async_client.py +67 -0
  87. threatzone-1.0.0/tests/integration/recipes/test_recipe_11_daily_summary.py +53 -0
  88. threatzone-1.0.0/tests/integration/recipes/test_recipe_12_mitre_cross_reference.py +64 -0
  89. threatzone-1.0.0/tests/integration/test_contract_pydantic.py +60 -0
  90. threatzone-1.0.0/tests/integration/test_examples.py +181 -0
  91. threatzone-1.0.0/tests/test_async_client.py +1474 -0
  92. threatzone-1.0.0/tests/test_client.py +517 -0
  93. threatzone-1.0.0/tests/test_exceptions.py +355 -0
  94. threatzone-1.0.0/tests/test_streaming.py +356 -0
  95. threatzone-1.0.0/tests/test_sync_client.py +1343 -0
  96. threatzone-1.0.0/tests/test_types.py +747 -0
  97. threatzone-1.0.0/uv.lock +708 -0
@@ -0,0 +1,18 @@
1
+ # act defaults for threatzone-python-sdk
2
+ # Use catthehacker's medium image — ships with python, node, uv-compatible tooling.
3
+ # "micro" is too small; "full" is multi-gigabyte. Medium is the sweet spot.
4
+ -P ubuntu-latest=catthehacker/ubuntu:act-latest
5
+ -P ubuntu-22.04=catthehacker/ubuntu:act-22.04
6
+ -P ubuntu-24.04=catthehacker/ubuntu:act-24.04
7
+
8
+ # Reuse containers between runs for caching. Faster repeat runs, mirrors CI re-runs.
9
+ --reuse
10
+
11
+ # Default event context file (realistic push-to-main payload).
12
+ --eventpath .github/act-events/push.json
13
+
14
+ # Default workflow directory (same as github).
15
+ --workflows .github/workflows/
16
+
17
+ # Default branch context so github.ref resolves to main.
18
+ --defaultbranch main
@@ -0,0 +1,21 @@
1
+ {
2
+ "action": "opened",
3
+ "pull_request": {
4
+ "number": 1,
5
+ "state": "open",
6
+ "title": "local act PR",
7
+ "base": {
8
+ "ref": "main",
9
+ "sha": "0000000000000000000000000000000000000000"
10
+ },
11
+ "head": {
12
+ "ref": "local-act-branch",
13
+ "sha": "0000000000000000000000000000000000000001"
14
+ }
15
+ },
16
+ "repository": {
17
+ "name": "threatzone-python-sdk",
18
+ "full_name": "Malwation/threatzone-python-sdk",
19
+ "default_branch": "main"
20
+ }
21
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "ref": "refs/heads/main",
3
+ "before": "0000000000000000000000000000000000000000",
4
+ "after": "0000000000000000000000000000000000000001",
5
+ "repository": {
6
+ "name": "threatzone-python-sdk",
7
+ "full_name": "Malwation/threatzone-python-sdk",
8
+ "default_branch": "main",
9
+ "master_branch": "main"
10
+ },
11
+ "pusher": {
12
+ "name": "local-act",
13
+ "email": "local-act@threatzone.test"
14
+ },
15
+ "commits": [],
16
+ "head_commit": {
17
+ "id": "0000000000000000000000000000000000000001",
18
+ "message": "local act push event",
19
+ "author": {
20
+ "name": "local-act",
21
+ "email": "local-act@threatzone.test"
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "action": "published",
3
+ "ref": "refs/tags/v1.0.0",
4
+ "release": {
5
+ "tag_name": "v1.0.0",
6
+ "name": "local act release v1.0.0",
7
+ "draft": false,
8
+ "prerelease": false
9
+ },
10
+ "repository": {
11
+ "name": "threatzone-python-sdk",
12
+ "full_name": "Malwation/threatzone-python-sdk",
13
+ "default_branch": "main"
14
+ }
15
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "inputs": {
3
+ "dry_run": "true"
4
+ },
5
+ "ref": "refs/heads/main",
6
+ "repository": {
7
+ "name": "threatzone-python-sdk",
8
+ "full_name": "Malwation/threatzone-python-sdk",
9
+ "default_branch": "main"
10
+ }
11
+ }
@@ -0,0 +1,68 @@
1
+ # Local workflow testing with `act`
2
+
3
+ This SDK ships two GitHub Actions workflows:
4
+
5
+ | File | Triggers | Jobs |
6
+ |---|---|---|
7
+ | `ci.yml` | `push` / `pull_request` on `main`,`develop`; `workflow_dispatch` | `lint`, `type-check`, `test` (Python 3.10–3.13 matrix), `build` |
8
+ | `publish.yml` | `release: published`; `workflow_dispatch` (with `dry_run` input) | `test` (matrix), `lint`, `type-check`, `version-check`, `build`, `publish-pypi` |
9
+
10
+ Both workflows are fully runnable **locally** via [`act`](https://github.com/nektos/act) so you can iterate on CI without pushing dummy commits. Everything needed for local runs is committed alongside the workflows:
11
+
12
+ ```
13
+ .actrc # default image, --reuse, --defaultbranch main
14
+ .github/act-events/ # realistic event payloads (push, PR, release, workflow_dispatch)
15
+ scripts/act-ci.sh # wrapper around `act` for ci.yml
16
+ scripts/act-publish.sh # wrapper around `act` for publish.yml (dry-run by default)
17
+ ```
18
+
19
+ ## Prerequisites
20
+
21
+ 1. **Docker** running locally.
22
+ 2. **`act` ≥ 0.2.87** on your PATH:
23
+ ```bash
24
+ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash
25
+ sudo mv ./bin/act /usr/local/bin/act
26
+ act --version
27
+ ```
28
+ 3. First invocation will pull `catthehacker/ubuntu:act-latest` (~1 GB). Subsequent runs reuse it.
29
+
30
+ ## Running CI
31
+
32
+ ```bash
33
+ ./scripts/act-ci.sh # run every CI job (lint + type-check + 4-python matrix + build)
34
+ ./scripts/act-ci.sh lint # ruff check + ruff format --check
35
+ ./scripts/act-ci.sh type-check # mypy
36
+ ./scripts/act-ci.sh test # pytest matrix (Python 3.10 → 3.13), 4 parallel containers
37
+ ./scripts/act-ci.sh build # uv build, wheel import smoke, artifact upload
38
+ ```
39
+
40
+ The `test` job is the slowest — act spins up one container per Python version. Use `./scripts/act-ci.sh test -j test --matrix python-version:3.12` if you only want to exercise a single version.
41
+
42
+ ## Running Publish (dry-run)
43
+
44
+ `publish.yml` gates the real PyPI upload behind `github.event.inputs.dry_run == 'false'` or a `release` event. The wrapper defaults to `workflow_dispatch` with `dry_run=true`, which means:
45
+
46
+ - ✅ `lint`, `type-check`, `test` matrix, `build`, wheel-import smoke ALL run
47
+ - ⏭ `version-check` is skipped (only runs for real `release` events)
48
+ - ⏭ `publish-pypi` is skipped (dry-run gate)
49
+
50
+ ```bash
51
+ ./scripts/act-publish.sh # dry-run: every job except publish-pypi
52
+ ./scripts/act-publish.sh build # dry-run, single job
53
+ ./scripts/act-publish.sh --release # simulate a real release event
54
+ ```
55
+
56
+ The `--release` mode will trigger `version-check` and attempt to reach `publish-pypi` but still fails safely because no real PyPI token is wired into the local act environment.
57
+
58
+ ## Event payloads
59
+
60
+ Every fixture under `.github/act-events/` is a realistic-enough subset of the GitHub webhook payload to let the workflow expressions (`github.event_name`, `github.ref`, `github.event.inputs.dry_run`) resolve correctly. Edit the files if a new workflow needs additional fields.
61
+
62
+ ## Known local-run caveats
63
+
64
+ | Step | Behavior under act | Why |
65
+ |---|---|---|
66
+ | `actions/upload-artifact@v4` | Skipped via `if: ${{ !env.ACT }}` | v4 requires `ACTIONS_RUNTIME_TOKEN` which act cannot provide. The step runs normally on real GitHub. Consequence: `publish-pypi` under `--release` cannot find the artifact locally, but that path also can't reach PyPI without credentials so it's moot. |
67
+ | `codecov/codecov-action@v4` | Runs, upload silently fails | Gated behind `github.event_name == 'push' && github.ref == 'refs/heads/main'` — the push event payload satisfies this but no codecov token is wired in locally. Non-fatal. |
68
+ | `pypa/gh-action-pypi-publish@release/v1` | Never reached | Blocked by the dry-run gate; see above. |
@@ -0,0 +1,155 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main, develop]
6
+ pull_request:
7
+ branches: [main, develop]
8
+ workflow_dispatch:
9
+
10
+ # Cancel in-progress runs on the same ref when a new commit lands
11
+ concurrency:
12
+ group: ci-${{ github.workflow }}-${{ github.ref }}
13
+ cancel-in-progress: true
14
+
15
+ permissions:
16
+ contents: read
17
+
18
+ jobs:
19
+ lint:
20
+ name: Lint + format (ruff)
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+
25
+ - name: Install uv
26
+ uses: astral-sh/setup-uv@v4
27
+ with:
28
+ version: latest
29
+ enable-cache: true
30
+ cache-dependency-glob: "uv.lock"
31
+
32
+ - name: Set up Python
33
+ run: uv python install 3.12
34
+
35
+ - name: Install dependencies
36
+ run: uv sync --all-extras --dev
37
+
38
+ - name: ruff check
39
+ run: uv run ruff check src/ tests/
40
+
41
+ - name: ruff format --check
42
+ run: uv run ruff format --check src/ tests/
43
+
44
+ type-check:
45
+ name: Type check (mypy)
46
+ runs-on: ubuntu-latest
47
+ steps:
48
+ - uses: actions/checkout@v4
49
+
50
+ - name: Install uv
51
+ uses: astral-sh/setup-uv@v4
52
+ with:
53
+ version: latest
54
+ enable-cache: true
55
+ cache-dependency-glob: "uv.lock"
56
+
57
+ - name: Set up Python
58
+ run: uv python install 3.12
59
+
60
+ - name: Install dependencies
61
+ run: uv sync --all-extras --dev
62
+
63
+ - name: mypy
64
+ run: uv run mypy src/threatzone
65
+
66
+ test:
67
+ name: Unit tests (Python ${{ matrix.python-version }})
68
+ runs-on: ubuntu-latest
69
+ strategy:
70
+ fail-fast: false
71
+ matrix:
72
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
73
+ env:
74
+ UV_PYTHON: ${{ matrix.python-version }}
75
+
76
+ steps:
77
+ - uses: actions/checkout@v4
78
+
79
+ - name: Install uv
80
+ uses: astral-sh/setup-uv@v4
81
+ with:
82
+ version: latest
83
+ enable-cache: true
84
+ cache-dependency-glob: "uv.lock"
85
+
86
+ - name: Set up Python ${{ matrix.python-version }}
87
+ run: uv python install ${{ matrix.python-version }}
88
+
89
+ - name: Install dependencies
90
+ run: uv sync --all-extras --dev --python ${{ matrix.python-version }}
91
+
92
+ - name: Run tests
93
+ run: uv run pytest tests/ -v --tb=short
94
+
95
+ - name: Run tests with coverage
96
+ if: matrix.python-version == '3.12'
97
+ run: |
98
+ uv run pytest tests/ \
99
+ --cov=src/threatzone \
100
+ --cov-report=xml \
101
+ --cov-report=term-missing \
102
+ --cov-fail-under=75
103
+
104
+ - name: Upload coverage to Codecov
105
+ if: matrix.python-version == '3.12' && github.event_name == 'push' && github.ref == 'refs/heads/main'
106
+ uses: codecov/codecov-action@v4
107
+ with:
108
+ file: ./coverage.xml
109
+ fail_ci_if_error: false
110
+
111
+ build:
112
+ name: Build distribution
113
+ runs-on: ubuntu-latest
114
+ needs: [lint, type-check, test]
115
+ steps:
116
+ - uses: actions/checkout@v4
117
+
118
+ - name: Install uv
119
+ uses: astral-sh/setup-uv@v4
120
+ with:
121
+ version: latest
122
+ enable-cache: true
123
+ cache-dependency-glob: "uv.lock"
124
+
125
+ - name: Set up Python
126
+ run: uv python install 3.12
127
+
128
+ - name: Install dependencies
129
+ run: uv sync --all-extras --dev
130
+
131
+ - name: Build wheel and sdist
132
+ run: uv build
133
+
134
+ - name: Check distribution contents
135
+ run: |
136
+ ls -la dist/
137
+ uv run python -m zipfile -l dist/*.whl | head -30
138
+
139
+ - name: Verify wheel imports cleanly
140
+ run: |
141
+ uv run --with ./dist/*.whl python -c "
142
+ import threatzone
143
+ print(f'threatzone version: {threatzone.__version__}')
144
+ assert threatzone.__version__, 'version missing'
145
+ "
146
+
147
+ - name: Upload distribution artifacts
148
+ # Skip under nektos/act — upload-artifact@v4 requires ACTIONS_RUNTIME_TOKEN
149
+ # which act cannot provide(learned it after countless errors dear readers...). Runs normally on real GitHub.
150
+ if: ${{ !env.ACT }}
151
+ uses: actions/upload-artifact@v4
152
+ with:
153
+ name: python-package-distributions
154
+ path: dist/
155
+ retention-days: 7
@@ -0,0 +1,199 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ workflow_dispatch:
7
+ inputs:
8
+ dry_run:
9
+ description: "Build only, do not publish"
10
+ required: false
11
+ default: "false"
12
+ type: choice
13
+ options: ["true", "false"]
14
+
15
+ # Only one publish run at a time
16
+ concurrency:
17
+ group: publish-${{ github.workflow }}
18
+ cancel-in-progress: false
19
+
20
+ permissions:
21
+ contents: read
22
+
23
+ jobs:
24
+ # Re-run the full test matrix before publishing. Never release broken code.
25
+ test:
26
+ name: Unit tests (Python ${{ matrix.python-version }})
27
+ runs-on: ubuntu-latest
28
+ strategy:
29
+ fail-fast: true
30
+ matrix:
31
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
32
+ env:
33
+ UV_PYTHON: ${{ matrix.python-version }}
34
+ steps:
35
+ - uses: actions/checkout@v4
36
+
37
+ - name: Install uv
38
+ uses: astral-sh/setup-uv@v4
39
+ with:
40
+ version: latest
41
+ enable-cache: true
42
+ cache-dependency-glob: "uv.lock"
43
+
44
+ - name: Set up Python ${{ matrix.python-version }}
45
+ run: uv python install ${{ matrix.python-version }}
46
+
47
+ - name: Install dependencies
48
+ run: uv sync --all-extras --dev --python ${{ matrix.python-version }}
49
+
50
+ - name: Run tests
51
+ run: uv run pytest tests/ -v --tb=short
52
+
53
+ lint:
54
+ name: Lint + format (ruff)
55
+ runs-on: ubuntu-latest
56
+ steps:
57
+ - uses: actions/checkout@v4
58
+ - name: Install uv
59
+ uses: astral-sh/setup-uv@v4
60
+ with:
61
+ version: latest
62
+ enable-cache: true
63
+ cache-dependency-glob: "uv.lock"
64
+ - name: Set up Python
65
+ run: uv python install 3.12
66
+ - name: Install dependencies
67
+ run: uv sync --all-extras --dev
68
+ - name: ruff check
69
+ run: uv run ruff check src/ tests/
70
+ - name: ruff format --check
71
+ run: uv run ruff format --check src/ tests/
72
+
73
+ type-check:
74
+ name: Type check (mypy)
75
+ runs-on: ubuntu-latest
76
+ steps:
77
+ - uses: actions/checkout@v4
78
+ - name: Install uv
79
+ uses: astral-sh/setup-uv@v4
80
+ with:
81
+ version: latest
82
+ enable-cache: true
83
+ cache-dependency-glob: "uv.lock"
84
+ - name: Set up Python
85
+ run: uv python install 3.12
86
+ - name: Install dependencies
87
+ run: uv sync --all-extras --dev
88
+ - name: mypy
89
+ run: uv run mypy src/threatzone
90
+
91
+ version-check:
92
+ name: Verify tag matches pyproject.toml version
93
+ runs-on: ubuntu-latest
94
+ # Skip version check on manual workflow_dispatch dry runs
95
+ if: github.event_name == 'release'
96
+ steps:
97
+ - uses: actions/checkout@v4
98
+
99
+ - name: Extract tag version
100
+ id: tag
101
+ run: |
102
+ TAG="${GITHUB_REF#refs/tags/}"
103
+ # Strip leading 'v' if present (e.g. v1.0.0 -> 1.0.0)
104
+ VERSION="${TAG#v}"
105
+ echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
106
+ echo "Tag version: ${VERSION}"
107
+
108
+ - name: Extract pyproject version
109
+ id: pyproject
110
+ run: |
111
+ VERSION=$(grep -E '^version = "' pyproject.toml | head -1 | sed -E 's/version = "([^"]+)"/\1/')
112
+ echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
113
+ echo "pyproject version: ${VERSION}"
114
+
115
+ - name: Extract SDK module version
116
+ id: module
117
+ run: |
118
+ VERSION=$(grep -E '^__version__ = "' src/threatzone/__init__.py | sed -E 's/__version__ = "([^"]+)"/\1/')
119
+ echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
120
+ echo "module version: ${VERSION}"
121
+
122
+ - name: Assert all three versions match
123
+ run: |
124
+ TAG="${{ steps.tag.outputs.version }}"
125
+ PYPROJECT="${{ steps.pyproject.outputs.version }}"
126
+ MODULE="${{ steps.module.outputs.version }}"
127
+ if [[ "$TAG" != "$PYPROJECT" || "$TAG" != "$MODULE" ]]; then
128
+ echo "::error::Version mismatch: tag=$TAG pyproject=$PYPROJECT module=$MODULE"
129
+ exit 1
130
+ fi
131
+ echo "All three versions match: $TAG"
132
+
133
+ build:
134
+ name: Build distribution
135
+ runs-on: ubuntu-latest
136
+ needs: [test, lint, type-check, version-check]
137
+ # If version-check was skipped (workflow_dispatch), don't require it
138
+ if: always() && needs.test.result == 'success' && needs.lint.result == 'success' && needs.type-check.result == 'success' && (needs.version-check.result == 'success' || needs.version-check.result == 'skipped')
139
+ steps:
140
+ - uses: actions/checkout@v4
141
+
142
+ - name: Install uv
143
+ uses: astral-sh/setup-uv@v4
144
+ with:
145
+ version: latest
146
+ enable-cache: true
147
+ cache-dependency-glob: "uv.lock"
148
+
149
+ - name: Set up Python
150
+ run: uv python install 3.12
151
+
152
+ - name: Install dependencies
153
+ run: uv sync --all-extras --dev
154
+
155
+ - name: Build wheel and sdist
156
+ run: uv build
157
+
158
+ - name: Verify wheel imports cleanly
159
+ run: |
160
+ uv run --with ./dist/*.whl python -c "
161
+ import threatzone
162
+ print(f'Built threatzone version: {threatzone.__version__}')
163
+ assert threatzone.__version__, 'version missing'
164
+ "
165
+
166
+ - name: Store distribution packages
167
+ # Skip under nektos/act — upload-artifact@v4 requires ACTIONS_RUNTIME_TOKEN
168
+ # which act cannot provide. Runs normally on real GitHub.
169
+ if: ${{ !env.ACT }}
170
+ uses: actions/upload-artifact@v4
171
+ with:
172
+ name: python-package-distributions
173
+ path: dist/
174
+ retention-days: 30
175
+
176
+ publish-pypi:
177
+ name: Publish to PyPI
178
+ needs: [build]
179
+ # Publish only on real releases, not dry runs
180
+ if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'false')
181
+ runs-on: ubuntu-latest
182
+ environment:
183
+ name: pypi
184
+ url: https://pypi.org/project/threatzone/
185
+ permissions:
186
+ id-token: write # Trusted publishing (OIDC)
187
+
188
+ steps:
189
+ - name: Download distributions
190
+ uses: actions/download-artifact@v4
191
+ with:
192
+ name: python-package-distributions
193
+ path: dist/
194
+
195
+ - name: Publish to PyPI
196
+ uses: pypa/gh-action-pypi-publish@release/v1
197
+ with:
198
+ skip-existing: false
199
+ verbose: true
@@ -0,0 +1,82 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ *.egg-info/
24
+ .installed.cfg
25
+ *.egg
26
+
27
+ # PyInstaller
28
+ *.manifest
29
+ *.spec
30
+
31
+ # Installer logs
32
+ pip-log.txt
33
+ pip-delete-this-directory.txt
34
+
35
+ # Unit test / coverage reports
36
+ htmlcov/
37
+ .tox/
38
+ .nox/
39
+ .coverage
40
+ .coverage.*
41
+ .cache
42
+ nosetests.xml
43
+ coverage.xml
44
+ *.cover
45
+ *.py,cover
46
+ .hypothesis/
47
+ .pytest_cache/
48
+
49
+ # Translations
50
+ *.mo
51
+ *.pot
52
+
53
+ # Environments
54
+ .env
55
+ .venv
56
+ env/
57
+ venv/
58
+ ENV/
59
+ env.bak/
60
+ venv.bak/
61
+
62
+ # IDEs
63
+ .idea/
64
+ .vscode/
65
+ *.swp
66
+ *.swo
67
+ *~
68
+
69
+ # mypy
70
+ .mypy_cache/
71
+ .dmypy.json
72
+ dmypy.json
73
+
74
+ # ruff
75
+ .ruff_cache/
76
+
77
+ # OS
78
+ .DS_Store
79
+ Thumbs.db
80
+
81
+ # Project specific
82
+ PLAN.md
@@ -0,0 +1 @@
1
+ 3.10
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Malwation
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.