codepraxis 0.3.2__tar.gz → 0.3.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.
- {codepraxis-0.3.2 → codepraxis-0.3.3}/PKG-INFO +27 -1
- {codepraxis-0.3.2 → codepraxis-0.3.3}/README.md +26 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/__init__.py +1 -1
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/cli.py +17 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/commands/catalog.py +33 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/commands/publish.py +23 -14
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/execution/remote/client.py +3 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/tests/test_publish.py +90 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/.gitignore +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/CONTRIBUTING.md +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/RELEASING.md +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/pyproject.toml +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/__main__.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/commands/__init__.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/commands/example.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/commands/lint.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/commands/login.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/commands/validate.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/domain/__init__.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/domain/contract.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/domain/pack.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/domain/results.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/errors.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/execution/__init__.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/execution/executor.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/execution/local/__init__.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/execution/local/backends.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/execution/local/executor.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/execution/local/worker.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/execution/local/workspace.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/execution/remote/__init__.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/execution/remote/config.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/execution/remote/executor.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/packio/__init__.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/packio/archive.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/packio/discovery.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/packio/loader.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/packio/toc.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/plugin/__init__.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/plugin/installer.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/plugin/templates/commands/new.md +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/plugin/templates/commands/validate.md +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/plugin/templates/marketplace.json +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/plugin/templates/plugin.json +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/plugin/templates/skills/pack-authoring/SKILL.md +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/reporting/__init__.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/reporting/human.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/reporting/json_reporter.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/reporting/reporter.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/scaffold/__init__.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/scaffold/generator.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/scaffold/templates/README.md +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/scaffold/templates/backend.conf +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/scaffold/templates/course_toc.json +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/scaffold/templates/feature.md +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/scaffold/templates/main.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/scaffold/templates/metadata.json +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/scaffold/templates/solution.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/scaffold/templates/test_1.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/validation/__init__.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/validation/registry.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/validation/rule.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/validation/rules/__init__.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/validation/rules/hygiene.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/validation/rules/instructions.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/validation/rules/testcases.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/tests/conformance/test_corpus.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/tests/conftest.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/tests/test_catalog.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/tests/test_classification.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/tests/test_toc.py +0 -0
- {codepraxis-0.3.2 → codepraxis-0.3.3}/tests/test_validation_rules.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codepraxis
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: Author and validate CodePraxis challenge packs.
|
|
5
5
|
Project-URL: Documentation, https://docs.codepraxis.com/authoring
|
|
6
6
|
Author: CodePraxis
|
|
@@ -37,6 +37,7 @@ codepraxis --login
|
|
|
37
37
|
codepraxis --publish my-challenge # validates in the runner, then publishes
|
|
38
38
|
codepraxis --list # list company questions
|
|
39
39
|
codepraxis --edit 123 --open # update metadata/open a preview container
|
|
40
|
+
codepraxis --publish my-challenge --challenge-id 123 # publish an edit, not a duplicate
|
|
40
41
|
```
|
|
41
42
|
|
|
42
43
|
## What this is
|
|
@@ -214,6 +215,31 @@ codepraxis --edit 123 --open
|
|
|
214
215
|
|
|
215
216
|
`--list` shows the questions owned by your API key's company, including drafts.
|
|
216
217
|
`--edit` updates question metadata and returns a container URL for inspection.
|
|
218
|
+
|
|
219
|
+
### Publishing an edit
|
|
220
|
+
|
|
221
|
+
Changing the *content* of a question means re-publishing the pack. Pass the
|
|
222
|
+
question's id, or you get a second copy:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
codepraxis --publish my-challenge --challenge-id 123
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
With `--challenge-id` the platform adds a new version to that question and
|
|
229
|
+
keeps its id, assignments and history. Without it, publish always creates a
|
|
230
|
+
new question — which is the right behaviour the first time and a duplicate
|
|
231
|
+
every time after.
|
|
232
|
+
|
|
233
|
+
### Deleting
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
codepraxis --delete 123
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Asks first, and the platform refuses outright once the question has been
|
|
240
|
+
assigned to anyone — deleting it then would orphan attempts and reports that
|
|
241
|
+
candidates and reviewers still depend on. To take an assigned question out of
|
|
242
|
+
circulation, set it back to draft with `--edit 123 --status draft`.
|
|
217
243
|
Code changes still go through the pack workflow: edit locally, validate, and
|
|
218
244
|
publish a new version.
|
|
219
245
|
|
|
@@ -12,6 +12,7 @@ codepraxis --login
|
|
|
12
12
|
codepraxis --publish my-challenge # validates in the runner, then publishes
|
|
13
13
|
codepraxis --list # list company questions
|
|
14
14
|
codepraxis --edit 123 --open # update metadata/open a preview container
|
|
15
|
+
codepraxis --publish my-challenge --challenge-id 123 # publish an edit, not a duplicate
|
|
15
16
|
```
|
|
16
17
|
|
|
17
18
|
## What this is
|
|
@@ -189,6 +190,31 @@ codepraxis --edit 123 --open
|
|
|
189
190
|
|
|
190
191
|
`--list` shows the questions owned by your API key's company, including drafts.
|
|
191
192
|
`--edit` updates question metadata and returns a container URL for inspection.
|
|
193
|
+
|
|
194
|
+
### Publishing an edit
|
|
195
|
+
|
|
196
|
+
Changing the *content* of a question means re-publishing the pack. Pass the
|
|
197
|
+
question's id, or you get a second copy:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
codepraxis --publish my-challenge --challenge-id 123
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
With `--challenge-id` the platform adds a new version to that question and
|
|
204
|
+
keeps its id, assignments and history. Without it, publish always creates a
|
|
205
|
+
new question — which is the right behaviour the first time and a duplicate
|
|
206
|
+
every time after.
|
|
207
|
+
|
|
208
|
+
### Deleting
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
codepraxis --delete 123
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Asks first, and the platform refuses outright once the question has been
|
|
215
|
+
assigned to anyone — deleting it then would orphan attempts and reports that
|
|
216
|
+
candidates and reviewers still depend on. To take an assigned question out of
|
|
217
|
+
circulation, set it back to draft with `--edit 123 --status draft`.
|
|
192
218
|
Code changes still go through the pack workflow: edit locally, validate, and
|
|
193
219
|
publish a new version.
|
|
194
220
|
|
|
@@ -71,6 +71,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
71
71
|
metavar="CHALLENGE_ID",
|
|
72
72
|
help="Edit question metadata and open its container preview.",
|
|
73
73
|
)
|
|
74
|
+
actions.add_argument(
|
|
75
|
+
"--delete",
|
|
76
|
+
type=int,
|
|
77
|
+
metavar="CHALLENGE_ID",
|
|
78
|
+
help="Delete a question your company owns. Refused once it has been assigned.",
|
|
79
|
+
)
|
|
74
80
|
actions.add_argument(
|
|
75
81
|
"--install",
|
|
76
82
|
choices=INSTALLABLES,
|
|
@@ -96,6 +102,14 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
96
102
|
action="store_true",
|
|
97
103
|
help="With --publish, publish straight to candidates instead of as a draft.",
|
|
98
104
|
)
|
|
105
|
+
parser.add_argument(
|
|
106
|
+
"--challenge-id",
|
|
107
|
+
type=int,
|
|
108
|
+
help=(
|
|
109
|
+
"With --publish, publish a new version of this existing question "
|
|
110
|
+
"instead of creating another one. Get the id from --list."
|
|
111
|
+
),
|
|
112
|
+
)
|
|
99
113
|
parser.add_argument(
|
|
100
114
|
"--validation-run-id",
|
|
101
115
|
help="With --publish, reuse a passing validation run instead of running a new one.",
|
|
@@ -316,6 +330,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
316
330
|
assume_yes=args.yes,
|
|
317
331
|
live=args.live,
|
|
318
332
|
validation_run_id=args.validation_run_id,
|
|
333
|
+
challenge_id=args.challenge_id,
|
|
319
334
|
)
|
|
320
335
|
if args.list:
|
|
321
336
|
return catalog_command.list_questions(as_json=args.json)
|
|
@@ -325,6 +340,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
325
340
|
updates=_edit_updates(args),
|
|
326
341
|
open_browser=args.open_browser,
|
|
327
342
|
)
|
|
343
|
+
if args.delete is not None:
|
|
344
|
+
return catalog_command.delete_question(args.delete, assume_yes=args.yes)
|
|
328
345
|
if args.install:
|
|
329
346
|
return _handle_install(args.install, args.force)
|
|
330
347
|
if args.example:
|
|
@@ -11,6 +11,7 @@ from ..execution.remote.client import ApiClient
|
|
|
11
11
|
from ..execution.remote.config import RemoteConfig
|
|
12
12
|
|
|
13
13
|
EXIT_OK = 0
|
|
14
|
+
EXIT_ABORTED = 2
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
def list_questions(client: ApiClient | None = None, as_json: bool = False) -> int:
|
|
@@ -79,3 +80,35 @@ def _print_challenge(challenge: dict[str, Any]) -> None:
|
|
|
79
80
|
if updated:
|
|
80
81
|
line += f"\t{updated}"
|
|
81
82
|
print(line)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def delete_question(
|
|
86
|
+
challenge_id: int,
|
|
87
|
+
client: ApiClient | None = None,
|
|
88
|
+
assume_yes: bool = False,
|
|
89
|
+
) -> int:
|
|
90
|
+
"""Delete a question the company owns.
|
|
91
|
+
|
|
92
|
+
Confirmed by default: unlike a draft, this is not recoverable from the
|
|
93
|
+
dashboard. The platform refuses outright once the question has been assigned
|
|
94
|
+
to anyone, so the destructive case this guards is an unassigned question the
|
|
95
|
+
author still wanted.
|
|
96
|
+
"""
|
|
97
|
+
import sys
|
|
98
|
+
|
|
99
|
+
client = client or ApiClient(RemoteConfig.resolve())
|
|
100
|
+
|
|
101
|
+
if not assume_yes:
|
|
102
|
+
if not sys.stdin.isatty():
|
|
103
|
+
raise PraxisError(
|
|
104
|
+
"Deleting needs confirmation, but stdin is not a terminal. Pass --yes."
|
|
105
|
+
)
|
|
106
|
+
print(f"Permanently delete question {challenge_id}? This cannot be undone.")
|
|
107
|
+
if input("Continue? [y/N] ").strip().lower() not in {"y", "yes"}:
|
|
108
|
+
print("Aborted.", file=sys.stderr)
|
|
109
|
+
return EXIT_ABORTED
|
|
110
|
+
|
|
111
|
+
payload = client.delete(f"/challenges/{challenge_id}")
|
|
112
|
+
removed = payload.get("versions_removed", 0)
|
|
113
|
+
print(f"Deleted question {challenge_id} ({removed} version(s) removed).")
|
|
114
|
+
return EXIT_OK
|
|
@@ -40,6 +40,7 @@ def run(
|
|
|
40
40
|
assume_yes: bool = False,
|
|
41
41
|
live: bool = False,
|
|
42
42
|
validation_run_id: str | None = None,
|
|
43
|
+
challenge_id: int | None = None,
|
|
43
44
|
client: ApiClient | None = None,
|
|
44
45
|
) -> int:
|
|
45
46
|
if not selector:
|
|
@@ -68,29 +69,36 @@ def run(
|
|
|
68
69
|
run_id = _validate(pack, client, reporter)
|
|
69
70
|
|
|
70
71
|
company = _company_name(client, config)
|
|
71
|
-
if not _confirm(pack.name, company, live, assume_yes):
|
|
72
|
+
if not _confirm(pack.name, company, live, assume_yes, challenge_id):
|
|
72
73
|
print("Aborted.", file=sys.stderr)
|
|
73
74
|
return EXIT_ABORTED
|
|
74
75
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
76
|
+
body = {
|
|
77
|
+
# No company_id: the server derives ownership from the API key.
|
|
78
|
+
"name": pack.name,
|
|
79
|
+
"validation_run_id": run_id,
|
|
80
|
+
"status": "published" if live else "draft",
|
|
81
|
+
}
|
|
82
|
+
# With an id, this publishes a new version of an existing question. Without
|
|
83
|
+
# one the platform creates a new question — which is why editing a pack and
|
|
84
|
+
# re-publishing used to leave a duplicate behind.
|
|
85
|
+
if challenge_id is not None:
|
|
86
|
+
body["challenge_id"] = challenge_id
|
|
87
|
+
|
|
88
|
+
payload = client.post_json("/challenges", body)
|
|
84
89
|
|
|
85
90
|
challenge_id = payload.get("challenge_id")
|
|
86
91
|
version_id = payload.get("challenge_version_id")
|
|
87
92
|
container_url = payload.get("container_url")
|
|
88
93
|
preview_error = payload.get("preview_error")
|
|
89
94
|
state = "published" if live else "draft"
|
|
95
|
+
created = payload.get("created", True)
|
|
90
96
|
|
|
91
|
-
|
|
97
|
+
verb = "published" if created else "updated"
|
|
98
|
+
print(f"{pack.name} {verb} for {company} ({state})")
|
|
92
99
|
if challenge_id:
|
|
93
|
-
|
|
100
|
+
label = "challenge" if created else "challenge (new version of)"
|
|
101
|
+
print(f" {label} {challenge_id}, version {version_id}")
|
|
94
102
|
if container_url:
|
|
95
103
|
print(f" {container_url}")
|
|
96
104
|
elif preview_error:
|
|
@@ -129,7 +137,7 @@ def _company_name(client: ApiClient, config: RemoteConfig | None) -> str:
|
|
|
129
137
|
return company.get("name") or cached or "your company"
|
|
130
138
|
|
|
131
139
|
|
|
132
|
-
def _confirm(pack_name: str, company: str, live: bool, assume_yes: bool) -> bool:
|
|
140
|
+
def _confirm(pack_name: str, company: str, live: bool, assume_yes: bool, challenge_id: int | None = None) -> bool:
|
|
133
141
|
if assume_yes:
|
|
134
142
|
return True
|
|
135
143
|
if not sys.stdin.isatty():
|
|
@@ -138,5 +146,6 @@ def _confirm(pack_name: str, company: str, live: bool, assume_yes: bool) -> bool
|
|
|
138
146
|
"Pass --yes to publish non-interactively (CI)."
|
|
139
147
|
)
|
|
140
148
|
visibility = "LIVE to candidates" if live else "as a draft"
|
|
141
|
-
|
|
149
|
+
target = f"as a new version of question {challenge_id}" if challenge_id else "as a new question"
|
|
150
|
+
print(f"\nAbout to publish {pack_name} to {company} {target}, {visibility}.")
|
|
142
151
|
return input("Continue? [y/N] ").strip().lower() in {"y", "yes"}
|
|
@@ -125,6 +125,9 @@ class ApiClient:
|
|
|
125
125
|
content_type="application/json",
|
|
126
126
|
)
|
|
127
127
|
|
|
128
|
+
def delete(self, path: str) -> dict[str, Any]:
|
|
129
|
+
return self.request("DELETE", path)
|
|
130
|
+
|
|
128
131
|
def post_bytes(self, path: str, blob: bytes) -> dict[str, Any]:
|
|
129
132
|
return self.request("POST", path, body=blob, content_type="application/zip")
|
|
130
133
|
|
|
@@ -227,3 +227,93 @@ def test_prints_container_url_when_platform_returns_one(tmp_path: Path, capsys):
|
|
|
227
227
|
)
|
|
228
228
|
|
|
229
229
|
assert "https://container.example" in capsys.readouterr().out
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class TestRepublishingAnEdit:
|
|
233
|
+
"""Editing a pack and publishing again must update, not duplicate.
|
|
234
|
+
|
|
235
|
+
Without a challenge_id the platform creates a new question every time, which
|
|
236
|
+
is how a typo fix used to leave two copies in the catalog.
|
|
237
|
+
"""
|
|
238
|
+
|
|
239
|
+
def test_challenge_id_is_sent_when_given(self, tmp_path):
|
|
240
|
+
build_pack(tmp_path)
|
|
241
|
+
client = FakeClient()
|
|
242
|
+
|
|
243
|
+
publish.run(
|
|
244
|
+
root=tmp_path,
|
|
245
|
+
selector="demo",
|
|
246
|
+
reporter=NullReporter(),
|
|
247
|
+
assume_yes=True,
|
|
248
|
+
validation_run_id="vr_1",
|
|
249
|
+
challenge_id=64,
|
|
250
|
+
client=client,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
assert client.posted[0][1]["challenge_id"] == 64
|
|
254
|
+
|
|
255
|
+
def test_no_challenge_id_means_create(self, tmp_path):
|
|
256
|
+
build_pack(tmp_path)
|
|
257
|
+
client = FakeClient()
|
|
258
|
+
|
|
259
|
+
publish.run(
|
|
260
|
+
root=tmp_path,
|
|
261
|
+
selector="demo",
|
|
262
|
+
reporter=NullReporter(),
|
|
263
|
+
assume_yes=True,
|
|
264
|
+
validation_run_id="vr_1",
|
|
265
|
+
client=client,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
assert "challenge_id" not in client.posted[0][1]
|
|
269
|
+
|
|
270
|
+
def test_output_says_updated_when_the_platform_reused_a_question(self, tmp_path, capsys):
|
|
271
|
+
build_pack(tmp_path)
|
|
272
|
+
client = FakeClient()
|
|
273
|
+
client.post_json = lambda path, payload: {
|
|
274
|
+
"challenge_id": 64,
|
|
275
|
+
"challenge_version_id": 91,
|
|
276
|
+
"created": False,
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
publish.run(
|
|
280
|
+
root=tmp_path,
|
|
281
|
+
selector="demo",
|
|
282
|
+
reporter=NullReporter(),
|
|
283
|
+
assume_yes=True,
|
|
284
|
+
validation_run_id="vr_1",
|
|
285
|
+
challenge_id=64,
|
|
286
|
+
client=client,
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
output = capsys.readouterr().out
|
|
290
|
+
assert "updated" in output
|
|
291
|
+
assert "published for" not in output
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
class TestDelete:
|
|
295
|
+
class Client:
|
|
296
|
+
def __init__(self, payload=None):
|
|
297
|
+
self.deleted = []
|
|
298
|
+
self.payload = payload or {"challenge_id": 7, "versions_removed": 2}
|
|
299
|
+
|
|
300
|
+
def delete(self, path):
|
|
301
|
+
self.deleted.append(path)
|
|
302
|
+
return self.payload
|
|
303
|
+
|
|
304
|
+
def test_deletes_after_confirmation_is_waived(self, capsys):
|
|
305
|
+
from codepraxis.commands import catalog
|
|
306
|
+
|
|
307
|
+
client = self.Client()
|
|
308
|
+
assert catalog.delete_question(7, client=client, assume_yes=True) == 0
|
|
309
|
+
assert client.deleted == ["/challenges/7"]
|
|
310
|
+
assert "Deleted question 7" in capsys.readouterr().out
|
|
311
|
+
|
|
312
|
+
def test_refuses_without_a_terminal_and_without_yes(self):
|
|
313
|
+
from codepraxis.commands import catalog
|
|
314
|
+
from codepraxis.errors import PraxisError
|
|
315
|
+
|
|
316
|
+
client = self.Client()
|
|
317
|
+
with pytest.raises(PraxisError, match="not a terminal"):
|
|
318
|
+
catalog.delete_question(7, client=client)
|
|
319
|
+
assert client.deleted == []
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codepraxis-0.3.2 → codepraxis-0.3.3}/src/codepraxis/plugin/templates/skills/pack-authoring/SKILL.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|