safeuploads 1.0.0__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.
Files changed (92) hide show
  1. safeuploads-1.0.1/.forgejo/scripts/check_conventional_commits.py +163 -0
  2. safeuploads-1.0.1/.forgejo/workflows/conventional-commits.yml +44 -0
  3. safeuploads-1.0.1/.forgejo/workflows/docs.yml +51 -0
  4. safeuploads-1.0.1/.forgejo/workflows/fuzz.yml +44 -0
  5. safeuploads-1.0.1/.forgejo/workflows/lint-and-test.yml +86 -0
  6. safeuploads-1.0.1/.forgejo/workflows/publish.yml +63 -0
  7. safeuploads-1.0.1/.forgejo/workflows/renovate.yml +37 -0
  8. {safeuploads-1.0.0 → safeuploads-1.0.1}/.github/copilot-instructions.md +21 -0
  9. {safeuploads-1.0.0 → safeuploads-1.0.1}/.github/workflows/lint&test.yml +1 -1
  10. safeuploads-1.0.1/CONTRIBUTING.md +82 -0
  11. {safeuploads-1.0.0 → safeuploads-1.0.1}/PKG-INFO +10 -12
  12. {safeuploads-1.0.0 → safeuploads-1.0.1}/README.md +9 -11
  13. {safeuploads-1.0.0 → safeuploads-1.0.1}/docs/index.md +10 -13
  14. {safeuploads-1.0.0 → safeuploads-1.0.1}/docs/rate-limiting.md +1 -1
  15. {safeuploads-1.0.0 → safeuploads-1.0.1}/examples/fastapi_example.py +2 -2
  16. {safeuploads-1.0.0 → safeuploads-1.0.1}/mkdocs.yml +1 -1
  17. {safeuploads-1.0.0 → safeuploads-1.0.1}/pyproject.toml +1 -1
  18. safeuploads-1.0.1/renovate.json +35 -0
  19. {safeuploads-1.0.0 → safeuploads-1.0.1}/safeuploads/file_validator.py +3 -1
  20. safeuploads-1.0.1/uv.lock +422 -0
  21. safeuploads-1.0.0/.github/CODEOWNERS +0 -15
  22. safeuploads-1.0.0/.github/FUNDING.yml +0 -13
  23. safeuploads-1.0.0/.github/dependabot.yml +0 -20
  24. safeuploads-1.0.0/.github/workflows/docs.yml +0 -49
  25. safeuploads-1.0.0/CONTRIBUTING.md +0 -47
  26. safeuploads-1.0.0/uv.lock +0 -419
  27. {safeuploads-1.0.0 → safeuploads-1.0.1}/.github/agents/se-gitops-ci-specialist.agent.md +0 -0
  28. {safeuploads-1.0.0 → safeuploads-1.0.1}/.github/agents/se-security-reviewer.agent.md +0 -0
  29. {safeuploads-1.0.0 → safeuploads-1.0.1}/.github/agents/test-implementor.agent.md +0 -0
  30. {safeuploads-1.0.0 → safeuploads-1.0.1}/.github/agents/test-reviewer.agent.md +0 -0
  31. {safeuploads-1.0.0 → safeuploads-1.0.1}/.github/instructions/agents.instructions.md +0 -0
  32. {safeuploads-1.0.0 → safeuploads-1.0.1}/.github/instructions/github-actions-ci-cd-best-practices.instructions.md +0 -0
  33. {safeuploads-1.0.0 → safeuploads-1.0.1}/.github/instructions/instructions.instructions.md +0 -0
  34. {safeuploads-1.0.0 → safeuploads-1.0.1}/.github/instructions/prompt.instructions.md +0 -0
  35. {safeuploads-1.0.0 → safeuploads-1.0.1}/.github/instructions/python.instructions.md +0 -0
  36. {safeuploads-1.0.0 → safeuploads-1.0.1}/.github/instructions/security-and-owasp.instructions.md +0 -0
  37. {safeuploads-1.0.0 → safeuploads-1.0.1}/.github/workflows/fuzz.yml +0 -0
  38. {safeuploads-1.0.0 → safeuploads-1.0.1}/.github/workflows/publish.yml +0 -0
  39. {safeuploads-1.0.0 → safeuploads-1.0.1}/.gitignore +0 -0
  40. {safeuploads-1.0.0 → safeuploads-1.0.1}/CODE_OF_CONDUCT.md +0 -0
  41. {safeuploads-1.0.0 → safeuploads-1.0.1}/LICENSE.md +0 -0
  42. {safeuploads-1.0.0 → safeuploads-1.0.1}/SECURITY.md +0 -0
  43. {safeuploads-1.0.0 → safeuploads-1.0.1}/docs/api.md +0 -0
  44. {safeuploads-1.0.0 → safeuploads-1.0.1}/docs/security/architecture.md +0 -0
  45. {safeuploads-1.0.0 → safeuploads-1.0.1}/docs/security/integration-checklist.md +0 -0
  46. {safeuploads-1.0.0 → safeuploads-1.0.1}/docs/security/threat-model.md +0 -0
  47. {safeuploads-1.0.0 → safeuploads-1.0.1}/examples/README.md +0 -0
  48. {safeuploads-1.0.0 → safeuploads-1.0.1}/safeuploads/__init__.py +0 -0
  49. {safeuploads-1.0.0 → safeuploads-1.0.1}/safeuploads/audit.py +0 -0
  50. {safeuploads-1.0.0 → safeuploads-1.0.1}/safeuploads/config.py +0 -0
  51. {safeuploads-1.0.0 → safeuploads-1.0.1}/safeuploads/enums.py +0 -0
  52. {safeuploads-1.0.0 → safeuploads-1.0.1}/safeuploads/exceptions.py +0 -0
  53. {safeuploads-1.0.0 → safeuploads-1.0.1}/safeuploads/inspectors/__init__.py +0 -0
  54. {safeuploads-1.0.0 → safeuploads-1.0.1}/safeuploads/inspectors/content_inspector.py +0 -0
  55. {safeuploads-1.0.0 → safeuploads-1.0.1}/safeuploads/inspectors/gzip_inspector.py +0 -0
  56. {safeuploads-1.0.0 → safeuploads-1.0.1}/safeuploads/inspectors/zip_inspector.py +0 -0
  57. {safeuploads-1.0.0 → safeuploads-1.0.1}/safeuploads/protocols.py +0 -0
  58. {safeuploads-1.0.0 → safeuploads-1.0.1}/safeuploads/utils.py +0 -0
  59. {safeuploads-1.0.0 → safeuploads-1.0.1}/safeuploads/validators/__init__.py +0 -0
  60. {safeuploads-1.0.0 → safeuploads-1.0.1}/safeuploads/validators/base.py +0 -0
  61. {safeuploads-1.0.0 → safeuploads-1.0.1}/safeuploads/validators/compression_validator.py +0 -0
  62. {safeuploads-1.0.0 → safeuploads-1.0.1}/safeuploads/validators/extension_validator.py +0 -0
  63. {safeuploads-1.0.0 → safeuploads-1.0.1}/safeuploads/validators/unicode_validator.py +0 -0
  64. {safeuploads-1.0.0 → safeuploads-1.0.1}/safeuploads/validators/windows_validator.py +0 -0
  65. {safeuploads-1.0.0 → safeuploads-1.0.1}/safeuploads/validators/xml_validator.py +0 -0
  66. {safeuploads-1.0.0 → safeuploads-1.0.1}/tests/__init__.py +0 -0
  67. {safeuploads-1.0.0 → safeuploads-1.0.1}/tests/conftest.py +0 -0
  68. {safeuploads-1.0.0 → safeuploads-1.0.1}/tests/fuzz/__init__.py +0 -0
  69. {safeuploads-1.0.0 → safeuploads-1.0.1}/tests/fuzz/test_fuzz_config.py +0 -0
  70. {safeuploads-1.0.0 → safeuploads-1.0.1}/tests/fuzz/test_fuzz_filenames.py +0 -0
  71. {safeuploads-1.0.0 → safeuploads-1.0.1}/tests/fuzz/test_fuzz_images.py +0 -0
  72. {safeuploads-1.0.0 → safeuploads-1.0.1}/tests/fuzz/test_fuzz_zip.py +0 -0
  73. {safeuploads-1.0.0 → safeuploads-1.0.1}/tests/inspectors/__init__.py +0 -0
  74. {safeuploads-1.0.0 → safeuploads-1.0.1}/tests/inspectors/test_content_inspector.py +0 -0
  75. {safeuploads-1.0.0 → safeuploads-1.0.1}/tests/inspectors/test_gzip_inspector.py +0 -0
  76. {safeuploads-1.0.0 → safeuploads-1.0.1}/tests/inspectors/test_zip_inspector.py +0 -0
  77. {safeuploads-1.0.0 → safeuploads-1.0.1}/tests/test_audit.py +0 -0
  78. {safeuploads-1.0.0 → safeuploads-1.0.1}/tests/test_config.py +0 -0
  79. {safeuploads-1.0.0 → safeuploads-1.0.1}/tests/test_config_validation.py +0 -0
  80. {safeuploads-1.0.0 → safeuploads-1.0.1}/tests/test_enums.py +0 -0
  81. {safeuploads-1.0.0 → safeuploads-1.0.1}/tests/test_exceptions.py +0 -0
  82. {safeuploads-1.0.0 → safeuploads-1.0.1}/tests/test_file_validator.py +0 -0
  83. {safeuploads-1.0.0 → safeuploads-1.0.1}/tests/test_performance.py +0 -0
  84. {safeuploads-1.0.0 → safeuploads-1.0.1}/tests/test_utils.py +0 -0
  85. {safeuploads-1.0.0 → safeuploads-1.0.1}/tests/test_validate_activity_file.py +0 -0
  86. {safeuploads-1.0.0 → safeuploads-1.0.1}/tests/test_validate_gzip_file.py +0 -0
  87. {safeuploads-1.0.0 → safeuploads-1.0.1}/tests/validators/__init__.py +0 -0
  88. {safeuploads-1.0.0 → safeuploads-1.0.1}/tests/validators/test_compression_validator.py +0 -0
  89. {safeuploads-1.0.0 → safeuploads-1.0.1}/tests/validators/test_extension_validator.py +0 -0
  90. {safeuploads-1.0.0 → safeuploads-1.0.1}/tests/validators/test_unicode_validator.py +0 -0
  91. {safeuploads-1.0.0 → safeuploads-1.0.1}/tests/validators/test_windows_validator.py +0 -0
  92. {safeuploads-1.0.0 → safeuploads-1.0.1}/tests/validators/test_xml_validator.py +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 }}
@@ -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`
@@ -82,7 +82,7 @@ jobs:
82
82
  run: uv sync --frozen --dev
83
83
 
84
84
  - name: Audit Python dependencies
85
- run: uvx pip-audit --require-hashes --disable-pip -r <(uv export --frozen --no-header --no-emit-project)
85
+ run: uvx pip-audit --require-hashes --disable-pip -r <(uv export --frozen --no-header --no-emit-project --no-dev)
86
86
 
87
87
  - name: Run tests with coverage
88
88
  run: uv run pytest --cov-fail-under=90
@@ -0,0 +1,82 @@
1
+ # Contributing to Endurain
2
+
3
+ Thank you for considering contributing to Endurain! Before diving in, please read these guidelines carefully. They exist to make the process sustainable for everyone.
4
+
5
+ ## A Note on Maintainership
6
+
7
+ Endurain is maintained by a single person in their spare time. This means review bandwidth is genuinely limited. Following these guidelines isn't bureaucracy, it's what allows contributions to actually get merged rather than sitting in a queue indefinitely.
8
+
9
+ ## Before You Write Any Code
10
+
11
+ **Open an issue first.** For anything beyond a small bug fix, typo, or documentation improvement, please open an issue and wait for a response before writing code. This takes minutes and can save you hours of work on something that won't be merged because it conflicts with planned direction, existing work, or project scope.
12
+
13
+ If an issue already exists, comment on it to signal your intent so work isn't duplicated.
14
+
15
+ ## Pull Request Size — The Most Important Rule
16
+
17
+ **Keep PRs small and focused on a single concern.**
18
+
19
+ As a rule of thumb:
20
+
21
+ - **Target under 300 lines changed** (excluding lock files, generated code, and migrations)
22
+ - **One PR = one thing.** Don't bundle a bug fix with a refactor with a new feature
23
+ - If your change is naturally large (e.g. a new integration), break it into a chain of smaller PRs that can each be reviewed and merged independently
24
+
25
+ PRs that are too large to review efficiently will be asked to be split before they receive a review. This isn't a judgment on the quality of the work, it's a maintainability constraint.
26
+
27
+ **Excluded from the line count:** `uv.lock`, migration files, and other generated or vendored files.
28
+
29
+ ## How to Contribute
30
+
31
+ ### Bug Fixes
32
+
33
+ - Check if an issue already exists before opening a new one
34
+ - Include clear steps to reproduce in the issue
35
+ - Small, targeted fixes are the fastest path to a merge
36
+
37
+ ### Documentation
38
+
39
+ - Always welcome and rarely requires prior discussion
40
+ - Improvements to the docs site, inline code comments, and the README all count
41
+ - Keep the same tone and structure as existing docs
42
+
43
+ ### New Features
44
+
45
+ - **Always discuss in an issue first** — this is required, not optional
46
+ - Features that haven't been discussed and approved in an issue may be closed without review, regardless of quality
47
+
48
+ ### Refactors and Code Quality
49
+
50
+ - Must be discussed in an issue first
51
+ - Pure refactor PRs (no behaviour change) are easiest to review. Keep them separate from feature or fix PRs
52
+ - Include a clear explanation of what improved and why
53
+
54
+
55
+ ## Getting Started
56
+
57
+ 1. **Fork the repository** on Codeberg
58
+ 2. **Clone your fork** locally:
59
+ ```bash
60
+ git clone https://codeberg.org/YOUR_USERNAME/endurain.git
61
+ ```
62
+ 3. **Create a branch** and use a descriptive name:
63
+ ```bash
64
+ git checkout -b fix/garmin-sync-timeout
65
+ # or
66
+ git checkout -b feat/gpx-export
67
+ ```
68
+ 4. **Make your changes**, committing with clear messages following [Conventional Commits](https://www.conventionalcommits.org/):
69
+ ```
70
+ fix: handle timeout on Garmin sync
71
+ feat: add GPX export for activities
72
+ docs: clarify Docker environment variables
73
+ ```
74
+ 5. **Push and open a PR** against the `master` branch, filling in the PR template completely
75
+
76
+ ## Response Time Expectations
77
+
78
+ Reviews may take days to weeks depending on availability. A PR sitting without a response is not a rejection. Please feel free to leave a polite ping after two weeks if there's been no activity.
79
+
80
+ ## Thank You
81
+
82
+ Even small contributions make a real difference. Thank you for taking the time to improve Endurain for everyone who self-hosts it.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: safeuploads
3
- Version: 1.0.0
3
+ Version: 1.0.1
4
4
  Summary: A comprehensive file security system for validating uploads and preventing attacks
5
5
  Author-email: João Vitória Silva <joao@endurain.com>
6
6
  License: MIT
@@ -14,9 +14,13 @@ Description-Content-Type: text/markdown
14
14
 
15
15
  # safeuploads
16
16
 
17
- ![License](https://img.shields.io/github/license/endurain-project/safeuploads)
18
- [![GitHub release](https://img.shields.io/github/v/release/endurain-project/safeuploads)](https://github.com/endurain-project/safeuploads/releases)
19
- [![GitHub stars](https://img.shields.io/github/stars/endurain-project/safeuploads.svg?style=social&label=Star)](https://github.com/endurain-project/safeuploads/stargazers)
17
+ [![License](https://img.shields.io/badge/license-MIT-green)](https://codeberg.org/endurain-project/safeuploads/src/branch/main/LICENSE.md)
18
+ [![Release](https://img.shields.io/badge/dynamic/json?url=https://codeberg.org/api/v1/repos/endurain-project/safeuploads/releases/latest&query=$.tag_name&label=release&color=blue)](https://codeberg.org/endurain-project/safeuploads/releases)
19
+ [![PyPI version](https://img.shields.io/pypi/v/safeuploads)](https://pypi.org/project/safeuploads/)
20
+ [![PyPI downloads](https://img.shields.io/pypi/dm/safeuploads)](https://pypi.org/project/safeuploads/)
21
+ [![Python](https://img.shields.io/badge/python-3.13%2B-blue)](https://pypi.org/project/safeuploads/)
22
+ [![Docs](https://img.shields.io/badge/docs-endurain--project.codeberg.page-blue)](https://endurain-project.codeberg.page/safeuploads/)
23
+ [![Stars](https://img.shields.io/badge/dynamic/json?url=https://codeberg.org/api/v1/repos/endurain-project/safeuploads&query=$.stars_count&label=stars&logo=codeberg)](https://codeberg.org/endurain-project/safeuploads)
20
24
 
21
25
  Secure file upload validation for Python 3.13+ applications. Catches dangerous filenames, malicious extensions, Windows reserved names, and compression-based attacks before you accept an upload.
22
26
 
@@ -130,13 +134,7 @@ except FileValidationError as err:
130
134
 
131
135
  ## Documentation
132
136
 
133
- Full documentation is available at the [safeuploads docs site](https://endurain-project.github.io/safeuploads/).
134
-
135
- ## Sponsors
136
-
137
- A huge thank you to the project sponsors! Your support helps keep this project going.
138
-
139
- Consider [sponsoring safeuploads on GitHub](https://github.com/sponsors/endurain-project) to ensure continuous development.
137
+ Full documentation is available at the [safeuploads docs site](https://endurain-project.codeberg.page/safeuploads/).
140
138
 
141
139
  ## License
142
140
 
@@ -147,5 +145,5 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
147
145
  Contributions welcome! See [Contributing Guidelines](CONTRIBUTING.md) for guidelines.
148
146
 
149
147
  <div align="center">
150
- <sub>Built with ❤️ from Portugal | Part of the <a href="https://github.com/endurain-project">Endurain</a> ecosystem</sub>
148
+ <sub>Built with ❤️ from Portugal | Part of the <a href="https://codeberg.org/endurain-project">Endurain</a> ecosystem</sub>
151
149
  </div>