safeuploads 0.1.2__tar.gz → 1.0.1__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.
- safeuploads-1.0.1/.forgejo/scripts/check_conventional_commits.py +163 -0
- safeuploads-1.0.1/.forgejo/workflows/conventional-commits.yml +44 -0
- safeuploads-1.0.1/.forgejo/workflows/docs.yml +51 -0
- safeuploads-1.0.1/.forgejo/workflows/fuzz.yml +44 -0
- safeuploads-1.0.1/.forgejo/workflows/lint-and-test.yml +86 -0
- safeuploads-1.0.1/.forgejo/workflows/publish.yml +63 -0
- safeuploads-1.0.1/.forgejo/workflows/renovate.yml +37 -0
- safeuploads-1.0.1/.github/agents/se-gitops-ci-specialist.agent.md +244 -0
- safeuploads-1.0.1/.github/agents/se-security-reviewer.agent.md +161 -0
- safeuploads-1.0.1/.github/agents/test-implementor.agent.md +168 -0
- safeuploads-1.0.1/.github/agents/test-reviewer.agent.md +113 -0
- safeuploads-1.0.1/.github/copilot-instructions.md +93 -0
- safeuploads-1.0.1/.github/instructions/agents.instructions.md +991 -0
- safeuploads-1.0.1/.github/instructions/github-actions-ci-cd-best-practices.instructions.md +607 -0
- safeuploads-1.0.1/.github/instructions/instructions.instructions.md +256 -0
- safeuploads-1.0.1/.github/instructions/prompt.instructions.md +88 -0
- safeuploads-1.0.1/.github/instructions/python.instructions.md +140 -0
- safeuploads-1.0.1/.github/instructions/security-and-owasp.instructions.md +51 -0
- safeuploads-1.0.1/.github/workflows/fuzz.yml +51 -0
- safeuploads-1.0.1/.github/workflows/lint&test.yml +96 -0
- safeuploads-1.0.1/.github/workflows/publish.yml +67 -0
- safeuploads-1.0.1/.gitignore +23 -0
- safeuploads-1.0.1/CODE_OF_CONDUCT.md +128 -0
- safeuploads-1.0.1/CONTRIBUTING.md +82 -0
- safeuploads-1.0.1/PKG-INFO +149 -0
- safeuploads-1.0.1/README.md +135 -0
- safeuploads-1.0.1/SECURITY.md +17 -0
- safeuploads-1.0.1/docs/api.md +6 -0
- safeuploads-1.0.1/docs/index.md +143 -0
- safeuploads-1.0.1/docs/rate-limiting.md +285 -0
- safeuploads-1.0.1/docs/security/architecture.md +370 -0
- safeuploads-1.0.1/docs/security/integration-checklist.md +143 -0
- safeuploads-1.0.1/docs/security/threat-model.md +315 -0
- safeuploads-1.0.1/examples/README.md +279 -0
- safeuploads-1.0.1/examples/fastapi_example.py +342 -0
- safeuploads-1.0.1/mkdocs.yml +34 -0
- safeuploads-1.0.1/pyproject.toml +117 -0
- safeuploads-1.0.1/renovate.json +35 -0
- {safeuploads-0.1.2 → safeuploads-1.0.1}/safeuploads/__init__.py +58 -24
- safeuploads-1.0.1/safeuploads/audit.py +296 -0
- {safeuploads-0.1.2 → safeuploads-1.0.1}/safeuploads/config.py +332 -115
- {safeuploads-0.1.2 → safeuploads-1.0.1}/safeuploads/enums.py +73 -1
- {safeuploads-0.1.2 → safeuploads-1.0.1}/safeuploads/exceptions.py +56 -5
- safeuploads-1.0.1/safeuploads/file_validator.py +1028 -0
- {safeuploads-0.1.2 → safeuploads-1.0.1}/safeuploads/inspectors/__init__.py +7 -1
- safeuploads-1.0.1/safeuploads/inspectors/content_inspector.py +237 -0
- safeuploads-1.0.1/safeuploads/inspectors/gzip_inspector.py +189 -0
- safeuploads-1.0.1/safeuploads/inspectors/zip_inspector.py +670 -0
- safeuploads-1.0.1/safeuploads/protocols.py +101 -0
- safeuploads-1.0.1/safeuploads/utils.py +176 -0
- {safeuploads-0.1.2 → safeuploads-1.0.1}/safeuploads/validators/__init__.py +4 -2
- {safeuploads-0.1.2 → safeuploads-1.0.1}/safeuploads/validators/base.py +1 -4
- safeuploads-1.0.1/safeuploads/validators/compression_validator.py +433 -0
- {safeuploads-0.1.2 → safeuploads-1.0.1}/safeuploads/validators/extension_validator.py +24 -8
- {safeuploads-0.1.2 → safeuploads-1.0.1}/safeuploads/validators/unicode_validator.py +38 -15
- {safeuploads-0.1.2 → safeuploads-1.0.1}/safeuploads/validators/windows_validator.py +17 -6
- safeuploads-1.0.1/safeuploads/validators/xml_validator.py +95 -0
- safeuploads-1.0.1/tests/__init__.py +1 -0
- safeuploads-1.0.1/tests/conftest.py +188 -0
- safeuploads-1.0.1/tests/fuzz/__init__.py +1 -0
- safeuploads-1.0.1/tests/fuzz/test_fuzz_config.py +94 -0
- safeuploads-1.0.1/tests/fuzz/test_fuzz_filenames.py +108 -0
- safeuploads-1.0.1/tests/fuzz/test_fuzz_images.py +115 -0
- safeuploads-1.0.1/tests/fuzz/test_fuzz_zip.py +116 -0
- safeuploads-1.0.1/tests/inspectors/__init__.py +1 -0
- safeuploads-1.0.1/tests/inspectors/test_content_inspector.py +204 -0
- safeuploads-1.0.1/tests/inspectors/test_gzip_inspector.py +255 -0
- safeuploads-1.0.1/tests/inspectors/test_zip_inspector.py +1191 -0
- safeuploads-1.0.1/tests/test_audit.py +472 -0
- safeuploads-1.0.1/tests/test_config.py +321 -0
- safeuploads-1.0.1/tests/test_config_validation.py +899 -0
- safeuploads-1.0.1/tests/test_enums.py +474 -0
- safeuploads-1.0.1/tests/test_exceptions.py +522 -0
- safeuploads-1.0.1/tests/test_file_validator.py +1291 -0
- safeuploads-1.0.1/tests/test_performance.py +552 -0
- safeuploads-1.0.1/tests/test_utils.py +214 -0
- safeuploads-1.0.1/tests/test_validate_activity_file.py +303 -0
- safeuploads-1.0.1/tests/test_validate_gzip_file.py +238 -0
- safeuploads-1.0.1/tests/validators/__init__.py +1 -0
- safeuploads-1.0.1/tests/validators/test_compression_validator.py +773 -0
- safeuploads-1.0.1/tests/validators/test_extension_validator.py +207 -0
- safeuploads-1.0.1/tests/validators/test_unicode_validator.py +209 -0
- safeuploads-1.0.1/tests/validators/test_windows_validator.py +225 -0
- safeuploads-1.0.1/tests/validators/test_xml_validator.py +175 -0
- safeuploads-1.0.1/uv.lock +422 -0
- safeuploads-0.1.2/PKG-INFO +0 -170
- safeuploads-0.1.2/README.md +0 -147
- safeuploads-0.1.2/pyproject.toml +0 -65
- safeuploads-0.1.2/safeuploads/file_validator.py +0 -525
- safeuploads-0.1.2/safeuploads/inspectors/zip_inspector.py +0 -419
- safeuploads-0.1.2/safeuploads/protocols.py +0 -51
- safeuploads-0.1.2/safeuploads/validators/compression_validator.py +0 -286
- {safeuploads-0.1.2 → safeuploads-1.0.1}/LICENSE.md +0 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Validate that messages follow the Conventional Commits 1.0.0 specification.
|
|
3
|
+
|
|
4
|
+
Reads one or more messages (either from CLI arguments or, when ``--stdin`` is
|
|
5
|
+
passed, one message per line from standard input) and exits non-zero if any of
|
|
6
|
+
them is invalid.
|
|
7
|
+
|
|
8
|
+
Spec reference: https://www.conventionalcommits.org/en/v1.0.0/
|
|
9
|
+
|
|
10
|
+
Usage::
|
|
11
|
+
|
|
12
|
+
check_conventional_commits.py "feat(api): add endpoint"
|
|
13
|
+
git log --format=%s base..head | check_conventional_commits.py --stdin
|
|
14
|
+
|
|
15
|
+
Designed to run with the Python interpreter already available on the Forgejo
|
|
16
|
+
runner image. Uses only the standard library so there is no third-party
|
|
17
|
+
supply-chain surface.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import argparse
|
|
23
|
+
import re
|
|
24
|
+
import sys
|
|
25
|
+
from collections.abc import Iterable
|
|
26
|
+
|
|
27
|
+
# Allowed types. This whitelist is a *project policy* layered on top of the
|
|
28
|
+
# Conventional Commits 1.0.0 spec (the spec recommends but does not mandate a
|
|
29
|
+
# fixed set of types). Keep this list in sync with the project's commit
|
|
30
|
+
# conventions documented in ``.github/copilot-instructions.md``.
|
|
31
|
+
#
|
|
32
|
+
# Per the spec, types are matched case-insensitively.
|
|
33
|
+
ALLOWED_TYPES: tuple[str, ...] = (
|
|
34
|
+
"build",
|
|
35
|
+
"chore",
|
|
36
|
+
"ci",
|
|
37
|
+
"docs",
|
|
38
|
+
"feat",
|
|
39
|
+
"fix",
|
|
40
|
+
"perf",
|
|
41
|
+
"refactor",
|
|
42
|
+
"revert",
|
|
43
|
+
"style",
|
|
44
|
+
"test",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Subject pattern: ``type(scope)!: description``. Scope and ``!`` are optional.
|
|
48
|
+
# Per CC 1.0.0:
|
|
49
|
+
# - the type is a noun (matched case-insensitively against ALLOWED_TYPES);
|
|
50
|
+
# - the scope is any noun describing a section of the codebase, enclosed in
|
|
51
|
+
# parentheses, so we accept any non-empty string that is not a closing
|
|
52
|
+
# parenthesis;
|
|
53
|
+
# - the description is free-form text.
|
|
54
|
+
_HEADER_RE = re.compile(
|
|
55
|
+
r"""
|
|
56
|
+
^
|
|
57
|
+
(?P<type>[A-Za-z]+)
|
|
58
|
+
(?:\((?P<scope>[^)]+)\))?
|
|
59
|
+
(?P<breaking>!)?
|
|
60
|
+
:\
|
|
61
|
+
(?P<description>.+?)
|
|
62
|
+
\s*$
|
|
63
|
+
""",
|
|
64
|
+
re.VERBOSE,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _validate_message(message: str) -> list[str]:
|
|
69
|
+
"""Return a list of validation errors for ``message`` (empty if valid)."""
|
|
70
|
+
errors: list[str] = []
|
|
71
|
+
|
|
72
|
+
# Conventional Commits only constrains the first line (the header).
|
|
73
|
+
header = message.splitlines()[0] if message else ""
|
|
74
|
+
|
|
75
|
+
if not header.strip():
|
|
76
|
+
return ["message is empty"]
|
|
77
|
+
|
|
78
|
+
match = _HEADER_RE.match(header)
|
|
79
|
+
if match is None:
|
|
80
|
+
errors.append(
|
|
81
|
+
"header does not match '<type>(<scope>)!: <description>' "
|
|
82
|
+
"(see https://www.conventionalcommits.org/en/v1.0.0/)"
|
|
83
|
+
)
|
|
84
|
+
return errors
|
|
85
|
+
|
|
86
|
+
commit_type = match.group("type")
|
|
87
|
+
|
|
88
|
+
# Project policy: restrict to a known set of types. Types are matched
|
|
89
|
+
# case-insensitively per the Conventional Commits spec.
|
|
90
|
+
if commit_type.lower() not in ALLOWED_TYPES:
|
|
91
|
+
errors.append(
|
|
92
|
+
f"type '{commit_type}' is not allowed; "
|
|
93
|
+
f"use one of: {', '.join(ALLOWED_TYPES)}"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
return errors
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _safe_log_value(value: str) -> str:
|
|
100
|
+
"""Return ``value`` escaped for safe CI log output."""
|
|
101
|
+
return value.encode("unicode_escape").decode("ascii")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _iter_messages(args: argparse.Namespace) -> Iterable[str]:
|
|
105
|
+
if args.stdin:
|
|
106
|
+
for line in sys.stdin:
|
|
107
|
+
stripped = line.rstrip("\n")
|
|
108
|
+
if stripped:
|
|
109
|
+
yield stripped
|
|
110
|
+
else:
|
|
111
|
+
yield from args.messages
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def main(argv: list[str] | None = None) -> int:
|
|
115
|
+
parser = argparse.ArgumentParser(
|
|
116
|
+
description="Validate Conventional Commits headers.",
|
|
117
|
+
)
|
|
118
|
+
parser.add_argument(
|
|
119
|
+
"messages",
|
|
120
|
+
nargs="*",
|
|
121
|
+
help="One or more commit messages (or PR titles) to validate.",
|
|
122
|
+
)
|
|
123
|
+
parser.add_argument(
|
|
124
|
+
"--stdin",
|
|
125
|
+
action="store_true",
|
|
126
|
+
help="Read messages from standard input, one per line.",
|
|
127
|
+
)
|
|
128
|
+
args = parser.parse_args(argv)
|
|
129
|
+
|
|
130
|
+
if not args.stdin and not args.messages:
|
|
131
|
+
parser.error("provide at least one message or use --stdin")
|
|
132
|
+
|
|
133
|
+
failures = 0
|
|
134
|
+
checked = 0
|
|
135
|
+
for message in _iter_messages(args):
|
|
136
|
+
checked += 1
|
|
137
|
+
errors = _validate_message(message)
|
|
138
|
+
safe_message = _safe_log_value(message)
|
|
139
|
+
if errors:
|
|
140
|
+
failures += 1
|
|
141
|
+
print(f"INVALID: {safe_message}", file=sys.stderr)
|
|
142
|
+
for err in errors:
|
|
143
|
+
print(f" - {err}", file=sys.stderr)
|
|
144
|
+
else:
|
|
145
|
+
print(f"OK: {safe_message}")
|
|
146
|
+
|
|
147
|
+
if checked == 0:
|
|
148
|
+
print("No messages to validate.", file=sys.stderr)
|
|
149
|
+
return 1
|
|
150
|
+
|
|
151
|
+
if failures:
|
|
152
|
+
print(
|
|
153
|
+
f"\n{failures} of {checked} message(s) failed Conventional Commits "
|
|
154
|
+
"validation.",
|
|
155
|
+
file=sys.stderr,
|
|
156
|
+
)
|
|
157
|
+
return 1
|
|
158
|
+
|
|
159
|
+
return 0
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
if __name__ == "__main__":
|
|
163
|
+
sys.exit(main())
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: Conventional Commits
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [opened, edited, synchronize, reopened]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
pull-requests: read
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: ${{ forgejo.workflow }}-${{ forgejo.event.pull_request.number }}
|
|
13
|
+
cancel-in-progress: true
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
validate:
|
|
17
|
+
runs-on: codeberg-small
|
|
18
|
+
|
|
19
|
+
steps:
|
|
20
|
+
- name: Checkout code
|
|
21
|
+
uses: https://data.forgejo.org/actions/checkout@v6
|
|
22
|
+
with:
|
|
23
|
+
fetch-depth: 0
|
|
24
|
+
|
|
25
|
+
- name: Validate PR title
|
|
26
|
+
# The PR title is passed through an environment variable to prevent
|
|
27
|
+
# shell injection via crafted titles (see security-and-owasp guidelines).
|
|
28
|
+
env:
|
|
29
|
+
PR_TITLE: ${{ forgejo.event.pull_request.title }}
|
|
30
|
+
run: |
|
|
31
|
+
python3 .forgejo/scripts/check_conventional_commits.py "$PR_TITLE"
|
|
32
|
+
|
|
33
|
+
- name: Validate commits in PR
|
|
34
|
+
# Validates every commit subject between the PR base and head. Use the
|
|
35
|
+
# base/head SHAs from the event payload so we don't depend on the local
|
|
36
|
+
# checkout ref layout.
|
|
37
|
+
env:
|
|
38
|
+
BASE_SHA: ${{ forgejo.event.pull_request.base.sha }}
|
|
39
|
+
HEAD_SHA: ${{ forgejo.event.pull_request.head.sha }}
|
|
40
|
+
run: |
|
|
41
|
+
set -euo pipefail
|
|
42
|
+
git fetch --no-tags origin "$BASE_SHA" "$HEAD_SHA"
|
|
43
|
+
git log --format=%s "$BASE_SHA".."$HEAD_SHA" \
|
|
44
|
+
| python3 .forgejo/scripts/check_conventional_commits.py --stdin
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
name: Deploy documentation
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
push:
|
|
6
|
+
branches:
|
|
7
|
+
- main
|
|
8
|
+
- master
|
|
9
|
+
paths:
|
|
10
|
+
- 'docs/**'
|
|
11
|
+
- 'mkdocs.yml'
|
|
12
|
+
- 'safeuploads/**/*.py'
|
|
13
|
+
- '.forgejo/workflows/docs.yml'
|
|
14
|
+
|
|
15
|
+
permissions:
|
|
16
|
+
contents: write
|
|
17
|
+
|
|
18
|
+
concurrency:
|
|
19
|
+
group: ${{ forgejo.workflow }}-${{ forgejo.ref }}
|
|
20
|
+
cancel-in-progress: true
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
|
|
24
|
+
deploy:
|
|
25
|
+
|
|
26
|
+
runs-on: codeberg-small
|
|
27
|
+
|
|
28
|
+
steps:
|
|
29
|
+
- name: Checkout code
|
|
30
|
+
uses: https://data.forgejo.org/actions/checkout@v4
|
|
31
|
+
with:
|
|
32
|
+
fetch-depth: 0
|
|
33
|
+
|
|
34
|
+
- name: Set up Python
|
|
35
|
+
uses: https://data.forgejo.org/actions/setup-python@v5
|
|
36
|
+
with:
|
|
37
|
+
python-version: '3.13'
|
|
38
|
+
cache: pip
|
|
39
|
+
|
|
40
|
+
- name: Install documentation dependencies
|
|
41
|
+
run: pip install mkdocs-material mkdocstrings[python]
|
|
42
|
+
|
|
43
|
+
- name: Build documentation
|
|
44
|
+
run: mkdocs build --site-dir site
|
|
45
|
+
|
|
46
|
+
- name: Deploy documentation to Codeberg Pages
|
|
47
|
+
uses: https://codeberg.org/git-pages/action@v2
|
|
48
|
+
with:
|
|
49
|
+
site: https://${{ forge.repository_owner }}.codeberg.page/${{ forge.event.repository.name }}/
|
|
50
|
+
token: ${{ forge.token }}
|
|
51
|
+
source: site/
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: Scheduled Fuzz
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
schedule:
|
|
5
|
+
# Every Sunday at 03:00 UTC
|
|
6
|
+
- cron: "0 3 * * 0"
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
# Cancel in-progress runs for the same branch to avoid wasted
|
|
10
|
+
# runner minutes.
|
|
11
|
+
concurrency:
|
|
12
|
+
group: ${{ forge.workflow }}-${{ forge.ref }}
|
|
13
|
+
cancel-in-progress: true
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
fuzz:
|
|
17
|
+
name: Fuzz
|
|
18
|
+
runs-on: codeberg-medium-lazy
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- name: Checkout repository
|
|
22
|
+
uses: https://data.forgejo.org/actions/checkout@v4
|
|
23
|
+
|
|
24
|
+
- name: Install system dependencies
|
|
25
|
+
run: sudo apt-get update && sudo apt-get install -y libmagic1
|
|
26
|
+
|
|
27
|
+
- name: Set up Python 3.13
|
|
28
|
+
uses: https://data.forgejo.org/actions/setup-python@v5
|
|
29
|
+
with:
|
|
30
|
+
python-version: "3.13"
|
|
31
|
+
|
|
32
|
+
- name: Install uv
|
|
33
|
+
run: pip install uv
|
|
34
|
+
|
|
35
|
+
- name: Install dependencies
|
|
36
|
+
run: uv sync --frozen --dev
|
|
37
|
+
|
|
38
|
+
- name: Run fuzz tests
|
|
39
|
+
run: >
|
|
40
|
+
uv run pytest -m fuzz
|
|
41
|
+
--hypothesis-seed=0
|
|
42
|
+
-x
|
|
43
|
+
--tb=short
|
|
44
|
+
-q
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
name: Lint & Test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
# Cancel in-progress runs for the same branch/PR to avoid wasted
|
|
10
|
+
# runner minutes and race conditions on shared resources.
|
|
11
|
+
concurrency:
|
|
12
|
+
group: ${{ forge.workflow }}-${{ forge.ref }}
|
|
13
|
+
cancel-in-progress: true
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
# ----------------------------------------------------------------
|
|
17
|
+
# Lint
|
|
18
|
+
# Runs ruff on all Python sources. Fails fast so test job is only
|
|
19
|
+
# triggered when the code is clean.
|
|
20
|
+
# ----------------------------------------------------------------
|
|
21
|
+
lint:
|
|
22
|
+
name: Lint
|
|
23
|
+
runs-on: codeberg-tiny
|
|
24
|
+
|
|
25
|
+
steps:
|
|
26
|
+
- name: Checkout repository
|
|
27
|
+
uses: https://data.forgejo.org/actions/checkout@v4
|
|
28
|
+
|
|
29
|
+
- name: Set up Python 3.13
|
|
30
|
+
uses: https://data.forgejo.org/actions/setup-python@v5
|
|
31
|
+
with:
|
|
32
|
+
python-version: "3.13"
|
|
33
|
+
|
|
34
|
+
- name: Install uv
|
|
35
|
+
run: pip install uv
|
|
36
|
+
|
|
37
|
+
- name: Install dependencies
|
|
38
|
+
run: uv sync --frozen --dev
|
|
39
|
+
|
|
40
|
+
- name: Lint with ruff
|
|
41
|
+
run: uv run ruff check safeuploads tests examples
|
|
42
|
+
|
|
43
|
+
- name: Check formatting with ruff
|
|
44
|
+
run: uv run ruff format --check safeuploads tests examples
|
|
45
|
+
|
|
46
|
+
# ----------------------------------------------------------------
|
|
47
|
+
# Test
|
|
48
|
+
# Runs the full pytest suite with coverage. Only runs after lint
|
|
49
|
+
# passes so we don't waste runner minutes on broken code.
|
|
50
|
+
# ----------------------------------------------------------------
|
|
51
|
+
test:
|
|
52
|
+
name: Test
|
|
53
|
+
runs-on: codeberg-medium
|
|
54
|
+
needs: lint
|
|
55
|
+
|
|
56
|
+
steps:
|
|
57
|
+
- name: Checkout repository
|
|
58
|
+
uses: https://data.forgejo.org/actions/checkout@v4
|
|
59
|
+
|
|
60
|
+
- name: Install system dependencies
|
|
61
|
+
run: sudo apt-get update && sudo apt-get install -y libmagic1
|
|
62
|
+
|
|
63
|
+
- name: Set up Python 3.13
|
|
64
|
+
uses: https://data.forgejo.org/actions/setup-python@v5
|
|
65
|
+
with:
|
|
66
|
+
python-version: "3.13"
|
|
67
|
+
|
|
68
|
+
- name: Install uv
|
|
69
|
+
run: pip install uv
|
|
70
|
+
|
|
71
|
+
- name: Install dependencies
|
|
72
|
+
run: uv sync --frozen --dev
|
|
73
|
+
|
|
74
|
+
- name: Audit Python dependencies
|
|
75
|
+
run: uvx pip-audit --require-hashes --disable-pip -r <(uv export --frozen --no-header --no-emit-project --no-dev)
|
|
76
|
+
|
|
77
|
+
- name: Run tests with coverage
|
|
78
|
+
run: uv run pytest --cov-fail-under=90
|
|
79
|
+
|
|
80
|
+
- name: Upload coverage report
|
|
81
|
+
uses: https://data.forgejo.org/forgejo/upload-artifact@v4
|
|
82
|
+
if: always()
|
|
83
|
+
with:
|
|
84
|
+
name: safeuploads-coverage
|
|
85
|
+
path: .coverage
|
|
86
|
+
retention-days: 7
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
confirm:
|
|
7
|
+
description: "Type the package version to confirm publish"
|
|
8
|
+
required: true
|
|
9
|
+
type: string
|
|
10
|
+
|
|
11
|
+
# Only one publish at a time.
|
|
12
|
+
concurrency:
|
|
13
|
+
group: publish
|
|
14
|
+
cancel-in-progress: false
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
publish:
|
|
18
|
+
name: Build & Publish
|
|
19
|
+
runs-on: codeberg-medium
|
|
20
|
+
|
|
21
|
+
steps:
|
|
22
|
+
- name: Checkout repository
|
|
23
|
+
uses: https://data.forgejo.org/actions/checkout@v4
|
|
24
|
+
|
|
25
|
+
- name: Verify version matches input
|
|
26
|
+
run: |
|
|
27
|
+
PKG_VERSION=$(python3 -c "
|
|
28
|
+
import tomllib, pathlib
|
|
29
|
+
data = tomllib.loads(
|
|
30
|
+
pathlib.Path('pyproject.toml').read_text()
|
|
31
|
+
)
|
|
32
|
+
print(data['project']['version'])
|
|
33
|
+
")
|
|
34
|
+
if [ "$PKG_VERSION" != "${{ inputs.confirm }}" ]; then
|
|
35
|
+
echo "::error::Input version ${{ inputs.confirm }} does not match pyproject.toml version $PKG_VERSION"
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
echo "Publishing version $PKG_VERSION"
|
|
39
|
+
|
|
40
|
+
- name: Install system dependencies
|
|
41
|
+
run: sudo apt-get update && sudo apt-get install -y libmagic1
|
|
42
|
+
|
|
43
|
+
- name: Set up Python 3.13
|
|
44
|
+
uses: https://data.forgejo.org/actions/setup-python@v5
|
|
45
|
+
with:
|
|
46
|
+
python-version: "3.13"
|
|
47
|
+
|
|
48
|
+
- name: Install uv
|
|
49
|
+
run: pip install uv
|
|
50
|
+
|
|
51
|
+
- name: Install dependencies
|
|
52
|
+
run: uv sync --frozen --dev
|
|
53
|
+
|
|
54
|
+
- name: Run tests before publish
|
|
55
|
+
run: uv run pytest --cov-fail-under=90
|
|
56
|
+
|
|
57
|
+
- name: Build package
|
|
58
|
+
run: uv build
|
|
59
|
+
|
|
60
|
+
- name: Publish to PyPI
|
|
61
|
+
env:
|
|
62
|
+
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
|
63
|
+
run: uv publish
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: Dependency updates
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
schedule:
|
|
6
|
+
- cron: '0 4 * * 0'
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: write
|
|
10
|
+
pull-requests: write
|
|
11
|
+
issues: write
|
|
12
|
+
|
|
13
|
+
concurrency:
|
|
14
|
+
group: ${{ forgejo.workflow }}-${{ forgejo.ref }}
|
|
15
|
+
cancel-in-progress: false
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
|
|
19
|
+
renovate:
|
|
20
|
+
|
|
21
|
+
runs-on: codeberg-small
|
|
22
|
+
|
|
23
|
+
steps:
|
|
24
|
+
- name: Checkout code
|
|
25
|
+
uses: https://data.forgejo.org/actions/checkout@v4
|
|
26
|
+
with:
|
|
27
|
+
fetch-depth: 1
|
|
28
|
+
|
|
29
|
+
- name: Run Renovate
|
|
30
|
+
uses: docker://ghcr.io/renovatebot/renovate:latest
|
|
31
|
+
env:
|
|
32
|
+
LOG_LEVEL: info
|
|
33
|
+
RENOVATE_CONFIG_FILE: renovate.json
|
|
34
|
+
RENOVATE_ENDPOINT: https://codeberg.org/api/v1/
|
|
35
|
+
RENOVATE_PLATFORM: gitea
|
|
36
|
+
RENOVATE_REPOSITORIES: ${{ forgejo.repository }}
|
|
37
|
+
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
|