prepare-assignment 0.4.0__tar.gz → 0.4.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/.github/workflows/semantic-release.yml +8 -2
  2. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/.github/workflows/test.yml +49 -0
  3. prepare_assignment-0.4.2/.releaserc.json +9 -0
  4. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/PKG-INFO +8 -8
  5. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/README.md +6 -6
  6. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/cli/main.py +1 -2
  7. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/cli/task.py +1 -2
  8. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/pyproject.toml +2 -2
  9. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/tests/core/test_command.py +17 -0
  10. prepare_assignment-0.4.2/tests/test_dependencies.py +48 -0
  11. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/tests/test_main.py +10 -0
  12. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/uv.lock +4 -4
  13. prepare_assignment-0.4.0/.releaserc.json +0 -9
  14. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  15. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  16. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/.github/dependabot.yml +0 -0
  17. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/.github/workflows/pr-title.yml +0 -0
  18. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/.github/workflows/pypi-publish.yml +0 -0
  19. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/.github/workflows/release.yml +0 -0
  20. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/.gitignore +0 -0
  21. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/LICENSE +0 -0
  22. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/__init__.py +0 -0
  23. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/__main__.py +0 -0
  24. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/cli/__init__.py +0 -0
  25. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/cli/tasks.py +0 -0
  26. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/core/__init__.py +0 -0
  27. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/core/check.py +0 -0
  28. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/core/command.py +0 -0
  29. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/core/expression.py +0 -0
  30. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/core/main.py +0 -0
  31. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/core/preparer.py +0 -0
  32. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/core/runner.py +0 -0
  33. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/core/shell.py +0 -0
  34. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/core/subsituter.py +0 -0
  35. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/core/task_handler.py +0 -0
  36. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/core/validator.py +0 -0
  37. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/core/versions.py +0 -0
  38. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/data/__init__.py +0 -0
  39. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/data/config.py +0 -0
  40. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/data/constants.py +0 -0
  41. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/data/errors.py +0 -0
  42. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/data/job_environment.py +0 -0
  43. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/data/prepare.py +0 -0
  44. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/data/task_definition.py +0 -0
  45. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/data/task_properties.py +0 -0
  46. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/data/types.py +0 -0
  47. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/schemas/config.schema.json +0 -0
  48. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/schemas/prepare.schema.json +0 -0
  49. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/schemas/task.schema.json +0 -0
  50. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/utils/__init__.py +0 -0
  51. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/utils/config.py +0 -0
  52. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/utils/default_validator.py +0 -0
  53. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/utils/dependency.py +0 -0
  54. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/utils/files.py +0 -0
  55. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/utils/logger.py +0 -0
  56. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/utils/paths.py +0 -0
  57. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/utils/resources.py +0 -0
  58. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/utils/tasks.py +0 -0
  59. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/utils/virtual_env.py +0 -0
  60. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/prepare_assignment/utils/yml_loader.py +0 -0
  61. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/tests/__init__.py +0 -0
  62. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/tests/core/__init__.py +0 -0
  63. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/tests/core/test_check.py +0 -0
  64. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/tests/core/test_expression.py +0 -0
  65. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/tests/core/test_preparer.py +0 -0
  66. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/tests/core/test_resolve_version.py +0 -0
  67. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/tests/core/test_runner.py +0 -0
  68. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/tests/core/test_runner_process.py +0 -0
  69. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/tests/core/test_shell.py +0 -0
  70. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/tests/core/test_substituter.py +0 -0
  71. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/tests/core/test_task_handler.py +0 -0
  72. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/tests/core/test_validator.py +0 -0
  73. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/tests/data/__init__.py +0 -0
  74. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/tests/data/test_prepare.py +0 -0
  75. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/tests/data/test_types.py +0 -0
  76. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/tests/testproject/prepare.yml +0 -0
  77. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/tests/testproject/solution/pom.xml +0 -0
  78. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/tests/testproject/solution/src/main/java/gradelist/AssessmentResult.java +0 -0
  79. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/tests/testproject/solution/src/main/java/gradelist/GradeList.java +0 -0
  80. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/tests/testproject/solution/src/main/java/gradelist/Test.java +0 -0
  81. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/tests/testproject/solution/src/test/java/gradelist/AssessmentResultTest.java +0 -0
  82. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/tests/testproject/solution/src/test/java/gradelist/GradeListTest.java +0 -0
  83. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/tests/utils/__init__.py +0 -0
  84. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/tests/utils/test_config.py +0 -0
  85. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/tests/utils/test_default_validator.py +0 -0
  86. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/tests/utils/test_files.py +0 -0
  87. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/tests/utils/test_logger.py +0 -0
  88. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/tests/utils/test_paths.py +0 -0
  89. {prepare_assignment-0.4.0 → prepare_assignment-0.4.2}/tests/utils/test_resources.py +0 -0
@@ -38,11 +38,17 @@ jobs:
38
38
  node-version: 22
39
39
 
40
40
  # Pinned to an exact version: this action publishes no floating major
41
- # tags, so @v6 would not resolve. All three plugins in .releaserc.json
42
- # ship with the semantic-release core it bundles.
41
+ # tags, so @v6 would not resolve. The plugins in .releaserc.json ship
42
+ # with the semantic-release core it bundles, the conventionalcommits
43
+ # preset (which understands `feat!:`) has to be installed. Version 9:
44
+ # version 10 needs a newer changelog writer than the bundled
45
+ # release-notes-generator uses.
43
46
  - name: Semantic release
44
47
  id: semantic
45
48
  uses: cycjimmy/semantic-release-action@v6.0.0
49
+ with:
50
+ extra_plugins: |
51
+ conventional-changelog-conventionalcommits@9
46
52
  env:
47
53
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
48
54
 
@@ -11,7 +11,39 @@ on:
11
11
  branches: [ "main" ]
12
12
 
13
13
  jobs:
14
+ # Skip the tests if a PR only changes documentation. The jobs are skipped instead of the whole workflow
15
+ # (paths-ignore), because a required check that never reports blocks the PR, see the 'tests' job.
16
+ changes:
17
+ runs-on: ubuntu-latest
18
+ permissions:
19
+ contents: read
20
+ pull-requests: read
21
+ outputs:
22
+ code: ${{ steps.filter.outputs.code }}
23
+ steps:
24
+ - id: filter
25
+ env:
26
+ GH_TOKEN: ${{ github.token }}
27
+ PR: ${{ github.event.pull_request.number }}
28
+ REPO: ${{ github.repository }}
29
+ run: |
30
+ files=$(gh pr diff "$PR" --name-only --repo "$REPO")
31
+ echo "Changed files:"
32
+ echo "$files"
33
+ code=false
34
+ # Fail safe: run the tests if the changed files cannot be determined
35
+ [ -z "$files" ] && code=true
36
+ while IFS= read -r file; do
37
+ case "$file" in
38
+ *.md|LICENSE|.github/ISSUE_TEMPLATE/*) ;;
39
+ *) code=true ;;
40
+ esac
41
+ done <<< "$files"
42
+ echo "code=$code" >> "$GITHUB_OUTPUT"
43
+
14
44
  test:
45
+ needs: changes
46
+ if: needs.changes.outputs.code == 'true'
15
47
  strategy:
16
48
  fail-fast: false
17
49
  matrix:
@@ -44,6 +76,8 @@ jobs:
44
76
  report_paths: tests.xml
45
77
 
46
78
  typecheck:
79
+ needs: changes
80
+ if: needs.changes.outputs.code == 'true'
47
81
  strategy:
48
82
  matrix:
49
83
  python-version: [ "3.12" ]
@@ -61,3 +95,18 @@ jobs:
61
95
  run: uv sync --locked --group test
62
96
  - name: Type checker
63
97
  run: uv run mypy prepare_assignment
98
+
99
+ # The only required check (next to the PR title): succeeds if all tests passed or were skipped because only
100
+ # documentation changed. Skipped matrix jobs don't report their individual checks (e.g. 'test (3.12, ...)').
101
+ tests:
102
+ if: always()
103
+ needs: [changes, test, typecheck]
104
+ runs-on: ubuntu-latest
105
+ steps:
106
+ - name: All tests passed or were skipped
107
+ env:
108
+ FAILED: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}
109
+ RESULTS: ${{ toJSON(needs) }}
110
+ run: |
111
+ echo "$RESULTS"
112
+ [ "$FAILED" = "false" ]
@@ -0,0 +1,9 @@
1
+ {
2
+ "branches": ["main"],
3
+ "tagFormat": "v${version}",
4
+ "plugins": [
5
+ ["@semantic-release/commit-analyzer", { "preset": "conventionalcommits" }],
6
+ ["@semantic-release/release-notes-generator", { "preset": "conventionalcommits" }],
7
+ "@semantic-release/github"
8
+ ]
9
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: prepare-assignment
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: Prepare assignment
5
5
  Author-email: Bonajo <m.bonajo@fontys.nl>
6
6
  License: MIT
@@ -11,7 +11,7 @@ Requires-Dist: gitpython>=3.1.59
11
11
  Requires-Dist: jsonschema>=4.21.1
12
12
  Requires-Dist: multipledispatch>=1.0.0
13
13
  Requires-Dist: packaging>=24.0
14
- Requires-Dist: prepare-toolbox==0.4.0
14
+ Requires-Dist: prepare-toolbox>=1.0.1
15
15
  Requires-Dist: ruamel-yaml>=0.18.5
16
16
  Requires-Dist: simpleeval>=1.0.0
17
17
  Requires-Dist: treelib>=1.7.0
@@ -30,7 +30,7 @@ Prepare assignment is a GitHub Actions inspired helper tool to prepare assignmen
30
30
 
31
31
  ## Installation
32
32
 
33
- Prepare-assignment is available from [PyPI](https://pypi.org/).
33
+ Prepare-assignment is available from [PyPI](https://pypi.org/project/prepare-assignment/).
34
34
 
35
35
 
36
36
  ```bash
@@ -47,13 +47,13 @@ To execute a `prepare.yml` simply run `prepare run` from the same directory.
47
47
 
48
48
  ### Command line interface
49
49
 
50
- Use `prepare --help` to which commands and flags are available.
50
+ Use `prepare --help` to see which commands and flags are available.
51
51
 
52
52
  ## Example `prepare.yml`
53
53
 
54
54
  First we need to have tasks available that can be executed. Take for example a look at the [remove](https://github.com/prepare-assignment/remove) task.
55
55
 
56
- The tests use a [testproject](https://github.com/prepare-assignment/core/tree/tests/testproject), which contains an example of a `prepare.yml`, see below for convenience.
56
+ The tests use a [testproject](https://github.com/prepare-assignment/core/tree/main/tests/testproject), which contains an example of a `prepare.yml`, see below for convenience.
57
57
 
58
58
  ```yaml
59
59
  name: Test project
@@ -200,9 +200,9 @@ Releases are automated with [semantic-release](https://semantic-release.gitbook.
200
200
 
201
201
  | PR title | Release |
202
202
  |----------|---------|
203
- | `fix: ...` | patch (1.2.3 → 1.2.4) |
203
+ | `fix: ...`, `perf: ...` | patch (1.2.3 → 1.2.4) |
204
204
  | `feat: ...` | minor (1.2.3 → 1.3.0) |
205
- | `feat!: ...` or a `BREAKING CHANGE:` footer | major (1.2.3 → 2.0.0) |
206
- | `docs:`, `chore:`, `ci:`, `build:`, `refactor:`, `test:`, `style:`, `perf:` | no release |
205
+ | `!` after the type (e.g. `feat!: ...`, `refactor!: ...`) or a `BREAKING CHANGE:` footer | major (1.2.3 → 2.0.0) |
206
+ | `docs:`, `chore:`, `ci:`, `build:`, `refactor:`, `test:`, `style:`, `revert:` | no release |
207
207
 
208
208
  On every merge to `main` the next version is determined, tagged (`vX.Y.Z`), a GitHub release is created and the package is published to PyPI. The version is set during the build and is not committed, so the version in `pyproject.toml` is not the released version.
@@ -9,7 +9,7 @@ Prepare assignment is a GitHub Actions inspired helper tool to prepare assignmen
9
9
 
10
10
  ## Installation
11
11
 
12
- Prepare-assignment is available from [PyPI](https://pypi.org/).
12
+ Prepare-assignment is available from [PyPI](https://pypi.org/project/prepare-assignment/).
13
13
 
14
14
 
15
15
  ```bash
@@ -26,13 +26,13 @@ To execute a `prepare.yml` simply run `prepare run` from the same directory.
26
26
 
27
27
  ### Command line interface
28
28
 
29
- Use `prepare --help` to which commands and flags are available.
29
+ Use `prepare --help` to see which commands and flags are available.
30
30
 
31
31
  ## Example `prepare.yml`
32
32
 
33
33
  First we need to have tasks available that can be executed. Take for example a look at the [remove](https://github.com/prepare-assignment/remove) task.
34
34
 
35
- The tests use a [testproject](https://github.com/prepare-assignment/core/tree/tests/testproject), which contains an example of a `prepare.yml`, see below for convenience.
35
+ The tests use a [testproject](https://github.com/prepare-assignment/core/tree/main/tests/testproject), which contains an example of a `prepare.yml`, see below for convenience.
36
36
 
37
37
  ```yaml
38
38
  name: Test project
@@ -179,9 +179,9 @@ Releases are automated with [semantic-release](https://semantic-release.gitbook.
179
179
 
180
180
  | PR title | Release |
181
181
  |----------|---------|
182
- | `fix: ...` | patch (1.2.3 → 1.2.4) |
182
+ | `fix: ...`, `perf: ...` | patch (1.2.3 → 1.2.4) |
183
183
  | `feat: ...` | minor (1.2.3 → 1.3.0) |
184
- | `feat!: ...` or a `BREAKING CHANGE:` footer | major (1.2.3 → 2.0.0) |
185
- | `docs:`, `chore:`, `ci:`, `build:`, `refactor:`, `test:`, `style:`, `perf:` | no release |
184
+ | `!` after the type (e.g. `feat!: ...`, `refactor!: ...`) or a `BREAKING CHANGE:` footer | major (1.2.3 → 2.0.0) |
185
+ | `docs:`, `chore:`, `ci:`, `build:`, `refactor:`, `test:`, `style:`, `revert:` | no release |
186
186
 
187
187
  On every merge to `main` the next version is determined, tagged (`vX.Y.Z`), a GitHub release is created and the package is published to PyPI. The version is set during the build and is not committed, so the version in `pyproject.toml` is not the released version.
@@ -1,10 +1,9 @@
1
1
  import os.path
2
2
  import sys
3
3
  from pathlib import Path
4
- from typing import Dict, List, Optional
4
+ from typing import Annotated, Dict, List, Optional
5
5
 
6
6
  import typer
7
- from typing_extensions import Annotated
8
7
 
9
8
  from prepare_assignment import __version__
10
9
  from prepare_assignment.cli.task import app as task_app
@@ -1,8 +1,7 @@
1
1
  import logging
2
- from typing import Optional
2
+ from typing import Annotated, Optional
3
3
 
4
4
  import typer
5
- from typing_extensions import Annotated
6
5
 
7
6
  from prepare_assignment.core.task_handler import info, remove, update, add
8
7
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "prepare-assignment"
3
- version = "0.4.0"
3
+ version = "0.4.2"
4
4
  description = "Prepare assignment"
5
5
  authors = [{name = "Bonajo", email = "m.bonajo@fontys.nl"}]
6
6
  license = {text = "MIT"}
@@ -10,7 +10,7 @@ dependencies = [
10
10
  "ruamel-yaml>=0.18.5",
11
11
  "jsonschema>=4.21.1",
12
12
  "gitpython>=3.1.59",
13
- "prepare-toolbox==0.4.0",
13
+ "prepare-toolbox>=1.0.1",
14
14
  "virtualenv>=20.25.0",
15
15
  "typer==0.27.2",
16
16
  "treelib>=1.7.0",
@@ -178,3 +178,20 @@ def test_handle_set_output_wrong_item_type(caplog: pytest.LogCaptureFixture) ->
178
178
  current_task_definition=definition, current_task=task)
179
179
  handle_set_output(fresh_env, ["", json.dumps({"l": ["a", 1]})])
180
180
  assert 'l' not in fresh_env.outputs['id']
181
+
182
+
183
+ def test_toolbox_set_output_with_separator_contract(capsys: pytest.CaptureFixture) -> None:
184
+ """Values containing the separator (escaped by prepare-toolbox >= 1.0.1) must arrive unchanged."""
185
+ from pathlib import Path
186
+ from prepare_toolbox.core import set_output
187
+ from prepare_assignment.core import runner
188
+ set_output("files", ["a:PA:b.txt", Path("src") / "A.java"])
189
+ line = capsys.readouterr().out
190
+ output_definition = TaskOutputDefinition(description="files", type="array", items="string")
191
+ definition = PythonTaskDefinition(outputs={"files": output_definition}, description="t", inputs=[], id="t",
192
+ name="t", path="path", main="main.py") # type: ignore
193
+ fresh_env = JobEnvironment(environment={}, outputs={"id": {}}, inputs={},
194
+ current_task_definition=definition, current_task=task)
195
+ runner.__process_output_line(line, fresh_env) # type: ignore[attr-defined]
196
+ assert fresh_env.task_errors == []
197
+ assert fresh_env.outputs["id"]["files"] == ["a:PA:b.txt", str(Path("src") / "A.java")]
@@ -0,0 +1,48 @@
1
+ import ast
2
+ import sys
3
+ from importlib.metadata import packages_distributions, requires
4
+ from pathlib import Path
5
+ from typing import Set
6
+
7
+ from packaging.requirements import Requirement
8
+ from packaging.utils import canonicalize_name
9
+
10
+ PACKAGE = Path(__file__).parent.parent / "prepare_assignment"
11
+
12
+
13
+ def _imported_modules() -> Set[str]:
14
+ modules: Set[str] = set()
15
+ for file in PACKAGE.rglob("*.py"):
16
+ tree = ast.parse(file.read_text(encoding="utf-8"))
17
+ for node in ast.walk(tree):
18
+ if isinstance(node, ast.Import):
19
+ modules.update(alias.name.split(".")[0] for alias in node.names)
20
+ elif isinstance(node, ast.ImportFrom) and node.level == 0 and node.module is not None:
21
+ modules.add(node.module.split(".")[0])
22
+ return {module for module in modules
23
+ if module not in sys.stdlib_module_names and module != "prepare_assignment"}
24
+
25
+
26
+ def _declared_dependencies() -> Set[str]:
27
+ declared: Set[str] = set()
28
+ for line in requires("prepare-assignment") or []:
29
+ requirement = Requirement(line)
30
+ # Skip optional dependencies (extras)
31
+ if requirement.marker is None or requirement.marker.evaluate({"extra": ""}):
32
+ declared.add(canonicalize_name(requirement.name))
33
+ return declared
34
+
35
+
36
+ def test_all_imports_are_declared_dependencies() -> None:
37
+ """
38
+ A module that is only installed as a dependency of a (test) dependency works in development, but not for users.
39
+ typer 0.27 dropped its typing-extensions dependency, which broke the released 0.4.0.
40
+ """
41
+ distributions = packages_distributions()
42
+ declared = _declared_dependencies()
43
+ undeclared = {}
44
+ for module in sorted(_imported_modules()):
45
+ names = {canonicalize_name(name) for name in distributions.get(module, [])}
46
+ if not names & declared:
47
+ undeclared[module] = sorted(names) or ["not installed"]
48
+ assert undeclared == {}, f"Imported, but not declared in pyproject.toml dependencies: {undeclared}"
@@ -86,3 +86,13 @@ def test_multiple_prepare_files_message(monkeypatch: pytest.MonkeyPatch, tmp_pat
86
86
  with pytest.raises(AssertionError) as exc:
87
87
  prepare(None)
88
88
  assert "both a prepare.yml and a prepare.yaml" in str(exc.value)
89
+
90
+
91
+ def test_prepare_file_in_directory_with_special_characters(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
92
+ """With prepare-toolbox < 0.4.4 the directory name was interpreted as part of the glob"""
93
+ from prepare_assignment.core.main import get_prepare_file
94
+ project = tmp_path / "[week 1] assignment"
95
+ project.mkdir()
96
+ (project / "prepare.yml").write_text("name: a\njobs: {}\n")
97
+ monkeypatch.chdir(project)
98
+ assert get_prepare_file(None) == "prepare.yml"
@@ -582,7 +582,7 @@ requires-dist = [
582
582
  { name = "jsonschema", specifier = ">=4.21.1" },
583
583
  { name = "multipledispatch", specifier = ">=1.0.0" },
584
584
  { name = "packaging", specifier = ">=24.0" },
585
- { name = "prepare-toolbox", specifier = "==0.4.0" },
585
+ { name = "prepare-toolbox", specifier = ">=1.0.1" },
586
586
  { name = "ruamel-yaml", specifier = ">=0.18.5" },
587
587
  { name = "simpleeval", specifier = ">=1.0.0" },
588
588
  { name = "treelib", specifier = ">=1.7.0" },
@@ -601,14 +601,14 @@ test = [
601
601
 
602
602
  [[package]]
603
603
  name = "prepare-toolbox"
604
- version = "0.4.0"
604
+ version = "1.0.1"
605
605
  source = { registry = "https://pypi.org/simple" }
606
606
  dependencies = [
607
607
  { name = "braceexpand" },
608
608
  ]
609
- sdist = { url = "https://files.pythonhosted.org/packages/1d/c3/b6d5ad3819fa8f9149a3cbd3653dbcb9208103f91fec3a379d528602e7eb/prepare_toolbox-0.4.0.tar.gz", hash = "sha256:825f50b9bccefcaf091d0e29b7881c311ce7c9a2f510ec481951beffd29871b9", size = 4952, upload-time = "2023-11-01T19:10:30.85Z" }
609
+ sdist = { url = "https://files.pythonhosted.org/packages/80/67/85a18c89af943534c23ce825de8e6e17550481f8c90e02db1c3a2d8a865e/prepare_toolbox-1.0.1.tar.gz", hash = "sha256:c11f499018b181ae4bc548669de698ee50dfbd309d9080e9538920c046798959", size = 55292, upload-time = "2026-09-22T06:15:35.074Z" }
610
610
  wheels = [
611
- { url = "https://files.pythonhosted.org/packages/81/3d/9569c6585515e637f32e3ca691ce5df85a01903ca7aa0ce9e4f75580640e/prepare_toolbox-0.4.0-py3-none-any.whl", hash = "sha256:15f694af2883463ec83cb568885f85bca4a4225816d86846b296da69c027c5e8", size = 6836, upload-time = "2023-11-01T19:10:29.84Z" },
611
+ { url = "https://files.pythonhosted.org/packages/b4/35/dd4cacca4d58361b66394160f9ca0f4062c2b5e55c0a8aa1dc557d347a63/prepare_toolbox-1.0.1-py3-none-any.whl", hash = "sha256:41e10ac9fa61e0cfe53d5c04b69baf2b0ee8a607dda2c9825c6ce0293055fd57", size = 7443, upload-time = "2026-09-22T06:15:34.015Z" },
612
612
  ]
613
613
 
614
614
  [[package]]
@@ -1,9 +0,0 @@
1
- {
2
- "branches": ["main"],
3
- "tagFormat": "v${version}",
4
- "plugins": [
5
- "@semantic-release/commit-analyzer",
6
- "@semantic-release/release-notes-generator",
7
- "@semantic-release/github"
8
- ]
9
- }