safeuploads 1.0.0__tar.gz → 1.1.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 (99) hide show
  1. safeuploads-1.1.0/.forgejo/ISSUE_TEMPLATE/bug_report.md +35 -0
  2. safeuploads-1.1.0/.forgejo/ISSUE_TEMPLATE/config.yml +5 -0
  3. safeuploads-1.1.0/.forgejo/ISSUE_TEMPLATE/feature_request.md +27 -0
  4. safeuploads-1.1.0/.forgejo/PULL_REQUEST_TEMPLATE.md +33 -0
  5. safeuploads-1.1.0/.forgejo/scripts/check_conventional_commits.py +165 -0
  6. safeuploads-1.1.0/.forgejo/workflows/conventional-commits.yml +40 -0
  7. safeuploads-1.1.0/.forgejo/workflows/docs.yml +59 -0
  8. {safeuploads-1.0.0/.github → safeuploads-1.1.0/.forgejo}/workflows/fuzz.yml +6 -16
  9. safeuploads-1.0.0/.github/workflows/lint&test.yml → safeuploads-1.1.0/.forgejo/workflows/lint-and-test.yml +19 -30
  10. {safeuploads-1.0.0/.github → safeuploads-1.1.0/.forgejo}/workflows/publish.yml +11 -18
  11. safeuploads-1.1.0/.forgejo/workflows/renovate.yml +32 -0
  12. {safeuploads-1.0.0 → safeuploads-1.1.0}/.github/copilot-instructions.md +21 -0
  13. safeuploads-1.1.0/CHANGELOG.md +147 -0
  14. safeuploads-1.1.0/CONTRIBUTING.md +82 -0
  15. {safeuploads-1.0.0 → safeuploads-1.1.0}/PKG-INFO +24 -14
  16. {safeuploads-1.0.0 → safeuploads-1.1.0}/README.md +23 -13
  17. {safeuploads-1.0.0 → safeuploads-1.1.0}/docs/index.md +23 -14
  18. {safeuploads-1.0.0 → safeuploads-1.1.0}/docs/rate-limiting.md +1 -1
  19. {safeuploads-1.0.0 → safeuploads-1.1.0}/docs/security/architecture.md +11 -11
  20. {safeuploads-1.0.0 → safeuploads-1.1.0}/docs/security/threat-model.md +21 -2
  21. {safeuploads-1.0.0 → safeuploads-1.1.0}/examples/README.md +13 -5
  22. {safeuploads-1.0.0 → safeuploads-1.1.0}/examples/fastapi_example.py +125 -3
  23. {safeuploads-1.0.0 → safeuploads-1.1.0}/mkdocs.yml +1 -1
  24. {safeuploads-1.0.0 → safeuploads-1.1.0}/pyproject.toml +51 -1
  25. safeuploads-1.1.0/renovate.json +35 -0
  26. {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/__init__.py +6 -3
  27. {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/audit.py +4 -3
  28. {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/config.py +467 -310
  29. {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/enums.py +28 -0
  30. {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/exceptions.py +29 -13
  31. safeuploads-1.1.0/safeuploads/file_validator.py +1212 -0
  32. {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/inspectors/__init__.py +4 -2
  33. safeuploads-1.1.0/safeuploads/inspectors/base.py +34 -0
  34. {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/inspectors/content_inspector.py +40 -63
  35. {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/inspectors/gzip_inspector.py +16 -20
  36. {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/inspectors/zip_inspector.py +44 -69
  37. safeuploads-1.1.0/safeuploads/py.typed +0 -0
  38. safeuploads-1.1.0/safeuploads/utils.py +311 -0
  39. {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/validators/base.py +6 -1
  40. {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/validators/compression_validator.py +67 -28
  41. {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/validators/extension_validator.py +3 -0
  42. {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/validators/unicode_validator.py +5 -1
  43. {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/validators/windows_validator.py +3 -0
  44. {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/validators/xml_validator.py +10 -3
  45. {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/inspectors/test_zip_inspector.py +36 -0
  46. {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/test_config.py +45 -0
  47. {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/test_config_validation.py +63 -0
  48. {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/test_enums.py +20 -0
  49. safeuploads-1.1.0/tests/test_fastapi_integration.py +164 -0
  50. {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/test_file_validator.py +241 -0
  51. {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/test_utils.py +37 -4
  52. {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/validators/test_compression_validator.py +89 -0
  53. {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/validators/test_unicode_validator.py +5 -1
  54. safeuploads-1.1.0/uv.lock +1110 -0
  55. safeuploads-1.0.0/.github/CODEOWNERS +0 -15
  56. safeuploads-1.0.0/.github/FUNDING.yml +0 -13
  57. safeuploads-1.0.0/.github/dependabot.yml +0 -20
  58. safeuploads-1.0.0/.github/workflows/docs.yml +0 -49
  59. safeuploads-1.0.0/CONTRIBUTING.md +0 -47
  60. safeuploads-1.0.0/safeuploads/file_validator.py +0 -1026
  61. safeuploads-1.0.0/safeuploads/utils.py +0 -176
  62. safeuploads-1.0.0/uv.lock +0 -419
  63. {safeuploads-1.0.0 → safeuploads-1.1.0}/.github/agents/se-gitops-ci-specialist.agent.md +0 -0
  64. {safeuploads-1.0.0 → safeuploads-1.1.0}/.github/agents/se-security-reviewer.agent.md +0 -0
  65. {safeuploads-1.0.0 → safeuploads-1.1.0}/.github/agents/test-implementor.agent.md +0 -0
  66. {safeuploads-1.0.0 → safeuploads-1.1.0}/.github/agents/test-reviewer.agent.md +0 -0
  67. {safeuploads-1.0.0 → safeuploads-1.1.0}/.github/instructions/agents.instructions.md +0 -0
  68. {safeuploads-1.0.0 → safeuploads-1.1.0}/.github/instructions/github-actions-ci-cd-best-practices.instructions.md +0 -0
  69. {safeuploads-1.0.0 → safeuploads-1.1.0}/.github/instructions/instructions.instructions.md +0 -0
  70. {safeuploads-1.0.0 → safeuploads-1.1.0}/.github/instructions/prompt.instructions.md +0 -0
  71. {safeuploads-1.0.0 → safeuploads-1.1.0}/.github/instructions/python.instructions.md +0 -0
  72. {safeuploads-1.0.0 → safeuploads-1.1.0}/.github/instructions/security-and-owasp.instructions.md +0 -0
  73. {safeuploads-1.0.0 → safeuploads-1.1.0}/.gitignore +0 -0
  74. {safeuploads-1.0.0 → safeuploads-1.1.0}/CODE_OF_CONDUCT.md +0 -0
  75. {safeuploads-1.0.0 → safeuploads-1.1.0}/LICENSE.md +0 -0
  76. {safeuploads-1.0.0 → safeuploads-1.1.0}/SECURITY.md +0 -0
  77. {safeuploads-1.0.0 → safeuploads-1.1.0}/docs/api.md +0 -0
  78. {safeuploads-1.0.0 → safeuploads-1.1.0}/docs/security/integration-checklist.md +0 -0
  79. {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/protocols.py +0 -0
  80. {safeuploads-1.0.0 → safeuploads-1.1.0}/safeuploads/validators/__init__.py +2 -2
  81. {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/__init__.py +0 -0
  82. {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/conftest.py +0 -0
  83. {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/fuzz/__init__.py +0 -0
  84. {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/fuzz/test_fuzz_config.py +0 -0
  85. {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/fuzz/test_fuzz_filenames.py +0 -0
  86. {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/fuzz/test_fuzz_images.py +0 -0
  87. {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/fuzz/test_fuzz_zip.py +0 -0
  88. {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/inspectors/__init__.py +0 -0
  89. {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/inspectors/test_content_inspector.py +0 -0
  90. {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/inspectors/test_gzip_inspector.py +0 -0
  91. {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/test_audit.py +0 -0
  92. {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/test_exceptions.py +0 -0
  93. {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/test_performance.py +0 -0
  94. {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/test_validate_activity_file.py +0 -0
  95. {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/test_validate_gzip_file.py +0 -0
  96. {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/validators/__init__.py +0 -0
  97. {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/validators/test_extension_validator.py +0 -0
  98. {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/validators/test_windows_validator.py +0 -0
  99. {safeuploads-1.0.0 → safeuploads-1.1.0}/tests/validators/test_xml_validator.py +0 -0
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: 🐞 Bug report
3
+ about: Report a bug
4
+ title: "[BUG]: "
5
+ labels: [bug]
6
+ assignees: ''
7
+ ---
8
+
9
+ ## Checklist
10
+
11
+ - [ ] I have searched **existing issues** to make sure this bug hasn't already been reported.
12
+ - [ ] I have checked the **documentation** at [https://safeuploads.endurain.com](https://safeuploads.endurain.com) and didn't find a solution.
13
+
14
+ ## Description
15
+
16
+ Describe the bug you encountered.
17
+
18
+ ## Steps to Reproduce
19
+
20
+ 1. Version
21
+ 2. Go to '...'
22
+ 3. Click on '...'
23
+ 4. See error
24
+
25
+ ## Expected Behavior
26
+
27
+ What did you expect to happen?
28
+
29
+ ## Screenshots, logs or files
30
+
31
+ If applicable, add screenshots, logs or files to help explain your problem. If you do not wish to upload sensitive information to the issue you can send that info to [joao@endurain.com](joao@endurain.com).
32
+
33
+ ## Additional Context
34
+
35
+ Add any other context about the problem here.
@@ -0,0 +1,5 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: 📚 Documentation
4
+ url: https://safeuploads.endurain.com
5
+ about: Please check our documentation before creating an issue.
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: 💡 Feature request
3
+ about: Suggest an idea or enhancement for this project
4
+ title: "[FEATURE]: "
5
+ labels: [enhancement]
6
+ assignees: ''
7
+ ---
8
+
9
+ ## Summary
10
+
11
+ Briefly describe the feature you'd like to see.
12
+
13
+ ## Motivation
14
+
15
+ Why is this feature important? What problem does it solve?
16
+
17
+ ## Proposed Solution
18
+
19
+ How would this feature work? Describe any ideas you have.
20
+
21
+ ## Alternatives
22
+
23
+ Have you considered any alternatives?
24
+
25
+ ## Additional Context
26
+
27
+ Add screenshots, mockups, or examples if relevant.
@@ -0,0 +1,33 @@
1
+ ## What does this PR do?
2
+
3
+ A clear description of the change.
4
+
5
+ ## Related issue
6
+
7
+ Required for anything beyond a small bug fix or typo.
8
+ Don't forget to reference the issue so it auto-closes works, for example use `Closes #XYZ`
9
+
10
+ ## Type of change
11
+
12
+ Check all that apply
13
+
14
+ - [ ] Bug fix
15
+ - [ ] New feature (exception during freeze; link prior discussion/acknowledgment)
16
+ - [ ] Documentation
17
+ - [ ] Refactor / code quality
18
+ - [ ] Translation
19
+ - [ ] Other (describe below)
20
+
21
+ ## Checklist
22
+
23
+ - [ ] This PR is focused on a single concern
24
+ - [ ] An issue was opened and acknowledged before writing this code (if not a small fix)
25
+ - [ ] If this PR introduces a new feature during freeze, it was discussed and explicitly acknowledged first
26
+ - [ ] The PR is under ~300 lines changed, excluding lock files, migrations, and generated code
27
+ - [ ] Tests have been added or updated where applicable
28
+ - [ ] Documentation has been updated where applicable
29
+ - [ ] Commit messages follow Conventional Commits format
30
+
31
+ ## Notes for the reviewer
32
+
33
+ Anything that would help review this. Tricky areas, alternative approaches considered, screenshots, etc.
@@ -0,0 +1,165 @@
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
+ :\x20
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
+ """Yield messages from stdin or CLI arguments."""
106
+ if args.stdin:
107
+ for line in sys.stdin:
108
+ stripped = line.rstrip("\n")
109
+ if stripped:
110
+ yield stripped
111
+ else:
112
+ yield from args.messages
113
+
114
+
115
+ def main(argv: list[str] | None = None) -> int:
116
+ """Validate all messages and return a process exit code."""
117
+ parser = argparse.ArgumentParser(
118
+ description="Validate Conventional Commits headers.",
119
+ )
120
+ parser.add_argument(
121
+ "messages",
122
+ nargs="*",
123
+ help="One or more commit messages (or PR titles) to validate.",
124
+ )
125
+ parser.add_argument(
126
+ "--stdin",
127
+ action="store_true",
128
+ help="Read messages from standard input, one per line.",
129
+ )
130
+ args = parser.parse_args(argv)
131
+
132
+ if not args.stdin and not args.messages:
133
+ parser.error("provide at least one message or use --stdin")
134
+
135
+ failures = 0
136
+ checked = 0
137
+ for message in _iter_messages(args):
138
+ checked += 1
139
+ errors = _validate_message(message)
140
+ safe_message = _safe_log_value(message)
141
+ if errors:
142
+ failures += 1
143
+ print(f"INVALID: {safe_message}", file=sys.stderr)
144
+ for err in errors:
145
+ print(f" - {err}", file=sys.stderr)
146
+ else:
147
+ print(f"OK: {safe_message}")
148
+
149
+ if checked == 0:
150
+ print("No messages to validate.", file=sys.stderr)
151
+ return 1
152
+
153
+ if failures:
154
+ print(
155
+ f"\n{failures} of {checked} message(s) failed "
156
+ "Conventional Commits validation.",
157
+ file=sys.stderr,
158
+ )
159
+ return 1
160
+
161
+ return 0
162
+
163
+
164
+ if __name__ == "__main__":
165
+ sys.exit(main())
@@ -0,0 +1,40 @@
1
+ name: Conventional Commits
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, edited, synchronize, reopened]
6
+
7
+ concurrency:
8
+ group: ${{ forgejo.workflow }}-${{ forgejo.event.pull_request.number }}
9
+ cancel-in-progress: true
10
+
11
+ jobs:
12
+ validate:
13
+ runs-on: local-runner
14
+
15
+ steps:
16
+ - name: Checkout code
17
+ uses: https://data.forgejo.org/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
18
+ with:
19
+ fetch-depth: 0
20
+
21
+ - name: Validate PR title
22
+ # The PR title is passed through an environment variable to prevent
23
+ # shell injection via crafted titles (see security-and-owasp guidelines).
24
+ env:
25
+ PR_TITLE: ${{ forgejo.event.pull_request.title }}
26
+ run: |
27
+ python3 .forgejo/scripts/check_conventional_commits.py "$PR_TITLE"
28
+
29
+ - name: Validate commits in PR
30
+ # Validates every commit subject between the PR base and head. Use the
31
+ # base/head SHAs from the event payload so we don't depend on the local
32
+ # checkout ref layout.
33
+ env:
34
+ BASE_SHA: ${{ forgejo.event.pull_request.base.sha }}
35
+ HEAD_SHA: ${{ forgejo.event.pull_request.head.sha }}
36
+ run: |
37
+ set -euo pipefail
38
+ git fetch --no-tags origin "$BASE_SHA" "$HEAD_SHA"
39
+ git log --no-merges --format=%s "$BASE_SHA".."$HEAD_SHA" \
40
+ | python3 .forgejo/scripts/check_conventional_commits.py --stdin
@@ -0,0 +1,59 @@
1
+ name: Deploy documentation
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ dry-run:
7
+ description: Build docs but do not deploy
8
+ required: false
9
+ default: false
10
+ type: boolean
11
+ push:
12
+ branches:
13
+ - main
14
+ - master
15
+ paths:
16
+ - 'docs/**'
17
+ - 'mkdocs.yml'
18
+ - 'safeuploads/**/*.py'
19
+ - '.forgejo/workflows/docs.yml'
20
+
21
+ concurrency:
22
+ group: ${{ forgejo.workflow }}-${{ forgejo.ref }}
23
+ cancel-in-progress: true
24
+
25
+ jobs:
26
+
27
+ deploy:
28
+
29
+ runs-on: local-runner
30
+
31
+ steps:
32
+ - name: Checkout code
33
+ uses: https://data.forgejo.org/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
34
+ with:
35
+ fetch-depth: 0
36
+
37
+ - name: Install uv
38
+ uses: https://data.forgejo.org/astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
39
+ with:
40
+ working-directory: .
41
+
42
+ - name: Sync documentation dependencies
43
+ working-directory: .
44
+ run: uv sync --frozen --only-group docs
45
+ - name: Build documentation
46
+ run: uv run --no-sync --project safeuploads mkdocs build --site-dir site
47
+
48
+ - name: Set dry-run mode
49
+ if: ${{ inputs.dry-run == true }}
50
+ run: echo "DOCS_DRY_RUN=true" >> "$FORGEJO_ENV"
51
+
52
+ - name: Deploy documentation to Codeberg Pages
53
+ if: env.DOCS_DRY_RUN != 'true'
54
+ uses: https://data.forgejo.org/actions/git-pages@5978530ffb9bbe196ad9fdc0c48fdaf1483de4cf # v2.1.0
55
+ with:
56
+ site: https://safeuploads.endurain.com/
57
+ server: codeberg.page
58
+ token: ${{ forge.token }}
59
+ source: site/
@@ -9,35 +9,25 @@ on:
9
9
  # Cancel in-progress runs for the same branch to avoid wasted
10
10
  # runner minutes.
11
11
  concurrency:
12
- group: ${{ github.workflow }}-${{ github.ref }}
12
+ group: ${{ forge.workflow }}-${{ forge.ref }}
13
13
  cancel-in-progress: true
14
14
 
15
- # Principle of least privilege: only read access by default.
16
- permissions:
17
- contents: read
18
-
19
15
  jobs:
20
16
  fuzz:
21
17
  name: Fuzz
22
- runs-on: ubuntu-latest
18
+ runs-on: local-runner
23
19
 
24
20
  steps:
25
21
  - name: Checkout repository
26
- uses: actions/checkout@v6
22
+ uses: https://data.forgejo.org/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
27
23
 
28
24
  - name: Install system dependencies
29
- run: sudo apt-get update && sudo apt-get install -y libmagic1
30
-
31
- - name: Set up Python 3.13
32
- uses: actions/setup-python@v6
33
- with:
34
- python-version: "3.13"
25
+ run: apt-get update && apt-get install -y libmagic1
35
26
 
36
27
  - name: Install uv
37
- uses: astral-sh/setup-uv@v7
28
+ uses: https://data.forgejo.org/astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
38
29
  with:
39
- enable-cache: true
40
- cache-dependency-glob: "uv.lock"
30
+ working-directory: .
41
31
 
42
32
  - name: Install dependencies
43
33
  run: uv sync --frozen --dev
@@ -9,13 +9,9 @@ on:
9
9
  # Cancel in-progress runs for the same branch/PR to avoid wasted
10
10
  # runner minutes and race conditions on shared resources.
11
11
  concurrency:
12
- group: ${{ github.workflow }}-${{ github.ref }}
12
+ group: ${{ forge.workflow }}-${{ forge.ref }}
13
13
  cancel-in-progress: true
14
14
 
15
- # Principle of least privilege: only read access by default.
16
- permissions:
17
- contents: read
18
-
19
15
  jobs:
20
16
  # ----------------------------------------------------------------
21
17
  # Lint
@@ -24,22 +20,18 @@ jobs:
24
20
  # ----------------------------------------------------------------
25
21
  lint:
26
22
  name: Lint
27
- runs-on: ubuntu-latest
23
+ runs-on: codeberg-tiny
28
24
 
29
25
  steps:
30
- - name: Checkout repository
31
- uses: actions/checkout@v6
32
-
33
- - name: Set up Python 3.13
34
- uses: actions/setup-python@v6
26
+ - name: Checkout code
27
+ uses: https://data.forgejo.org/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
35
28
  with:
36
- python-version: "3.13"
29
+ fetch-depth: 1
37
30
 
38
31
  - name: Install uv
39
- uses: astral-sh/setup-uv@v7
32
+ uses: https://data.forgejo.org/astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
40
33
  with:
41
- enable-cache: true
42
- cache-dependency-glob: "uv.lock"
34
+ working-directory: .
43
35
 
44
36
  - name: Install dependencies
45
37
  run: uv sync --frozen --dev
@@ -50,6 +42,9 @@ jobs:
50
42
  - name: Check formatting with ruff
51
43
  run: uv run ruff format --check safeuploads tests examples
52
44
 
45
+ - name: Type check with mypy
46
+ run: uv run mypy
47
+
53
48
  # ----------------------------------------------------------------
54
49
  # Test
55
50
  # Runs the full pytest suite with coverage. Only runs after lint
@@ -57,40 +52,34 @@ jobs:
57
52
  # ----------------------------------------------------------------
58
53
  test:
59
54
  name: Test
60
- runs-on: ubuntu-latest
55
+ runs-on: local-runner
61
56
  needs: lint
62
57
 
63
58
  steps:
64
59
  - name: Checkout repository
65
- uses: actions/checkout@v6
60
+ uses: https://data.forgejo.org/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
66
61
 
67
62
  - name: Install system dependencies
68
- run: sudo apt-get update && sudo apt-get install -y libmagic1
69
-
70
- - name: Set up Python 3.13
71
- uses: actions/setup-python@v6
72
- with:
73
- python-version: "3.13"
63
+ run: apt-get update && apt-get install -y libmagic1
74
64
 
75
65
  - name: Install uv
76
- uses: astral-sh/setup-uv@v7
66
+ uses: https://data.forgejo.org/astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
77
67
  with:
78
- enable-cache: true
79
- cache-dependency-glob: "uv.lock"
68
+ working-directory: .
80
69
 
81
70
  - name: Install dependencies
82
- run: uv sync --frozen --dev
71
+ run: uv sync --frozen --dev --extra fastapi
83
72
 
84
73
  - name: Audit Python dependencies
85
- run: uvx pip-audit --require-hashes --disable-pip -r <(uv export --frozen --no-header --no-emit-project)
74
+ run: uvx pip-audit --require-hashes --disable-pip -r <(uv export --frozen --no-header --no-emit-project --no-dev)
86
75
 
87
76
  - name: Run tests with coverage
88
77
  run: uv run pytest --cov-fail-under=90
89
78
 
90
79
  - name: Upload coverage report
91
- uses: actions/upload-artifact@v7
80
+ uses: https://data.forgejo.org/forgejo/upload-artifact@v4
92
81
  if: always()
93
82
  with:
94
83
  name: safeuploads-coverage
95
84
  path: .coverage
96
- retention-days: 7
85
+ retention-days: 7
@@ -13,25 +13,22 @@ concurrency:
13
13
  group: publish
14
14
  cancel-in-progress: false
15
15
 
16
- permissions:
17
- contents: read
18
- id-token: write # Required for trusted publishing (OIDC)
19
-
20
16
  jobs:
21
17
  publish:
22
18
  name: Build & Publish
23
- runs-on: ubuntu-latest
24
- environment: pypi
19
+ runs-on: local-runner
25
20
 
26
21
  steps:
27
22
  - name: Checkout repository
28
- uses: actions/checkout@v6
23
+ uses: https://data.forgejo.org/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
29
24
 
30
25
  - name: Verify version matches input
31
26
  run: |
32
27
  PKG_VERSION=$(python3 -c "
33
28
  import tomllib, pathlib
34
- data = tomllib.loads(pathlib.Path('pyproject.toml').read_text())
29
+ data = tomllib.loads(
30
+ pathlib.Path('pyproject.toml').read_text()
31
+ )
35
32
  print(data['project']['version'])
36
33
  ")
37
34
  if [ "$PKG_VERSION" != "${{ inputs.confirm }}" ]; then
@@ -41,18 +38,12 @@ jobs:
41
38
  echo "Publishing version $PKG_VERSION"
42
39
 
43
40
  - name: Install system dependencies
44
- run: sudo apt-get update && sudo apt-get install -y libmagic1
45
-
46
- - name: Set up Python 3.13
47
- uses: actions/setup-python@v6
48
- with:
49
- python-version: "3.13"
41
+ run: apt-get update && apt-get install -y libmagic1
50
42
 
51
43
  - name: Install uv
52
- uses: astral-sh/setup-uv@v7
44
+ uses: https://data.forgejo.org/astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
53
45
  with:
54
- enable-cache: true
55
- cache-dependency-glob: "uv.lock"
46
+ working-directory: .
56
47
 
57
48
  - name: Install dependencies
58
49
  run: uv sync --frozen --dev
@@ -64,4 +55,6 @@ jobs:
64
55
  run: uv build
65
56
 
66
57
  - name: Publish to PyPI
67
- uses: pypa/gh-action-pypi-publish@release/v1
58
+ env:
59
+ UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
60
+ run: uv publish
@@ -0,0 +1,32 @@
1
+ name: Dependency updates
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ schedule:
6
+ - cron: '0 4 * * 0'
7
+
8
+ concurrency:
9
+ group: ${{ forgejo.workflow }}-${{ forgejo.ref }}
10
+ cancel-in-progress: false
11
+
12
+ jobs:
13
+
14
+ renovate:
15
+
16
+ runs-on: local-runner
17
+
18
+ steps:
19
+ - name: Checkout code
20
+ uses: https://data.forgejo.org/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
21
+ with:
22
+ fetch-depth: 1
23
+
24
+ - name: Run Renovate
25
+ uses: docker://ghcr.io/renovatebot/renovate@sha256:38aac6e29e09288bc3fb3b95ba9445962508a1081013144d5b6ec5a6735707b9 # 43.168.5
26
+ env:
27
+ LOG_LEVEL: info
28
+ RENOVATE_CONFIG_FILE: renovate.json
29
+ RENOVATE_ENDPOINT: https://codeberg.org/api/v1/
30
+ RENOVATE_PLATFORM: gitea
31
+ RENOVATE_REPOSITORIES: ${{ forgejo.repository }}
32
+ RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
@@ -70,3 +70,24 @@ class MyClass:
70
70
  ## Documentation & Examples
71
71
  - Update docstrings and README examples when public APIs change (e.g., `FileValidator`, utility helpers).
72
72
  - Demonstrate usage snippets with async FastAPI context where applicable.
73
+
74
+ ## Commits logic
75
+
76
+ Committing should use clear messages following [Conventional Commits](https://www.conventionalcommits.org/) format:
77
+
78
+ **Format:** `<type>(<scope>)!: <description>` — `(<scope>)` and the breaking-change `!` are optional.
79
+
80
+ The following rules are enforced automatically on every PR (against the PR title and every commit subject) by `.forgejo/workflows/conventional-commits.yml`. Validation follows the [Conventional Commits 1.0.0](https://www.conventionalcommits.org/en/v1.0.0/) spec, with a single project-policy addition (the allowed-type whitelist). Generated commit messages must comply:
81
+
82
+ - **Header format:** `<type>(<scope>)!: <description>` — the `(<scope>)` and breaking-change `!` are optional, and a space is required after the colon.
83
+ - **Allowed types (project policy):** `build`, `chore`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `style`, `test`. Types are matched case-insensitively per the spec.
84
+ - **Scope** (optional) is any non-empty string (no parentheses), per the spec.
85
+ - **Description** is free-form text, per the spec (no lowercase requirement, no trailing-period restriction, no length limit).
86
+ - **Breaking changes** are marked with `!` after the type/scope (e.g. `feat(api)!: ...`) or a `BREAKING CHANGE:` footer in the commit body.
87
+
88
+ Examples:
89
+ - `feat: add GPX max speed parsing`
90
+ - `fix(garmin): handle multi-segment GPX distance correctly`
91
+ - `docs: update development instructions`
92
+ - `test(activities): add regression test for GPX segment handling`
93
+ - `refactor(api)!: rename Activity.distance to total_distance`