prepare-assignment 0.4.1__tar.gz → 0.4.3__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.
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/.github/workflows/semantic-release.yml +8 -2
- prepare_assignment-0.4.3/.releaserc.json +9 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/PKG-INFO +8 -8
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/README.md +6 -6
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/core/preparer.py +11 -1
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/core/validator.py +24 -1
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/pyproject.toml +2 -2
- prepare_assignment-0.4.3/tests/conftest.py +25 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/core/test_command.py +17 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/core/test_preparer.py +34 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/core/test_validator.py +32 -1
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/test_main.py +10 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/uv.lock +4 -4
- prepare_assignment-0.4.1/.releaserc.json +0 -9
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/.github/dependabot.yml +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/.github/workflows/pr-title.yml +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/.github/workflows/pypi-publish.yml +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/.github/workflows/release.yml +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/.github/workflows/test.yml +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/.gitignore +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/LICENSE +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/__init__.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/__main__.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/cli/__init__.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/cli/main.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/cli/task.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/cli/tasks.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/core/__init__.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/core/check.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/core/command.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/core/expression.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/core/main.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/core/runner.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/core/shell.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/core/subsituter.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/core/task_handler.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/core/versions.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/data/__init__.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/data/config.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/data/constants.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/data/errors.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/data/job_environment.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/data/prepare.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/data/task_definition.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/data/task_properties.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/data/types.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/schemas/config.schema.json +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/schemas/prepare.schema.json +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/schemas/task.schema.json +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/utils/__init__.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/utils/config.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/utils/default_validator.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/utils/dependency.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/utils/files.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/utils/logger.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/utils/paths.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/utils/resources.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/utils/tasks.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/utils/virtual_env.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/utils/yml_loader.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/__init__.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/core/__init__.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/core/test_check.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/core/test_expression.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/core/test_resolve_version.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/core/test_runner.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/core/test_runner_process.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/core/test_shell.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/core/test_substituter.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/core/test_task_handler.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/data/__init__.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/data/test_prepare.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/data/test_types.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/test_dependencies.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/testproject/prepare.yml +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/testproject/solution/pom.xml +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/testproject/solution/src/main/java/gradelist/AssessmentResult.java +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/testproject/solution/src/main/java/gradelist/GradeList.java +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/testproject/solution/src/main/java/gradelist/Test.java +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/testproject/solution/src/test/java/gradelist/AssessmentResultTest.java +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/testproject/solution/src/test/java/gradelist/GradeListTest.java +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/utils/__init__.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/utils/test_config.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/utils/test_default_validator.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/utils/test_files.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/utils/test_logger.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/utils/test_paths.py +0 -0
- {prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/tests/utils/test_resources.py +0 -0
{prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/.github/workflows/semantic-release.yml
RENAMED
|
@@ -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.
|
|
42
|
-
#
|
|
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
|
|
|
@@ -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.
|
|
3
|
+
Version: 0.4.3
|
|
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
|
|
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:`, `
|
|
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:`, `
|
|
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.
|
|
@@ -12,7 +12,7 @@ from virtualenv import cli_run
|
|
|
12
12
|
|
|
13
13
|
from prepare_assignment.core.versions import COMMIT_HASH_RE, get_git_url, resolve_version
|
|
14
14
|
from prepare_assignment.core.validator import validate_task_definition, validate_tasks, load_yaml, \
|
|
15
|
-
validate_default_values
|
|
15
|
+
validate_default_values, validate_unique_ids
|
|
16
16
|
from prepare_assignment.data.errors import DependencyError, ValidationError, PrepareTaskError
|
|
17
17
|
from prepare_assignment.data.task_definition import TaskDefinition, CompositeTaskDefinition, \
|
|
18
18
|
PythonTaskDefinition, ValidableTask
|
|
@@ -154,10 +154,19 @@ def __sub_tasks(task: TaskDefinition) -> List[Any]:
|
|
|
154
154
|
return [step for step in task.tasks if step.get("uses", None) is not None]
|
|
155
155
|
|
|
156
156
|
|
|
157
|
+
def __validate_sub_task_ids(props: TaskProperties, task: TaskDefinition) -> None:
|
|
158
|
+
if isinstance(task, CompositeTaskDefinition):
|
|
159
|
+
validate_unique_ids(str(props.definition_path), f"task '{task.name}'", task.tasks)
|
|
160
|
+
|
|
161
|
+
|
|
157
162
|
def __load_task_from_disk(props: TaskProperties, parsed: Dict[str, ValidableTask]) -> None:
|
|
158
163
|
logger.debug(f"Task '{props}' is already available, loading from disk")
|
|
159
164
|
task_yaml = load_yaml(props.definition_path)
|
|
160
165
|
task = TaskDefinition.of(task_yaml, props.task_path)
|
|
166
|
+
try:
|
|
167
|
+
__validate_sub_task_ids(props, task)
|
|
168
|
+
except ValidationError as e:
|
|
169
|
+
raise PrepareTaskError(f"Unable to prepare task '{props}'", e) from e
|
|
161
170
|
# Always rebuild the schema, so schemas generated by older versions of prepare are updated
|
|
162
171
|
json_schema = __build_json_schema(props, task)
|
|
163
172
|
with open(__schema_path(props), "r", encoding="utf-8") as handle:
|
|
@@ -181,6 +190,7 @@ def __prepare_task(props: TaskProperties) -> ValidableTask:
|
|
|
181
190
|
task_yaml = validate_task_definition(props.definition_path)
|
|
182
191
|
task: TaskDefinition = TaskDefinition.of(task_yaml, props.task_path)
|
|
183
192
|
validate_default_values(task)
|
|
193
|
+
__validate_sub_task_ids(props, task)
|
|
184
194
|
if isinstance(task, PythonTaskDefinition):
|
|
185
195
|
main_path = os.path.join(props.repo_path, Path(task.main)) # type: ignore
|
|
186
196
|
if not Path(main_path).resolve().is_relative_to(props.repo_path.resolve()):
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import Dict, Any
|
|
6
|
+
from typing import Dict, Any, List
|
|
7
7
|
|
|
8
8
|
from jsonschema.exceptions import ValidationError
|
|
9
9
|
from jsonschema.validators import validate
|
|
@@ -37,9 +37,32 @@ def validate_prepare(prepare_file: str, prepare: Dict[str, Any]) -> None:
|
|
|
37
37
|
except ValidationError as ve:
|
|
38
38
|
message = f"Error in: {prepare_file}, unable to verify '{ve.json_path}'\n\t -> {ve.message}"
|
|
39
39
|
raise VE(message)
|
|
40
|
+
for job, steps in prepare["jobs"].items():
|
|
41
|
+
validate_unique_ids(prepare_file, f"job '{job}'", steps)
|
|
40
42
|
logger.debug("✓ Prepare file is valid")
|
|
41
43
|
|
|
42
44
|
|
|
45
|
+
def validate_unique_ids(file: str, where: str, steps: List[Dict[str, Any]]) -> None:
|
|
46
|
+
"""
|
|
47
|
+
Validate that the ids of the steps are unique, the outputs of a step are stored under its id
|
|
48
|
+
NOTE: steps without an id are not checked
|
|
49
|
+
:param file: path/name of the file that contains the steps
|
|
50
|
+
:param where: the job or composite task that contains the steps, used in the error message
|
|
51
|
+
:param steps: the steps of the job or composite task
|
|
52
|
+
:return: None
|
|
53
|
+
:raises: ValidationError: if multiple steps have the same id
|
|
54
|
+
"""
|
|
55
|
+
names: Dict[str, List[str]] = {}
|
|
56
|
+
for step in steps:
|
|
57
|
+
step_id = step.get("id", None)
|
|
58
|
+
if step_id is not None:
|
|
59
|
+
names.setdefault(step_id, []).append(step.get("name", ""))
|
|
60
|
+
for step_id, step_names in names.items():
|
|
61
|
+
if len(step_names) > 1:
|
|
62
|
+
quoted = ", ".join(f"'{name}'" for name in step_names)
|
|
63
|
+
raise VE(f"Error in: {file}, {where} has multiple steps with id '{step_id}': {quoted}")
|
|
64
|
+
|
|
65
|
+
|
|
43
66
|
def validate_tasks(file: str, task: Dict[str, Any], json_schema: Any) -> None:
|
|
44
67
|
"""
|
|
45
68
|
Validate all tasks based on their respective json schemas
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "prepare-assignment"
|
|
3
|
-
version = "0.4.
|
|
3
|
+
version = "0.4.3"
|
|
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
|
|
13
|
+
"prepare-toolbox>=1.0.1",
|
|
14
14
|
"virtualenv>=20.25.0",
|
|
15
15
|
"typer==0.27.2",
|
|
16
16
|
"treelib>=1.7.0",
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Iterator
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
# Loggers that prepare() configures (level, handlers), see prepare_assignment/core/main.py
|
|
7
|
+
_LOGGERS = ("prepare_assignment", "tasks")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.fixture(autouse=True)
|
|
11
|
+
def reset_loggers() -> Iterator[None]:
|
|
12
|
+
"""
|
|
13
|
+
Restore the prepare loggers after every test. prepare() sets their level (e.g. ERROR for verbosity 0), which made
|
|
14
|
+
tests that check log output with caplog fail when they ran after a test that calls prepare().
|
|
15
|
+
"""
|
|
16
|
+
saved = {}
|
|
17
|
+
for name in _LOGGERS:
|
|
18
|
+
logger = logging.getLogger(name)
|
|
19
|
+
saved[name] = (logger.level, list(logger.handlers), logger.propagate)
|
|
20
|
+
yield
|
|
21
|
+
for name, (level, handlers, propagate) in saved.items():
|
|
22
|
+
logger = logging.getLogger(name)
|
|
23
|
+
logger.setLevel(level)
|
|
24
|
+
logger.handlers[:] = handlers
|
|
25
|
+
logger.propagate = propagate
|
|
@@ -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")]
|
|
@@ -283,3 +283,37 @@ def test_outdated_schema_is_updated_on_load(mocker: MockerFixture) -> None:
|
|
|
283
283
|
spy.assert_not_called()
|
|
284
284
|
with open(schema_file) as handle:
|
|
285
285
|
assert "if" in json.load(handle)["properties"]
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
DUPLICATE_ID_COMPOSITE: Final[Dict[str, Any]] = {
|
|
289
|
+
'id': 'duplicate',
|
|
290
|
+
'name': 'duplicate',
|
|
291
|
+
'description': 'Composite task with a duplicate step id',
|
|
292
|
+
'runs': {
|
|
293
|
+
'using': 'composite',
|
|
294
|
+
'tasks': [{'name': 'first', 'id': 'same', 'run': 'echo 1'},
|
|
295
|
+
{'name': 'second', 'id': 'same', 'run': 'echo 2'}]
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def test_composite_duplicate_id_is_rejected(mocker: MockerFixture) -> None:
|
|
301
|
+
__clean_cache()
|
|
302
|
+
mocker.patch("prepare_assignment.core.preparer.__download_task")
|
|
303
|
+
mocker.patch("prepare_assignment.core.preparer.validate_task_definition", return_value=DUPLICATE_ID_COMPOSITE)
|
|
304
|
+
remove = mocker.patch("prepare_assignment.core.preparer.remove_tree")
|
|
305
|
+
prepare = {'prepare': [{'name': 'duplicate', 'uses': 'duplicate', 'with': {}}]}
|
|
306
|
+
with pytest.raises(PrepareTaskError) as exc:
|
|
307
|
+
prepare_tasks("prepare.yml", prepare)
|
|
308
|
+
assert "multiple steps with id 'same'" in str(exc.value.cause)
|
|
309
|
+
remove.assert_called()
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def test_cached_composite_duplicate_id_is_rejected(mocker: MockerFixture) -> None:
|
|
313
|
+
__clean_cache()
|
|
314
|
+
mocker.patch("prepare_assignment.core.preparer.__is_installed", return_value=True)
|
|
315
|
+
mocker.patch("prepare_assignment.core.preparer.load_yaml", return_value=DUPLICATE_ID_COMPOSITE)
|
|
316
|
+
prepare = {'prepare': [{'name': 'duplicate', 'uses': 'duplicate', 'with': {}}]}
|
|
317
|
+
with pytest.raises(PrepareTaskError) as exc:
|
|
318
|
+
prepare_tasks("prepare.yml", prepare)
|
|
319
|
+
assert "multiple steps with id 'same'" in str(exc.value.cause)
|
|
@@ -5,7 +5,8 @@ import pytest
|
|
|
5
5
|
from pytest_mock import MockerFixture
|
|
6
6
|
|
|
7
7
|
from prepare_assignment.core.validator import (validate_prepare, validate_tasks, load_yaml,
|
|
8
|
-
validate_task_definition, validate_default_values
|
|
8
|
+
validate_task_definition, validate_default_values,
|
|
9
|
+
validate_unique_ids)
|
|
9
10
|
from prepare_assignment.data.task_definition import PythonTaskDefinition
|
|
10
11
|
from prepare_assignment.data.errors import ValidationError
|
|
11
12
|
|
|
@@ -179,3 +180,33 @@ def test_validate_default_values_bool_for_integer_invalid() -> None:
|
|
|
179
180
|
task_copy["inputs"]["num"] = {'description': 'n', 'type': 'integer', 'default': True, 'required': False}
|
|
180
181
|
with pytest.raises(ValidationError):
|
|
181
182
|
validate_default_values(PythonTaskDefinition.of(task_copy, "test.yml"))
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def test_validate_unique_ids_valid() -> None:
|
|
186
|
+
steps = [{'name': 'a', 'id': 'a', 'run': 'echo'}, {'name': 'b', 'id': 'b', 'run': 'echo'},
|
|
187
|
+
{'name': 'no id', 'run': 'echo'}, {'name': 'no id', 'run': 'echo'}]
|
|
188
|
+
validate_unique_ids("prepare.yml", "job 'build'", steps)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def test_validate_unique_ids_duplicate() -> None:
|
|
192
|
+
steps = [{'name': 'Remove old artefacts', 'id': 'remove', 'uses': 'remove'},
|
|
193
|
+
{'name': 'other', 'id': 'other', 'uses': 'remove'},
|
|
194
|
+
{'name': 'Remove old temp directory', 'id': 'remove', 'uses': 'remove'}]
|
|
195
|
+
with pytest.raises(ValidationError) as exc:
|
|
196
|
+
validate_unique_ids("prepare.yml", "job 'build'", steps)
|
|
197
|
+
assert exc.value.message == ("Error in: prepare.yml, job 'build' has multiple steps with id 'remove': "
|
|
198
|
+
"'Remove old artefacts', 'Remove old temp directory'")
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def test_validate_prepare_duplicate_id() -> None:
|
|
202
|
+
prepare = {'name': 'Duplicate', 'jobs': {'build': [{'name': 'a', 'id': 'same', 'run': 'echo'},
|
|
203
|
+
{'name': 'b', 'id': 'same', 'run': 'echo'}]}}
|
|
204
|
+
with pytest.raises(ValidationError) as exc:
|
|
205
|
+
validate_prepare("prepare.yml", prepare)
|
|
206
|
+
assert "job 'build'" in exc.value.message
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def test_validate_prepare_same_id_in_different_jobs() -> None:
|
|
210
|
+
prepare = {'name': 'Different jobs', 'jobs': {'build': [{'name': 'a', 'id': 'same', 'run': 'echo'}],
|
|
211
|
+
'test': [{'name': 'b', 'id': 'same', 'run': 'echo'}]}}
|
|
212
|
+
validate_prepare("prepare.yml", prepare)
|
|
@@ -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 = "
|
|
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.
|
|
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/
|
|
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/
|
|
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]]
|
|
File without changes
|
{prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/.github/ISSUE_TEMPLATE/feature_request.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/core/task_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/data/job_environment.py
RENAMED
|
File without changes
|
|
File without changes
|
{prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/data/task_definition.py
RENAMED
|
File without changes
|
{prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/data/task_properties.py
RENAMED
|
File without changes
|
|
File without changes
|
{prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/schemas/config.schema.json
RENAMED
|
File without changes
|
{prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/schemas/prepare.schema.json
RENAMED
|
File without changes
|
{prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/schemas/task.schema.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/utils/default_validator.py
RENAMED
|
File without changes
|
{prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/utils/dependency.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/utils/virtual_env.py
RENAMED
|
File without changes
|
{prepare_assignment-0.4.1 → prepare_assignment-0.4.3}/prepare_assignment/utils/yml_loader.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|