making-with-code-cli 2.0.5__tar.gz → 2.2.0__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 (35) hide show
  1. {making_with_code_cli-2.0.5 → making_with_code_cli-2.2.0}/PKG-INFO +5 -2
  2. {making_with_code_cli-2.0.5 → making_with_code_cli-2.2.0}/making_with_code_cli/teach/__init__.py +4 -2
  3. making_with_code_cli-2.2.0/making_with_code_cli/teach/assess.py +30 -0
  4. making_with_code_cli-2.2.0/making_with_code_cli/teach/check/__init__.py +31 -0
  5. making_with_code_cli-2.2.0/making_with_code_cli/teach/check/check_module.py +71 -0
  6. making_with_code_cli-2.2.0/making_with_code_cli/teach/patch.py +91 -0
  7. {making_with_code_cli-2.0.5 → making_with_code_cli-2.2.0}/pyproject.toml +14 -2
  8. making_with_code_cli-2.0.5/making_with_code_cli/teach/check.py +0 -25
  9. making_with_code_cli-2.0.5/making_with_code_cli/teach/test/__init__.py +0 -14
  10. making_with_code_cli-2.0.5/making_with_code_cli/teach/test/test_module.py +0 -9
  11. {making_with_code_cli-2.0.5 → making_with_code_cli-2.2.0}/README.md +0 -0
  12. {making_with_code_cli-2.0.5 → making_with_code_cli-2.2.0}/making_with_code_cli/cli.py +0 -0
  13. {making_with_code_cli-2.0.5 → making_with_code_cli-2.2.0}/making_with_code_cli/curriculum.py +0 -0
  14. {making_with_code_cli-2.0.5 → making_with_code_cli-2.2.0}/making_with_code_cli/errors.py +0 -0
  15. {making_with_code_cli-2.0.5 → making_with_code_cli-2.2.0}/making_with_code_cli/git_backend/__init__.py +0 -0
  16. {making_with_code_cli-2.0.5 → making_with_code_cli-2.2.0}/making_with_code_cli/git_backend/base_backend.py +0 -0
  17. {making_with_code_cli-2.0.5 → making_with_code_cli-2.2.0}/making_with_code_cli/git_backend/mwc_backend.py +0 -0
  18. {making_with_code_cli-2.0.5 → making_with_code_cli-2.2.0}/making_with_code_cli/git_wrapper.py +0 -0
  19. {making_with_code_cli-2.0.5 → making_with_code_cli-2.2.0}/making_with_code_cli/helpers.py +0 -0
  20. {making_with_code_cli-2.0.5 → making_with_code_cli-2.2.0}/making_with_code_cli/mwc_accounts_api.py +0 -0
  21. {making_with_code_cli-2.0.5 → making_with_code_cli-2.2.0}/making_with_code_cli/settings.py +0 -0
  22. {making_with_code_cli-2.0.5 → making_with_code_cli-2.2.0}/making_with_code_cli/setup/__init__.py +0 -0
  23. {making_with_code_cli-2.0.5 → making_with_code_cli-2.2.0}/making_with_code_cli/setup/tasks.py +0 -0
  24. {making_with_code_cli-2.0.5 → making_with_code_cli-2.2.0}/making_with_code_cli/styles.py +0 -0
  25. {making_with_code_cli-2.0.5 → making_with_code_cli-2.2.0}/making_with_code_cli/submit.py +0 -0
  26. {making_with_code_cli-2.0.5 → making_with_code_cli-2.2.0}/making_with_code_cli/teach/gitea_api/api.py +0 -0
  27. {making_with_code_cli-2.0.5 → making_with_code_cli-2.2.0}/making_with_code_cli/teach/gitea_api/exceptions.py +0 -0
  28. {making_with_code_cli-2.0.5 → making_with_code_cli-2.2.0}/making_with_code_cli/teach/log.py +0 -0
  29. {making_with_code_cli-2.0.5 → making_with_code_cli-2.2.0}/making_with_code_cli/teach/setup.py +0 -0
  30. {making_with_code_cli-2.0.5 → making_with_code_cli-2.2.0}/making_with_code_cli/teach/status.py +0 -0
  31. {making_with_code_cli-2.0.5 → making_with_code_cli-2.2.0}/making_with_code_cli/teach/student_repo_functions.py +0 -0
  32. {making_with_code_cli-2.0.5 → making_with_code_cli-2.2.0}/making_with_code_cli/teach/student_repos.py +0 -0
  33. {making_with_code_cli-2.0.5 → making_with_code_cli-2.2.0}/making_with_code_cli/teach/update.py +0 -0
  34. {making_with_code_cli-2.0.5 → making_with_code_cli-2.2.0}/making_with_code_cli/update/__init__.py +0 -0
  35. {making_with_code_cli-2.0.5 → making_with_code_cli-2.2.0}/making_with_code_cli/version.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: making-with-code-cli
3
- Version: 2.0.5
3
+ Version: 2.2.0
4
4
  Summary: Courseware for Making With Code
5
5
  Home-page: https://github.com/cproctor/making-with-code-courseware
6
6
  License: MIT
@@ -11,6 +11,8 @@ Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Programming Language :: Python :: 3
12
12
  Classifier: Programming Language :: Python :: 3.10
13
13
  Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
14
16
  Requires-Dist: PyYAML (>=6.0,<7.0)
15
17
  Requires-Dist: click (>=8.0.3,<9.0.0)
16
18
  Requires-Dist: dateparser (>=1.1.8,<2.0.0)
@@ -18,6 +20,7 @@ Requires-Dist: gitpython (>=3.1.32,<4.0.0)
18
20
  Requires-Dist: requests (>=2.27.1,<3.0.0)
19
21
  Requires-Dist: tabulate (>=0.9.0,<0.10.0)
20
22
  Requires-Dist: toml (>=0.10.2,<0.11.0)
23
+ Requires-Dist: tqdm (>=4.67.1,<5.0.0)
21
24
  Project-URL: issues, https://github.com/cproctor/making-with-code-courseware/issues
22
25
  Description-Content-Type: text/markdown
23
26
 
@@ -3,7 +3,8 @@ from making_with_code_cli.teach.setup import setup
3
3
  from making_with_code_cli.teach.update import update
4
4
  from making_with_code_cli.teach.status import status
5
5
  from making_with_code_cli.teach.log import log
6
- from making_with_code_cli.teach.test import test
6
+ from making_with_code_cli.teach.patch import patch
7
+ from making_with_code_cli.teach.check import check
7
8
 
8
9
  @click.group()
9
10
  def teach():
@@ -13,4 +14,5 @@ teach.add_command(setup)
13
14
  teach.add_command(update)
14
15
  teach.add_command(status)
15
16
  teach.add_command(log)
16
- teach.add_command(test)
17
+ teach.add_command(patch)
18
+ teach.add_command(check)
@@ -0,0 +1,30 @@
1
+ # teach/assess.py
2
+ # ---------------
3
+ # Implements `mwc teach assess`.
4
+ # This task iterates over student repos and assesses them.
5
+ #
6
+ # New settings:
7
+ # - I need some way of specifying a search root for assessment repos.
8
+ # Possibly teacher_assessment_repo_dir. We would search this directory
9
+ # for a directory having the same name as the lab being assessed. Within
10
+ # this direcory, we would locate assessment_policy.toml and possibly
11
+ # modules for testing. It would be recommended that assessment materials
12
+ # not be shared with students, but instead to be kept in an assessment
13
+ # branch of the base repos which are forked.
14
+ # Assessment:
15
+ # - A student module (lab, project, or problem set) is assessed according
16
+ # to a policy, which defines:
17
+ # - A front matter comment
18
+ # - The type of assessment (completion, points, or rubric)
19
+ # - The method of scoring:
20
+ # - Using a measure (e.g. number of commits, lines of code changed)
21
+ # - Using automated tests
22
+ # - Qualitatively through teacher interaction
23
+ # - Assessment is recorded in `assessment.md`, in a student's module repo.
24
+ # Machine-readable information is stored in TOML-based front matter, using
25
+ # an [[assessment]] table for each time assessment occurs. Each [[assessment]]
26
+ # has keys for date and score. The type of score depends on the type of assessment,
27
+ # and could be a string (effectively an enum) representing completion, a ratio
28
+ # (a two-integer array specifying points awarded and points possible), or
29
+ # a sub-table for rubric scores.
30
+ # - Assessment should be repeatable, with subsequent assessments appended to the record.
@@ -0,0 +1,31 @@
1
+ import click
2
+ import requests
3
+ from pathlib import Path
4
+ from tqdm import tqdm
5
+ from making_with_code_cli.curriculum import get_curriculum
6
+ from making_with_code_cli.teach.check.check_module import TestMWCModule
7
+
8
+ @click.command()
9
+ @click.argument("url")
10
+ @click.argument("course_name")
11
+ @click.argument("repo_dir", type=click.Path(exists=True, file_okay=False, writable=True,
12
+ path_type=Path))
13
+ def check(url, course_name, repo_dir):
14
+ "Test MWC curriuclum and modules"
15
+ curriculum = get_curriculum(url, course_name)
16
+ test_cases = []
17
+ for unit in curriculum['units']:
18
+ for module in unit['modules']:
19
+ full_slug = '/'.join([curriculum['slug'], unit['slug'], module['slug']])
20
+ path = repo_dir / full_slug
21
+ test_cases.append((module, path, full_slug))
22
+ results = []
23
+ for mod, path, slug in tqdm(test_cases):
24
+ test = TestMWCModule(module, path)
25
+ errors = test.run()
26
+ if errors:
27
+ results.append((slug, errors))
28
+ for slug, errors in results:
29
+ print(slug)
30
+ for error in errors:
31
+ print(f" - {error}")
@@ -0,0 +1,71 @@
1
+ # test_module.py
2
+ # --------------
3
+ # Defines a test case for a MWC module.
4
+
5
+ from pathlib import Path
6
+ import requests
7
+ import toml
8
+ from git import Repo, InvalidGitRepositoryError, GitCommandError
9
+
10
+ DEFAULT_BRANCH_NAME = "main"
11
+ PYTHON_VERSION = "^3.10"
12
+
13
+ class TestCouldNotContinue(Exception):
14
+ pass
15
+
16
+ class TestMWCModule:
17
+
18
+ def __init__(self, module_metadata, repo_path):
19
+ self.module_metadata = module_metadata
20
+ self.repo_path = repo_path
21
+
22
+ def run(self):
23
+ self.errors = []
24
+ try:
25
+ self.fetch_repo()
26
+ self.test_curriculum_page_exists()
27
+ self.test_has_commit_template()
28
+ self.test_module_metadata()
29
+ except TestCouldNotContinue as err:
30
+ self.errors.append(str(err))
31
+ return self.errors
32
+
33
+ def fetch_repo(self):
34
+ """Ensures the repo is present and up to date.
35
+ """
36
+ if self.repo_path.exists():
37
+ try:
38
+ repo = Repo(self.repo_path)
39
+ repo.remotes.origin.pull()
40
+ except InvalidGitRepositoryError:
41
+ raise TestCouldNotContinue(f"{self.module_path} exists but is not a repo")
42
+ else:
43
+ try:
44
+ repo = Repo.clone_from(self.module_metadata['repo_url'], self.repo_path)
45
+ except GitCommandError:
46
+ raise TestCouldNotContinue("Could not clone repo")
47
+ if not repo.active_branch.name == DEFAULT_BRANCH_NAME:
48
+ self.errors.append(f"Default branch is not '{DEFAULT_BRANCH_NAME}'")
49
+
50
+ def test_curriculum_page_exists(self):
51
+ page_url = self.module_metadata['url']
52
+ response = requests.get(page_url)
53
+ if not response.ok:
54
+ self.errors.append(f"Curriculum page missing: {page_url}")
55
+
56
+ def test_has_commit_template(self):
57
+ ct = self.repo_path / ".commit_template"
58
+ if not ct.exists:
59
+ self.errors.append(".commit_template is missing")
60
+
61
+ def test_module_metadata(self):
62
+ md_file = self.repo_path/"pyproject.toml"
63
+ if not md_file.exists():
64
+ self.errors.append(f"pyproject.toml missing")
65
+ return
66
+ md = toml.load(md_file)
67
+ pyversion = md["tool"]["poetry"]["dependencies"]["python"]
68
+ if not pyversion == PYTHON_VERSION:
69
+ self.errors.append(f"python version is {pyversion}, expected {PYTHON_VERSION}")
70
+ if "dev-dependencies" in md["tool"]["poetry"]:
71
+ self.errors.append("pyproject.toml has deprecated tool.poetry.dev-dependencies")
@@ -0,0 +1,91 @@
1
+ # teach/patch.py
2
+ # ---------------
3
+ # Implements `mwc teach patch`.
4
+ # This task patches a module for all students in a group.
5
+ # Patching is most useful when a change needs to be applied
6
+ # to a module after a course has started, and students each
7
+ # have their own copy of the repo.
8
+
9
+ import click
10
+ import git
11
+ from git import Repo
12
+ from making_with_code_cli.settings import read_settings
13
+ from making_with_code_cli.teach.setup import check_required_teacher_settings
14
+ from making_with_code_cli.teach.student_repos import StudentRepos
15
+ from making_with_code_cli.teach.update import update as update_task
16
+ from making_with_code_cli.styles import success, error
17
+
18
+ GIT_REMOTE_NAME = "origin"
19
+
20
+ @click.command()
21
+ @click.argument("patch")
22
+ @click.argument("group")
23
+ @click.argument("module")
24
+ @click.option("--config", help="Path to config file (default: ~/.mwc)")
25
+ @click.option('-c', "--course", help="Filter by course name")
26
+ @click.option('-u', "--user", help="Filter by username")
27
+ @click.option('-n', "--no-commit", is_flag=True, help="Commit and push changes")
28
+ @click.option('-m', "--message", default="Applying patch", help="Commit message")
29
+ @click.option('-t', "--threads", type=int, default=8, help="Maximum simultaneous threads")
30
+ def patch(patch, group, module, config, course, user, no_commit, message, threads):
31
+ settings = read_settings(config)
32
+ if not check_required_teacher_settings(settings):
33
+ return
34
+ update_task.callback(config, group, course, user, None, module, threads)
35
+ apply_patch = apply_patch_factory(patch, message, no_commit)
36
+ repos = StudentRepos(settings, threads)
37
+ results = repos.apply(apply_patch, group=group, course=course, module=module,
38
+ user=user, status_message=f"Applying patch {patch}")
39
+ for result in results:
40
+ if result['success']:
41
+ click.echo(success(f"SUCCESS: {result['path']}", preformatted=True))
42
+ else:
43
+ click.echo(error(f"ERROR: {result['path']}: {result['message']}", preformatted=True))
44
+
45
+ def apply_patch_factory(patch, commit_message, no_commit=False):
46
+ """Creates a function which will apply a patch to a repo.
47
+ """
48
+ def apply_patch(semaphore, results, group, username, path, token):
49
+ semaphore.acquire()
50
+ if path.exists():
51
+ repo = Repo(path)
52
+ if has_staged_changes(repo):
53
+ results.append({
54
+ "path": path,
55
+ "success": False,
56
+ "message": "Repo has staged changes."
57
+ })
58
+ elif has_unstaged_changes(repo):
59
+ results.append({
60
+ "path": path,
61
+ "success": False,
62
+ "message": "Repo has unstaged changes."
63
+ })
64
+ else:
65
+ try:
66
+ repo.git.apply(patch)
67
+ except git.exc.GitCommandError as e:
68
+ results.append({"path": path, "success": False, "message": str(e)})
69
+ return
70
+ if not no_commit:
71
+ try:
72
+ repo.git.add(update=True)
73
+ repo.index.commit(commit_message)
74
+ remote = repo.remote(name=GIT_REMOTE_NAME)
75
+ remote.push()
76
+ except Exception as e:
77
+ results.append({"path": path, "success": False, "message": str(e)})
78
+ return
79
+ results.append({"path": path, "success": True})
80
+ semaphore.release()
81
+ return apply_patch
82
+
83
+ def has_staged_changes(repo):
84
+ staged_changes = repo.index.diff("HEAD")
85
+ return len(staged_changes) > 0
86
+
87
+ def has_unstaged_changes(repo):
88
+ unstaged_changes = repo.index.diff(None)
89
+ return len(unstaged_changes) > 0
90
+
91
+
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "making-with-code-cli"
3
- version = "2.0.5"
3
+ version = "2.2.0"
4
4
  description = "Courseware for Making With Code"
5
5
  authors = ["Chris Proctor <chris@chrisproctor.net>"]
6
6
  license = "MIT"
@@ -16,8 +16,20 @@ toml = "^0.10.2"
16
16
  tabulate = "^0.9.0"
17
17
  gitpython = "^3.1.32"
18
18
  dateparser = "^1.1.8"
19
+ tqdm = "^4.67.1"
19
20
 
20
- [tool.poetry.dev-dependencies]
21
+ [tool.poetry.group.docs]
22
+ optional = true
23
+
24
+ [tool.poetry.group.docs.dependencies]
25
+ sphinx = "^7.3.7"
26
+ sphinx-rtd-theme = "^2.0.0"
27
+
28
+ [tool.poetry.group.teacher]
29
+ optional = true
30
+
31
+ [tool.poetry.group.teacher.dependencies]
32
+ gitpython = "^3.1.44"
21
33
 
22
34
  [build-system]
23
35
  requires = ["poetry-core>=1.0.0"]
@@ -1,25 +0,0 @@
1
- # Checks setup of
2
-
3
- # - Curriculum
4
- # - Check out each template directory
5
- # - Main branch?
6
- # - Has .commit_template?
7
- # - Is template?
8
-
9
- class MWCModuleTests:
10
-
11
- {'teacher_groups': [{'code': 'lai676',
12
- 'course_name': 'Making With Code I',
13
- 'curriculum_site_url': 'https://makingwithcode.org',
14
- 'group_name': 'LAI 676 Summer 2023',
15
- 'student_tokens': {'cchung': 'b6f7bbeb34a076c1da7c6a58c9d41e1c1faecb9e',
16
- 'finn': '320abfad1c97c78a274a66d1f7cbd651151d7f7d',
17
- 'jtoombs': 'aae00b2af1722517efadd5cfc58156696de9626c',
18
- 'kodell-hamilton': '66054d1f27ff7b793c3d19b96a1e207105938149',
19
- 'lcooper': '31d0d64ecb7182eef437fd33f8eeb7e45f4c32ad',
20
- 'mgunsolus': '1f9549e36f44fc710bac82153d972e41b0df2b0f',
21
- 'mhall': '8dbab4a23a988bbfab71ff96ee673a03224f90ac',
22
- 'pwick': '3e28563be3eba4e9fcd8e9d8ce5abb494fe27d94',
23
- 'test_user_lai676': '87bbf8e278b6fa5ead76467810f8e48d93b1b46e',
24
- 'tnaber': '00456bd8df3e513e787d113df8992d5c66475c3f'}}],
25
- 'username': 'chris'}
@@ -1,14 +0,0 @@
1
- import click
2
- from making_with_code_cli.teach.test.test_module import TestMWCModule
3
-
4
- @click.command()
5
- def test():
6
- "Test MWC curriuclum and modules"
7
-
8
- """
9
- """
10
- # - Curriculum
11
- # - Check out each template directory
12
- # - Main branch?
13
- # - Has .commit_template?
14
- # - Is template?
@@ -1,9 +0,0 @@
1
- # test_module.py
2
- # --------------
3
- # Defines a testcase for a MWC module.
4
-
5
- from unittest import TestCase
6
-
7
- class TestMWCModule:
8
- def __init__(self, module_path):
9
- self.module_path = module_path