phigrade 3.4.0__tar.gz → 3.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.
- {phigrade-3.4.0 → phigrade-3.4.2}/PKG-INFO +52 -28
- {phigrade-3.4.0 → phigrade-3.4.2}/README.md +51 -27
- {phigrade-3.4.0 → phigrade-3.4.2}/examples/colab/README.md +15 -5
- {phigrade-3.4.0 → phigrade-3.4.2}/examples/colab/lab1_instructor.ipynb +9 -4
- {phigrade-3.4.0 → phigrade-3.4.2}/examples/colab/lab1_student.ipynb +7 -6
- phigrade-3.4.2/examples/group/README.md +71 -0
- phigrade-3.4.2/examples/group/phigrade.yaml +61 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/phigrade/phigrade.py +203 -9
- phigrade-3.4.2/phigrade/pytest_plugin.py +90 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/pyproject.toml +1 -1
- {phigrade-3.4.0 → phigrade-3.4.2}/tests/test_notebook_ipytest.py +25 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/tests/test_phigrade_config.py +125 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/tests/test_submit_colab_notebook.py +83 -2
- {phigrade-3.4.0 → phigrade-3.4.2}/tests/test_teacher_mode_endpoints.py +159 -1
- phigrade-3.4.0/phigrade/pytest_plugin.py +0 -46
- {phigrade-3.4.0 → phigrade-3.4.2}/.gitignore +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/LICENSE +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/examples/colab/phigrade.yaml +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/examples/phigrade.yaml +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/examples/phigrade_db.json +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/examples/run_teacher_mode.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/examples/simplefns.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/examples/test_simplefns.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/phigrade/__init__.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/phigrade/app.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/phigrade/cli.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/phigrade/colab.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/phigrade/compare.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/phigrade/credentials.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/phigrade/errors.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/phigrade/gradescope_output.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/phigrade/messages.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/phigrade/server.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/tests/__init__.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/tests/conftest.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/tests/notebook_harness.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/tests/test_all_close_local.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/tests/test_cli.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/tests/test_client_setup_errors.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/tests/test_colab.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/tests/test_colab_api_key.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/tests/test_compare_keyvalue.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/tests/test_compare_rowmatch.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/tests/test_comparison_failure_message.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/tests/test_credentials.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/tests/test_errors.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/tests/test_failure_message_local.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/tests/test_gradescope_output.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/tests/test_group_submission.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/tests/test_is_equal_local.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/tests/test_local_server.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/tests/test_messages.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/tests/test_notebook_config_errors.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/tests/test_phigrade_aggregation_local.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/tests/test_public_api_names.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/tests/test_real_backend_integration.py +0 -0
- {phigrade-3.4.0 → phigrade-3.4.2}/tests/test_student_mode_no_persistence.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: phigrade
|
|
3
|
-
Version: 3.4.
|
|
3
|
+
Version: 3.4.2
|
|
4
4
|
Summary: A lightweight autograder where tests run locally and data is stored remotely
|
|
5
5
|
Project-URL: Repository, https://github.com/mld-instructors/phigrade
|
|
6
6
|
Author-email: Matt Gormley <mgormley@cs.cmu.edu>, Jacob Rast <jrast@andrew.cmu.edu>
|
|
@@ -533,16 +533,29 @@ but a Colab notebook raises `PhiGradeUsageError`, naming the config key and sayi
|
|
|
533
533
|
set it `false` for local runs, rather than silently submitting without the notebook the
|
|
534
534
|
student believes they sent.
|
|
535
535
|
|
|
536
|
-
> **
|
|
537
|
-
>
|
|
538
|
-
>
|
|
539
|
-
>
|
|
540
|
-
>
|
|
541
|
-
>
|
|
542
|
-
>
|
|
543
|
-
>
|
|
544
|
-
>
|
|
545
|
-
>
|
|
536
|
+
> **Each `%%ipytest` run starts fresh, even without restarting the
|
|
537
|
+
> kernel — with one caveat for the notebook itself.** phigrade caches the
|
|
538
|
+
> notebook it attaches, the submission it creates, and `phigrade.yaml` at
|
|
539
|
+
> module level — a plain `pytest` process is fresh every time, but a notebook
|
|
540
|
+
> kernel is not, and this used to mean a second `%%ipytest` run reused the
|
|
541
|
+
> first run's submission and never noticed later edits. A
|
|
542
|
+
> `pytest_sessionstart` hook now clears that cache at the start of every
|
|
543
|
+
> pytest session, so editing code or `phigrade.yaml` and re-running
|
|
544
|
+
> `%%ipytest` submits your latest edits, not the first run's. What this
|
|
545
|
+
> guarantees for the notebook itself is narrower: each run *re-asks Colab*
|
|
546
|
+
> for it (`google.colab._message`'s `get_ipynb`) rather than replaying a
|
|
547
|
+
> cached copy, but not that Colab answers with unsaved editor state. **Save
|
|
548
|
+
> the notebook (Ctrl/Cmd+S) before the run you want graded.**
|
|
549
|
+
>
|
|
550
|
+
> **Exception: switching between teacher and student mode against the local
|
|
551
|
+
> server needs a restart.** `use_local_server: true` starts one server
|
|
552
|
+
> process for the life of the kernel, and that process is locked to whichever
|
|
553
|
+
> mode — and `local_server_db_file` — it first saw: `app._setup_db` uses
|
|
554
|
+
> `teacher_mode` at that moment to decide between the real reference file and
|
|
555
|
+
> a throwaway temp copy, so a later, disagreeing config could otherwise read
|
|
556
|
+
> or write the wrong one. phigrade refuses instead of doing that silently. If
|
|
557
|
+
> you flip `PHIGRADE_TEACHER_MODE` or `teacher_mode` mid-kernel and hit that
|
|
558
|
+
> error, use **Runtime → Restart session and run all**.
|
|
546
559
|
|
|
547
560
|
> **Publish the answer key from the notebook, not from a `.py` file.** A
|
|
548
561
|
> test defined in a cell belongs to module `__main__`, and the server
|
|
@@ -561,15 +574,20 @@ student believes they sent.
|
|
|
561
574
|
> ```
|
|
562
575
|
>
|
|
563
576
|
> or set `teacher_mode: true` in the `%%writefile phigrade.yaml` cell.
|
|
564
|
-
> Either way,
|
|
565
|
-
>
|
|
566
|
-
>
|
|
577
|
+
> Either way, re-run the `%%ipytest` cell after setting it — the next pytest
|
|
578
|
+
> session reloads the environment and `phigrade.yaml` fresh (see above). If
|
|
579
|
+
> you are running against the local server and this switches modes from an
|
|
580
|
+
> earlier run in the same kernel, restart the session first (see the
|
|
581
|
+
> exception above), or the run raises rather than publishing.
|
|
567
582
|
|
|
568
583
|
## Configuration
|
|
569
584
|
|
|
570
585
|
The only configuration source is a YAML file named `phigrade.yaml`, located **in the
|
|
571
586
|
directory containing the test module** that defines the `@weight` function — not the
|
|
572
|
-
current working directory. It is loaded once and cached for
|
|
587
|
+
current working directory. It is loaded once per pytest session and cached for
|
|
588
|
+
the rest of that session — one session per `pytest` process outside a notebook,
|
|
589
|
+
and, since the [Colab notebooks](#colab-notebooks) section above, one session per
|
|
590
|
+
`%%ipytest` run in a single kernel.
|
|
573
591
|
|
|
574
592
|
Five keys can be overridden from the environment — see
|
|
575
593
|
[Environment overrides](#environment-overrides) below. No other key can.
|
|
@@ -628,7 +646,8 @@ Local answer key: slot_a_key.json (PHIGRADE_LOCAL_DB_FILE)
|
|
|
628
646
|
|
|
629
647
|
That line is the whole point of announcing: a variable exported once in a shell profile
|
|
630
648
|
must not silently republish an answer key, or redirect a submission, weeks later. The
|
|
631
|
-
config is loaded once per
|
|
649
|
+
config is loaded once per pytest session, so each line appears at most once per
|
|
650
|
+
session (see [Configuration](#configuration) above).
|
|
632
651
|
|
|
633
652
|
### Submission Slots
|
|
634
653
|
|
|
@@ -667,9 +686,10 @@ Either way the resolved slot is announced — `Submitting to: Human only` — so
|
|
|
667
686
|
submissions. On the environment-variable path that line goes to stdout, which
|
|
668
687
|
pytest captures and prints on failure (or always, under `-s`).
|
|
669
688
|
|
|
670
|
-
The prompt reads and writes
|
|
671
|
-
|
|
672
|
-
|
|
689
|
+
The prompt reads and writes the terminal device directly rather than stdin/stdout
|
|
690
|
+
— `/dev/tty` on macOS and Linux, and `CONIN$`/`CONOUT$` on Windows, which has no
|
|
691
|
+
single duplex device — so it works under a plain `pytest` run **with no `-s`
|
|
692
|
+
flag**: pytest's output capture never sees it.
|
|
673
693
|
|
|
674
694
|
Some runs have no terminal that can be prompted on, and they **must** set
|
|
675
695
|
`PHIGRADE_SLOT` to one of the slot names:
|
|
@@ -678,14 +698,18 @@ Some runs have no terminal that can be prompted on, and they **must** set
|
|
|
678
698
|
export PHIGRADE_SLOT="Human only"
|
|
679
699
|
```
|
|
680
700
|
|
|
681
|
-
That covers non-interactive environments — a Gradescope autograder, CI
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
701
|
+
That covers non-interactive environments — a Gradescope autograder, CI, or a
|
|
702
|
+
Windows process with no console at all (a `pythonw` run, or native Windows
|
|
703
|
+
Python under Git Bash/mintty) — and three cases that would otherwise hang:
|
|
704
|
+
`pytest -n auto` (each xdist worker is its own process, so N workers would
|
|
705
|
+
interleave N prompts on one terminal), any background job (`nohup pytest &`
|
|
706
|
+
under a job-control shell, where reading the terminal raises `SIGTTIN` and
|
|
707
|
+
suspends the process — POSIX only), and a notebook kernel, which on Windows
|
|
708
|
+
can inherit the console of whatever launched it and would prompt into a window
|
|
709
|
+
the student is not looking at. Each is detected and turned into the same error
|
|
710
|
+
naming the valid slot names.
|
|
711
|
+
|
|
712
|
+
Because the config is loaded once and cached for the rest of the session (see
|
|
689
713
|
above), a `pytest` run resolves the slot — and therefore prompts, if it prompts at
|
|
690
714
|
all — at most once, no matter how many tests run.
|
|
691
715
|
|
|
@@ -698,7 +722,7 @@ gets a `Sub-unit-check reference not found` error on every checkpoint, naming th
|
|
|
698
722
|
assessment, module, function and checkpoint it looked for.
|
|
699
723
|
|
|
700
724
|
So an instructor must publish once per slot. Because the config is cached per
|
|
701
|
-
|
|
725
|
+
session, that means one `pytest` process (or one `%%ipytest` run) per slot:
|
|
702
726
|
|
|
703
727
|
```bash
|
|
704
728
|
PHIGRADE_TEACHER_MODE=true PHIGRADE_SLOT="Human only" pytest test_mysolution.py
|
|
@@ -508,16 +508,29 @@ but a Colab notebook raises `PhiGradeUsageError`, naming the config key and sayi
|
|
|
508
508
|
set it `false` for local runs, rather than silently submitting without the notebook the
|
|
509
509
|
student believes they sent.
|
|
510
510
|
|
|
511
|
-
> **
|
|
512
|
-
>
|
|
513
|
-
>
|
|
514
|
-
>
|
|
515
|
-
>
|
|
516
|
-
>
|
|
517
|
-
>
|
|
518
|
-
>
|
|
519
|
-
>
|
|
520
|
-
>
|
|
511
|
+
> **Each `%%ipytest` run starts fresh, even without restarting the
|
|
512
|
+
> kernel — with one caveat for the notebook itself.** phigrade caches the
|
|
513
|
+
> notebook it attaches, the submission it creates, and `phigrade.yaml` at
|
|
514
|
+
> module level — a plain `pytest` process is fresh every time, but a notebook
|
|
515
|
+
> kernel is not, and this used to mean a second `%%ipytest` run reused the
|
|
516
|
+
> first run's submission and never noticed later edits. A
|
|
517
|
+
> `pytest_sessionstart` hook now clears that cache at the start of every
|
|
518
|
+
> pytest session, so editing code or `phigrade.yaml` and re-running
|
|
519
|
+
> `%%ipytest` submits your latest edits, not the first run's. What this
|
|
520
|
+
> guarantees for the notebook itself is narrower: each run *re-asks Colab*
|
|
521
|
+
> for it (`google.colab._message`'s `get_ipynb`) rather than replaying a
|
|
522
|
+
> cached copy, but not that Colab answers with unsaved editor state. **Save
|
|
523
|
+
> the notebook (Ctrl/Cmd+S) before the run you want graded.**
|
|
524
|
+
>
|
|
525
|
+
> **Exception: switching between teacher and student mode against the local
|
|
526
|
+
> server needs a restart.** `use_local_server: true` starts one server
|
|
527
|
+
> process for the life of the kernel, and that process is locked to whichever
|
|
528
|
+
> mode — and `local_server_db_file` — it first saw: `app._setup_db` uses
|
|
529
|
+
> `teacher_mode` at that moment to decide between the real reference file and
|
|
530
|
+
> a throwaway temp copy, so a later, disagreeing config could otherwise read
|
|
531
|
+
> or write the wrong one. phigrade refuses instead of doing that silently. If
|
|
532
|
+
> you flip `PHIGRADE_TEACHER_MODE` or `teacher_mode` mid-kernel and hit that
|
|
533
|
+
> error, use **Runtime → Restart session and run all**.
|
|
521
534
|
|
|
522
535
|
> **Publish the answer key from the notebook, not from a `.py` file.** A
|
|
523
536
|
> test defined in a cell belongs to module `__main__`, and the server
|
|
@@ -536,15 +549,20 @@ student believes they sent.
|
|
|
536
549
|
> ```
|
|
537
550
|
>
|
|
538
551
|
> or set `teacher_mode: true` in the `%%writefile phigrade.yaml` cell.
|
|
539
|
-
> Either way,
|
|
540
|
-
>
|
|
541
|
-
>
|
|
552
|
+
> Either way, re-run the `%%ipytest` cell after setting it — the next pytest
|
|
553
|
+
> session reloads the environment and `phigrade.yaml` fresh (see above). If
|
|
554
|
+
> you are running against the local server and this switches modes from an
|
|
555
|
+
> earlier run in the same kernel, restart the session first (see the
|
|
556
|
+
> exception above), or the run raises rather than publishing.
|
|
542
557
|
|
|
543
558
|
## Configuration
|
|
544
559
|
|
|
545
560
|
The only configuration source is a YAML file named `phigrade.yaml`, located **in the
|
|
546
561
|
directory containing the test module** that defines the `@weight` function — not the
|
|
547
|
-
current working directory. It is loaded once and cached for
|
|
562
|
+
current working directory. It is loaded once per pytest session and cached for
|
|
563
|
+
the rest of that session — one session per `pytest` process outside a notebook,
|
|
564
|
+
and, since the [Colab notebooks](#colab-notebooks) section above, one session per
|
|
565
|
+
`%%ipytest` run in a single kernel.
|
|
548
566
|
|
|
549
567
|
Five keys can be overridden from the environment — see
|
|
550
568
|
[Environment overrides](#environment-overrides) below. No other key can.
|
|
@@ -603,7 +621,8 @@ Local answer key: slot_a_key.json (PHIGRADE_LOCAL_DB_FILE)
|
|
|
603
621
|
|
|
604
622
|
That line is the whole point of announcing: a variable exported once in a shell profile
|
|
605
623
|
must not silently republish an answer key, or redirect a submission, weeks later. The
|
|
606
|
-
config is loaded once per
|
|
624
|
+
config is loaded once per pytest session, so each line appears at most once per
|
|
625
|
+
session (see [Configuration](#configuration) above).
|
|
607
626
|
|
|
608
627
|
### Submission Slots
|
|
609
628
|
|
|
@@ -642,9 +661,10 @@ Either way the resolved slot is announced — `Submitting to: Human only` — so
|
|
|
642
661
|
submissions. On the environment-variable path that line goes to stdout, which
|
|
643
662
|
pytest captures and prints on failure (or always, under `-s`).
|
|
644
663
|
|
|
645
|
-
The prompt reads and writes
|
|
646
|
-
|
|
647
|
-
|
|
664
|
+
The prompt reads and writes the terminal device directly rather than stdin/stdout
|
|
665
|
+
— `/dev/tty` on macOS and Linux, and `CONIN$`/`CONOUT$` on Windows, which has no
|
|
666
|
+
single duplex device — so it works under a plain `pytest` run **with no `-s`
|
|
667
|
+
flag**: pytest's output capture never sees it.
|
|
648
668
|
|
|
649
669
|
Some runs have no terminal that can be prompted on, and they **must** set
|
|
650
670
|
`PHIGRADE_SLOT` to one of the slot names:
|
|
@@ -653,14 +673,18 @@ Some runs have no terminal that can be prompted on, and they **must** set
|
|
|
653
673
|
export PHIGRADE_SLOT="Human only"
|
|
654
674
|
```
|
|
655
675
|
|
|
656
|
-
That covers non-interactive environments — a Gradescope autograder, CI
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
676
|
+
That covers non-interactive environments — a Gradescope autograder, CI, or a
|
|
677
|
+
Windows process with no console at all (a `pythonw` run, or native Windows
|
|
678
|
+
Python under Git Bash/mintty) — and three cases that would otherwise hang:
|
|
679
|
+
`pytest -n auto` (each xdist worker is its own process, so N workers would
|
|
680
|
+
interleave N prompts on one terminal), any background job (`nohup pytest &`
|
|
681
|
+
under a job-control shell, where reading the terminal raises `SIGTTIN` and
|
|
682
|
+
suspends the process — POSIX only), and a notebook kernel, which on Windows
|
|
683
|
+
can inherit the console of whatever launched it and would prompt into a window
|
|
684
|
+
the student is not looking at. Each is detected and turned into the same error
|
|
685
|
+
naming the valid slot names.
|
|
686
|
+
|
|
687
|
+
Because the config is loaded once and cached for the rest of the session (see
|
|
664
688
|
above), a `pytest` run resolves the slot — and therefore prompts, if it prompts at
|
|
665
689
|
all — at most once, no matter how many tests run.
|
|
666
690
|
|
|
@@ -673,7 +697,7 @@ gets a `Sub-unit-check reference not found` error on every checkpoint, naming th
|
|
|
673
697
|
assessment, module, function and checkpoint it looked for.
|
|
674
698
|
|
|
675
699
|
So an instructor must publish once per slot. Because the config is cached per
|
|
676
|
-
|
|
700
|
+
session, that means one `pytest` process (or one `%%ipytest` run) per slot:
|
|
677
701
|
|
|
678
702
|
```bash
|
|
679
703
|
PHIGRADE_TEACHER_MODE=true PHIGRADE_SLOT="Human only" pytest test_mysolution.py
|
|
@@ -42,11 +42,21 @@ are half of each unit check's identity, so the two must not drift. The notebooks
|
|
|
42
42
|
generated from one shared definition for exactly this reason — if you edit the tests,
|
|
43
43
|
edit both.
|
|
44
44
|
|
|
45
|
-
**
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
**Each `%%ipytest` run starts fresh — save before the run that counts.** A
|
|
46
|
+
`pytest_sessionstart` hook resets phigrade's cached submission and config at the
|
|
47
|
+
start of every pytest session, so editing code or `phigrade.yaml` and re-running
|
|
48
|
+
`%%ipytest` submits your latest edits, not the first run's — no restart needed for
|
|
49
|
+
that. The guarantee is narrower for the notebook itself: each run re-asks Colab for
|
|
50
|
+
it rather than replaying a cached copy, not that Colab returns unsaved editor
|
|
51
|
+
state, so save the notebook (Ctrl/Cmd+S) before the run you want graded. A restart
|
|
52
|
+
is still needed if you run this example against a local server
|
|
53
|
+
(`use_local_server: true`, see "Trying it without Colab" below): the server
|
|
54
|
+
process is locked to whichever mode it first saw, so phigrade now refuses a
|
|
55
|
+
mid-kernel switch between teacher and student mode rather than misroute a
|
|
56
|
+
submission, and re-publishing the answer key from `lab1_instructor.ipynb` in the
|
|
57
|
+
same kernel still needs a restart too, since the local server's answer-key file
|
|
58
|
+
accumulates duplicate rows across sessions otherwise. Both notebooks say so at the
|
|
59
|
+
point where it matters.
|
|
50
60
|
|
|
51
61
|
## Trying it without Colab
|
|
52
62
|
|
|
@@ -176,10 +176,15 @@
|
|
|
176
176
|
"cell_type": "markdown",
|
|
177
177
|
"metadata": {},
|
|
178
178
|
"source": [
|
|
179
|
-
"> **Re-publishing?** Use **Runtime -> Restart session and run all
|
|
180
|
-
">
|
|
181
|
-
">
|
|
182
|
-
"> start a
|
|
179
|
+
"> **Re-publishing?** Use **Runtime -> Restart session and run all** if\n",
|
|
180
|
+
"> you're running against a local server (`use_local_server: true`).\n",
|
|
181
|
+
"> phigrade's cached config and reference attempt now reset automatically\n",
|
|
182
|
+
"> at the start of every `%%ipytest` run, so a second run in the same\n",
|
|
183
|
+
"> *hosted* session does start a clean publish. Against a local server,\n",
|
|
184
|
+
"> restart anyway: the server process is locked to whichever mode it first\n",
|
|
185
|
+
"> saw, so phigrade now refuses a mode switch mid-kernel rather than\n",
|
|
186
|
+
"> misroute a submission, and a second publish in the same kernel still\n",
|
|
187
|
+
"> appends to the local answer-key file instead of replacing it."
|
|
183
188
|
]
|
|
184
189
|
}
|
|
185
190
|
],
|
|
@@ -150,13 +150,14 @@
|
|
|
150
150
|
"cell_type": "markdown",
|
|
151
151
|
"metadata": {},
|
|
152
152
|
"source": [
|
|
153
|
-
"> **
|
|
153
|
+
"> **Save before the run you want graded.**\n",
|
|
154
154
|
">\n",
|
|
155
|
-
"> phigrade
|
|
156
|
-
"> `
|
|
157
|
-
">
|
|
158
|
-
">
|
|
159
|
-
">
|
|
155
|
+
"> phigrade resets its cached submission and config at the start of every\n",
|
|
156
|
+
"> `%%ipytest` run, so editing your code and re-running submits your latest\n",
|
|
157
|
+
"> work -- no restart needed for that. The guarantee is narrower for the\n",
|
|
158
|
+
"> notebook itself: each run re-asks Colab for it rather than reusing an old\n",
|
|
159
|
+
"> copy, not that Colab returns unsaved editor state. Save the notebook\n",
|
|
160
|
+
"> (Ctrl/Cmd+S) before the run you want graded."
|
|
160
161
|
]
|
|
161
162
|
}
|
|
162
163
|
],
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# A group submission
|
|
2
|
+
|
|
3
|
+
A worked example of the `group_members` key from
|
|
4
|
+
[the client README's configuration table](../../README.md#configuration): one
|
|
5
|
+
student runs the tests, and every student named in the config gets the grade.
|
|
6
|
+
|
|
7
|
+
| File | What it is |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `phigrade.yaml` | A complete group configuration, commented. The example *is* the config — there is no test file here, because nothing about writing tests changes when a submission is shared. |
|
|
10
|
+
|
|
11
|
+
Copy it beside the test module that defines your `@weight` functions —
|
|
12
|
+
`phigrade.yaml` is looked up next to that file, not in the working directory —
|
|
13
|
+
fill in `course_id`, `assessment_id` and `server_url`, and put both group members'
|
|
14
|
+
addresses in `group_members`.
|
|
15
|
+
|
|
16
|
+
Your instructor has to allow it first: **Group Size** on the assessment settings
|
|
17
|
+
page is `1` by default, which means students submit individually, and a submission
|
|
18
|
+
carrying a group is refused with a message saying so.
|
|
19
|
+
|
|
20
|
+
## Three things this example exists to show
|
|
21
|
+
|
|
22
|
+
**List the whole group, including yourself.** Not "your partners" — everyone. Both
|
|
23
|
+
students work in the same repository, so both commit the same file, and both
|
|
24
|
+
machines tag every attempt with the same group. That symmetry is what makes the
|
|
25
|
+
resolution agree for both of you: your grade comes from the most recently
|
|
26
|
+
submitted active attempt among the attempts you are on, and if the two of you
|
|
27
|
+
described the group differently, that sentence would resolve differently for each
|
|
28
|
+
of you. A list that omits your own address is refused rather than quietly guessed
|
|
29
|
+
at.
|
|
30
|
+
|
|
31
|
+
**Read the line it prints.** A group submission announces itself:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
Created submission 8f2c1a4e-…
|
|
35
|
+
Submitting as a group: alice@andrew.cmu.edu, bob@andrew.cmu.edu
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
That is the only guard against a silent solo submission, and the failure it
|
|
39
|
+
guards against — believing you submitted together and discovering otherwise when
|
|
40
|
+
grades come out — has no undo. In Colab, use `%%ipytest -s`; plain `%%ipytest`
|
|
41
|
+
captures stdout and the line never reaches you. The client README's
|
|
42
|
+
[Colab notebooks](../../README.md#colab-notebooks) section says the same thing
|
|
43
|
+
about submission slots, for the same reason.
|
|
44
|
+
|
|
45
|
+
**Only one of you needs to run the tests.** The partner who never ran anything
|
|
46
|
+
still sees the submission and its per-unit-test breakdown in the web app, on both
|
|
47
|
+
the Attempts page and the assessment Overview, labelled with who submitted it.
|
|
48
|
+
That is the whole feature: the work is graded once and counts for everyone on it.
|
|
49
|
+
|
|
50
|
+
## What this does not do
|
|
51
|
+
|
|
52
|
+
There are no persistent teams. The group is named at submission time and restated
|
|
53
|
+
by submitting again — change `group_members` and re-run, and the new list is what
|
|
54
|
+
counts. There is no invitation or acceptance step either, so a submission can name
|
|
55
|
+
you without asking; it is visible to you and to staff the moment it lands, and
|
|
56
|
+
submitting your own work makes yours the one that counts again.
|
|
57
|
+
|
|
58
|
+
Group submissions also work on the rubric-graded PDF side, the same rule
|
|
59
|
+
applying to a scanned upload as to a code submission. The group is named in a
|
|
60
|
+
different place on each side: `group_members` in `phigrade.yaml` here, versus
|
|
61
|
+
a "Partners" field on the PDF upload form in the web application.
|
|
62
|
+
|
|
63
|
+
One PDF-specific wrinkle to know before you rely on it: if your partner
|
|
64
|
+
uploads a PDF alone after you two submitted together, that solo upload
|
|
65
|
+
replaces your shared submission for **both** of you, and you are left with
|
|
66
|
+
no active submission at all until you submit again yourself. On the coding
|
|
67
|
+
side this does not happen — a partner's solo re-submission only replaces
|
|
68
|
+
their own attempt, and yours keeps counting. So on the PDF side, treat a
|
|
69
|
+
group submission as something either partner can end for both of you by
|
|
70
|
+
submitting alone; if that happens, re-upload to have your own work count
|
|
71
|
+
again.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# A group submission: one student runs the tests, the whole group gets the grade.
|
|
2
|
+
#
|
|
3
|
+
# Drop this beside the test module that defines your `@weight` functions --
|
|
4
|
+
# `phigrade.yaml` is looked up next to that file, not in the working directory.
|
|
5
|
+
# Replace course_id, assessment_id and server_url with your own.
|
|
6
|
+
|
|
7
|
+
course_id: "your-course-uuid"
|
|
8
|
+
assessment_id: "your-assessment-uuid"
|
|
9
|
+
|
|
10
|
+
use_local_server: false
|
|
11
|
+
server_url: "https://phigrade.example.org"
|
|
12
|
+
|
|
13
|
+
# Every student this submission is for, INCLUDING YOURSELF.
|
|
14
|
+
#
|
|
15
|
+
# Not "your partners" -- everyone. Both of you work in the same repository, so
|
|
16
|
+
# both of you commit this same file, and both machines then tag every attempt
|
|
17
|
+
# with the same group. That symmetry is the point: the server resolves your
|
|
18
|
+
# grade to the most recently submitted active attempt among the attempts you
|
|
19
|
+
# are on, so listing the group identically on both sides is what makes it give
|
|
20
|
+
# both of you the same answer.
|
|
21
|
+
#
|
|
22
|
+
# Leaving this out entirely is an ordinary solo submission.
|
|
23
|
+
group_members:
|
|
24
|
+
- alice@andrew.cmu.edu
|
|
25
|
+
- bob@andrew.cmu.edu
|
|
26
|
+
|
|
27
|
+
teacher_mode: false
|
|
28
|
+
submission_files:
|
|
29
|
+
- solution.py
|
|
30
|
+
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
# What to expect when you run this
|
|
33
|
+
#
|
|
34
|
+
# Every group submission prints its group, right after the submission id:
|
|
35
|
+
#
|
|
36
|
+
# Created submission 8f2c1a4e-...
|
|
37
|
+
# Submitting as a group: alice@andrew.cmu.edu, bob@andrew.cmu.edu
|
|
38
|
+
#
|
|
39
|
+
# Read that line. It is the only thing standing between you and a silent solo
|
|
40
|
+
# submission -- believing you submitted as a group and finding out at grade
|
|
41
|
+
# release has no undo. In a Colab notebook use `%%ipytest -s`: plain
|
|
42
|
+
# `%%ipytest` captures stdout and swallows the line.
|
|
43
|
+
#
|
|
44
|
+
# Four things are refused, each with 422 and a message naming the problem:
|
|
45
|
+
#
|
|
46
|
+
# * the assessment does not allow groups -- "Group submissions are not
|
|
47
|
+
# enabled for this assessment." Your instructor sets Group Size in the
|
|
48
|
+
# assessment settings; it is 1 by default, which means submit individually.
|
|
49
|
+
# * the group is larger than that setting.
|
|
50
|
+
# * an address is not an active student on this course. Staff are refused
|
|
51
|
+
# here too, by their own address: group submissions are for students.
|
|
52
|
+
# * the list does not contain your own address (see above -- list everyone).
|
|
53
|
+
#
|
|
54
|
+
# Addresses are stripped, lower-cased and de-duplicated before they are sent,
|
|
55
|
+
# so spacing and capitalisation here do not matter.
|
|
56
|
+
#
|
|
57
|
+
# `PHIGRADE_GROUP_MEMBERS="alice@andrew.cmu.edu,bob@andrew.cmu.edu"` overrides
|
|
58
|
+
# this key for one run, which is what a CI job or a scripted submission wants.
|
|
59
|
+
# It announces itself on stdout when it takes effect. The file is the normal
|
|
60
|
+
# path -- in Colab as much as on a laptop, since a Colab session writes this
|
|
61
|
+
# file from a `%%writefile phigrade.yaml` cell.
|