From 338f5be0900cd3e8fb88c2619c7fa15aa38f9bc4 Mon Sep 17 00:00:00 2001 From: scott-yj-yang Date: Fri, 27 Feb 2026 11:44:32 -0800 Subject: [PATCH 01/24] Migrate from nbdev to standard Python package with pyproject.toml Replace nbdev notebook-first workflow with a standard Python package structure to enable agentic coding workflows. Source .py files are now the canonical code, Quarto docs are kept as-is for the documentation site. - Add pyproject.toml with hatchling build backend (replaces setup.py + settings.ini) - Clean nbdev markers from source .py files and add missing import os in assign.py - Remove nbdev artifacts: _modidx.py, _proc/, .gitattributes, .gitconfig - Remove nbdev config: settings.ini, setup.py, MANIFEST.in, environment.yml - Clean nbdev directives from API and tutorial notebooks - Inline nbs/nbdev.yml metadata into nbs/_quarto.yml - Replace CI workflows: nbdev-ci -> uv+pytest, quarto-ghp -> direct Quarto render - Update .gitignore and README.md Co-Authored-By: Claude Opus 4.6 --- .gitattributes | 1 - .gitconfig | 11 - .github/workflows/deploy.yaml | 18 +- .github/workflows/test.yaml | 17 +- .gitignore | 5 +- CanvasGroupy/_modidx.py | 110 -- CanvasGroupy/assign.py | 8 +- CanvasGroupy/canvas.py | 45 +- CanvasGroupy/github.py | 33 +- CanvasGroupy/grading.py | 8 - MANIFEST.in | 5 - README.md | 2 - _proc/.gitignore | 1 - _proc/_docs/api/canvas.html | 753 -------- _proc/_docs/api/github.html | 1360 ------------- _proc/_docs/api/groupeng_assign.html | 707 ------- _proc/_docs/api/index.html | 440 ----- _proc/_docs/api/project_grading.html | 632 ------ .../checkpoint_feedback.html | 495 ----- .../final_project_feedback.html | 505 ----- .../feedback_template/proposal_feedback.html | 497 ----- _proc/_docs/index.html | 490 ----- _proc/_docs/listings.json | 11 - _proc/_docs/robots.txt | 1 - _proc/_docs/search.json | 205 -- .../site_libs/bootstrap/bootstrap-icons.css | 1704 ----------------- .../site_libs/bootstrap/bootstrap-icons.woff | Bin 137124 -> 0 bytes .../site_libs/bootstrap/bootstrap.min.css | 10 - .../site_libs/bootstrap/bootstrap.min.js | 7 - .../site_libs/clipboard/clipboard.min.js | 7 - .../_docs/site_libs/quarto-html/anchor.min.js | 9 - .../_docs/site_libs/quarto-html/popper.min.js | 6 - .../quarto-syntax-highlighting.css | 171 -- _proc/_docs/site_libs/quarto-html/quarto.js | 770 -------- _proc/_docs/site_libs/quarto-html/tippy.css | 1 - .../site_libs/quarto-html/tippy.umd.min.js | 2 - .../site_libs/quarto-listing/list.min.js | 2 - .../quarto-listing/quarto-listing.js | 243 --- .../site_libs/quarto-nav/headroom.min.js | 7 - .../_docs/site_libs/quarto-nav/quarto-nav.js | 222 --- .../quarto-search/autocomplete.umd.js | 3 - .../_docs/site_libs/quarto-search/fuse.min.js | 9 - .../site_libs/quarto-search/quarto-search.js | 1123 ----------- _proc/_docs/sitemap.xml | 51 - _proc/_docs/styles.css | 37 - _proc/_docs/tutorials/authentications.html | 436 ----- ...create github group from canvas group.html | 1473 -------------- .../tutorials/create template issues.html | 481 ----- _proc/_quarto.yml | 28 - _proc/api/01_GroupEng_assign.ipynb | 403 ---- _proc/api/02_github.ipynb | 1505 --------------- _proc/api/03_canvas.ipynb | 631 ------ _proc/api/04_project_grading.ipynb | 376 ---- _proc/api/github_username.csv | 2 - _proc/api/index.qmd | 11 - _proc/credentials.json | 4 - _proc/data/195_class.csv | 6 - _proc/data/195_group_specification.groupeng | 23 - _proc/data/dir.txt | 1 - .../195_group_specification_classlist.csv | 7 - .../195_group_specification_details.csv | 9 - .../195_group_specification_groups.csv | 2 - .../195_group_specification_groups.txt | 7 - .../195_group_specification_statistics.txt | 23 - _proc/dir.txt | 1 - .../feedback_template/checkpoint_feedback.md | 40 - .../final_project_feedback.md | 37 - _proc/feedback_template/proposal_feedback.md | 45 - _proc/index.ipynb | 142 -- _proc/nbdev.yml | 9 - _proc/sidebar.yml | 14 - _proc/styles.css | 37 - _proc/tutorials/Authentications.ipynb | 99 - ...reate GitHub Group from Canvas Group.ipynb | 1277 ------------ _proc/tutorials/create template issues.ipynb | 125 -- environment.yml | 22 - nbs/_quarto.yml | 8 +- nbs/api/01_GroupEng_assign.ipynb | 60 - nbs/api/02_github.ipynb | 731 ------- nbs/api/03_canvas.ipynb | 370 +--- nbs/api/04_project_grading.ipynb | 69 - nbs/api/index.qmd | 2 +- nbs/nbdev.yml | 9 - nbs/sidebar.yml | 14 - nbs/tutorials/Authentications.ipynb | 10 - pyproject.toml | 45 + settings.ini | 44 - setup.py | 57 - uv.lock | 1050 ++++++++++ 89 files changed, 1172 insertions(+), 19317 deletions(-) delete mode 100644 .gitattributes delete mode 100644 .gitconfig delete mode 100644 CanvasGroupy/_modidx.py delete mode 100644 MANIFEST.in delete mode 100644 _proc/.gitignore delete mode 100644 _proc/_docs/api/canvas.html delete mode 100644 _proc/_docs/api/github.html delete mode 100644 _proc/_docs/api/groupeng_assign.html delete mode 100644 _proc/_docs/api/index.html delete mode 100644 _proc/_docs/api/project_grading.html delete mode 100644 _proc/_docs/feedback_template/checkpoint_feedback.html delete mode 100644 _proc/_docs/feedback_template/final_project_feedback.html delete mode 100644 _proc/_docs/feedback_template/proposal_feedback.html delete mode 100644 _proc/_docs/index.html delete mode 100644 _proc/_docs/listings.json delete mode 100644 _proc/_docs/robots.txt delete mode 100644 _proc/_docs/search.json delete mode 100644 _proc/_docs/site_libs/bootstrap/bootstrap-icons.css delete mode 100644 _proc/_docs/site_libs/bootstrap/bootstrap-icons.woff delete mode 100644 _proc/_docs/site_libs/bootstrap/bootstrap.min.css delete mode 100644 _proc/_docs/site_libs/bootstrap/bootstrap.min.js delete mode 100644 _proc/_docs/site_libs/clipboard/clipboard.min.js delete mode 100644 _proc/_docs/site_libs/quarto-html/anchor.min.js delete mode 100644 _proc/_docs/site_libs/quarto-html/popper.min.js delete mode 100644 _proc/_docs/site_libs/quarto-html/quarto-syntax-highlighting.css delete mode 100644 _proc/_docs/site_libs/quarto-html/quarto.js delete mode 100644 _proc/_docs/site_libs/quarto-html/tippy.css delete mode 100644 _proc/_docs/site_libs/quarto-html/tippy.umd.min.js delete mode 100644 _proc/_docs/site_libs/quarto-listing/list.min.js delete mode 100644 _proc/_docs/site_libs/quarto-listing/quarto-listing.js delete mode 100644 _proc/_docs/site_libs/quarto-nav/headroom.min.js delete mode 100644 _proc/_docs/site_libs/quarto-nav/quarto-nav.js delete mode 100644 _proc/_docs/site_libs/quarto-search/autocomplete.umd.js delete mode 100644 _proc/_docs/site_libs/quarto-search/fuse.min.js delete mode 100644 _proc/_docs/site_libs/quarto-search/quarto-search.js delete mode 100644 _proc/_docs/sitemap.xml delete mode 100644 _proc/_docs/styles.css delete mode 100644 _proc/_docs/tutorials/authentications.html delete mode 100644 _proc/_docs/tutorials/create github group from canvas group.html delete mode 100644 _proc/_docs/tutorials/create template issues.html delete mode 100644 _proc/_quarto.yml delete mode 100644 _proc/api/01_GroupEng_assign.ipynb delete mode 100644 _proc/api/02_github.ipynb delete mode 100644 _proc/api/03_canvas.ipynb delete mode 100644 _proc/api/04_project_grading.ipynb delete mode 100644 _proc/api/github_username.csv delete mode 100644 _proc/api/index.qmd delete mode 100644 _proc/credentials.json delete mode 100644 _proc/data/195_class.csv delete mode 100644 _proc/data/195_group_specification.groupeng delete mode 100644 _proc/data/dir.txt delete mode 100644 _proc/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_classlist.csv delete mode 100644 _proc/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_details.csv delete mode 100644 _proc/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_groups.csv delete mode 100644 _proc/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_groups.txt delete mode 100644 _proc/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_statistics.txt delete mode 100644 _proc/dir.txt delete mode 100644 _proc/feedback_template/checkpoint_feedback.md delete mode 100644 _proc/feedback_template/final_project_feedback.md delete mode 100644 _proc/feedback_template/proposal_feedback.md delete mode 100644 _proc/index.ipynb delete mode 100644 _proc/nbdev.yml delete mode 100644 _proc/sidebar.yml delete mode 100644 _proc/styles.css delete mode 100644 _proc/tutorials/Authentications.ipynb delete mode 100644 _proc/tutorials/Create GitHub Group from Canvas Group.ipynb delete mode 100644 _proc/tutorials/create template issues.ipynb delete mode 100644 environment.yml delete mode 100644 nbs/nbdev.yml delete mode 100644 nbs/sidebar.yml create mode 100644 pyproject.toml delete mode 100644 settings.ini delete mode 100644 setup.py create mode 100644 uv.lock diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 753b249..0000000 --- a/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -*.ipynb merge=nbdev-merge diff --git a/.gitconfig b/.gitconfig deleted file mode 100644 index 9054574..0000000 --- a/.gitconfig +++ /dev/null @@ -1,11 +0,0 @@ -# Generated by nbdev_install_hooks -# -# If you need to disable this instrumentation do: -# git config --local --unset include.path -# -# To restore: -# git config --local include.path ../.gitconfig -# -[merge "nbdev-merge"] - name = resolve conflicts with nbdev_fix - driver = nbdev_merge %O %A %B %P diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 29bfc57..c18b1a5 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -6,9 +6,23 @@ permissions: on: push: - branches: [ "main", "master" ] + branches: ["main", "master"] workflow_dispatch: + jobs: deploy: runs-on: ubuntu-latest - steps: [uses: fastai/workflows/quarto-ghp@master] + steps: + - uses: actions/checkout@v4 + - uses: quarto-dev/quarto-actions/setup@v2 + - name: Install uv + uses: astral-sh/setup-uv@v4 + - name: Install Python dependencies + run: uv sync --all-extras + - name: Render Quarto site + run: cd nbs && uv run quarto render + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./nbs/_docs diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 5608592..05ede43 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,7 +1,20 @@ name: CI -on: [workflow_dispatch, pull_request, push] + +on: [workflow_dispatch, pull_request, push] jobs: test: runs-on: ubuntu-latest - steps: [uses: fastai/workflows/nbdev-ci@master] + strategy: + matrix: + python-version: ["3.10", "3.11", "3.12"] + steps: + - uses: actions/checkout@v4 + - name: Install uv + uses: astral-sh/setup-uv@v4 + - name: Set up Python ${{ matrix.python-version }} + run: uv python install ${{ matrix.python-version }} + - name: Install dependencies + run: uv sync --all-extras + - name: Run tests + run: uv run pytest tests/ -v diff --git a/.gitignore b/.gitignore index 9fdfe99..375860d 100644 --- a/.gitignore +++ b/.gitignore @@ -131,6 +131,9 @@ dmypy.json # additional files .DS_Store nbs/groups_example* -_proc/groups_example* nbs/api/github_username.csv .idea + +# nbdev build artifacts (removed) +_proc/ +_docs/ diff --git a/CanvasGroupy/_modidx.py b/CanvasGroupy/_modidx.py deleted file mode 100644 index 5fceb8f..0000000 --- a/CanvasGroupy/_modidx.py +++ /dev/null @@ -1,110 +0,0 @@ -# Autogenerated by nbdev - -d = { 'settings': { 'branch': 'main', - 'doc_baseurl': '/CanvasGroupy', - 'doc_host': 'https://FleischerResearchLab.github.io', - 'git_url': 'https://github.com/FleischerResearchLab/CanvasGroupy', - 'lib_path': 'CanvasGroupy'}, - 'syms': { 'CanvasGroupy.assign': { 'CanvasGroupy.assign.AssignGroup': ('api/groupeng_assign.html#assigngroup', 'CanvasGroupy/assign.py'), - 'CanvasGroupy.assign.AssignGroup.__init__': ( 'api/groupeng_assign.html#assigngroup.__init__', - 'CanvasGroupy/assign.py'), - 'CanvasGroupy.assign.AssignGroup.assign_groups': ( 'api/groupeng_assign.html#assigngroup.assign_groups', - 'CanvasGroupy/assign.py'), - 'CanvasGroupy.assign.AssignGroup.create_canvas_group': ( 'api/groupeng_assign.html#assigngroup.create_canvas_group', - 'CanvasGroupy/assign.py'), - 'CanvasGroupy.assign.AssignGroup.create_github_group': ( 'api/groupeng_assign.html#assigngroup.create_github_group', - 'CanvasGroupy/assign.py')}, - 'CanvasGroupy.canvas': { 'CanvasGroupy.canvas.CanvasGroup': ('api/canvas.html#canvasgroup', 'CanvasGroupy/canvas.py'), - 'CanvasGroupy.canvas.CanvasGroup.__init__': ( 'api/canvas.html#canvasgroup.__init__', - 'CanvasGroupy/canvas.py'), - 'CanvasGroupy.canvas.CanvasGroup._check_single_github_username': ( 'api/canvas.html#canvasgroup._check_single_github_username', - 'CanvasGroupy/canvas.py'), - 'CanvasGroupy.canvas.CanvasGroup._progress': ( 'api/canvas.html#canvasgroup._progress', - 'CanvasGroupy/canvas.py'), - 'CanvasGroupy.canvas.CanvasGroup.assign_canvas_group': ( 'api/canvas.html#canvasgroup.assign_canvas_group', - 'CanvasGroupy/canvas.py'), - 'CanvasGroupy.canvas.CanvasGroup.auth_canvas': ( 'api/canvas.html#canvasgroup.auth_canvas', - 'CanvasGroupy/canvas.py'), - 'CanvasGroupy.canvas.CanvasGroup.check_github_usernames': ( 'api/canvas.html#canvasgroup.check_github_usernames', - 'CanvasGroupy/canvas.py'), - 'CanvasGroupy.canvas.CanvasGroup.create_conversation': ( 'api/canvas.html#canvasgroup.create_conversation', - 'CanvasGroupy/canvas.py'), - 'CanvasGroupy.canvas.CanvasGroup.create_group': ( 'api/canvas.html#canvasgroup.create_group', - 'CanvasGroupy/canvas.py'), - 'CanvasGroupy.canvas.CanvasGroup.create_group_category': ( 'api/canvas.html#canvasgroup.create_group_category', - 'CanvasGroupy/canvas.py'), - 'CanvasGroupy.canvas.CanvasGroup.fetch_username_from_quiz': ( 'api/canvas.html#canvasgroup.fetch_username_from_quiz', - 'CanvasGroupy/canvas.py'), - 'CanvasGroupy.canvas.CanvasGroup.get_course': ( 'api/canvas.html#canvasgroup.get_course', - 'CanvasGroupy/canvas.py'), - 'CanvasGroupy.canvas.CanvasGroup.get_email_by_name': ( 'api/canvas.html#canvasgroup.get_email_by_name', - 'CanvasGroupy/canvas.py'), - 'CanvasGroupy.canvas.CanvasGroup.get_group_categories': ( 'api/canvas.html#canvasgroup.get_group_categories', - 'CanvasGroupy/canvas.py'), - 'CanvasGroupy.canvas.CanvasGroup.get_groups': ( 'api/canvas.html#canvasgroup.get_groups', - 'CanvasGroupy/canvas.py'), - 'CanvasGroupy.canvas.CanvasGroup.join_canvas_group': ( 'api/canvas.html#canvasgroup.join_canvas_group', - 'CanvasGroupy/canvas.py'), - 'CanvasGroupy.canvas.CanvasGroup.link_assignment': ( 'api/canvas.html#canvasgroup.link_assignment', - 'CanvasGroupy/canvas.py'), - 'CanvasGroupy.canvas.CanvasGroup.post_grade': ( 'api/canvas.html#canvasgroup.post_grade', - 'CanvasGroupy/canvas.py'), - 'CanvasGroupy.canvas.CanvasGroup.set_course': ( 'api/canvas.html#canvasgroup.set_course', - 'CanvasGroupy/canvas.py'), - 'CanvasGroupy.canvas.CanvasGroup.set_group_category': ( 'api/canvas.html#canvasgroup.set_group_category', - 'CanvasGroupy/canvas.py'), - 'CanvasGroupy.canvas.bcolors': ('api/canvas.html#bcolors', 'CanvasGroupy/canvas.py')}, - 'CanvasGroupy.github': { 'CanvasGroupy.github.GitHubGroup': ('api/github.html#githubgroup', 'CanvasGroupy/github.py'), - 'CanvasGroupy.github.GitHubGroup.__init__': ( 'api/github.html#githubgroup.__init__', - 'CanvasGroupy/github.py'), - 'CanvasGroupy.github.GitHubGroup.add_collaborator': ( 'api/github.html#githubgroup.add_collaborator', - 'CanvasGroupy/github.py'), - 'CanvasGroupy.github.GitHubGroup.add_team': ( 'api/github.html#githubgroup.add_team', - 'CanvasGroupy/github.py'), - 'CanvasGroupy.github.GitHubGroup.auth_github': ( 'api/github.html#githubgroup.auth_github', - 'CanvasGroupy/github.py'), - 'CanvasGroupy.github.GitHubGroup.create_feedback_dir': ( 'api/github.html#githubgroup.create_feedback_dir', - 'CanvasGroupy/github.py'), - 'CanvasGroupy.github.GitHubGroup.create_group_repo': ( 'api/github.html#githubgroup.create_group_repo', - 'CanvasGroupy/github.py'), - 'CanvasGroupy.github.GitHubGroup.create_issue': ( 'api/github.html#githubgroup.create_issue', - 'CanvasGroupy/github.py'), - 'CanvasGroupy.github.GitHubGroup.create_issue_from_md': ( 'api/github.html#githubgroup.create_issue_from_md', - 'CanvasGroupy/github.py'), - 'CanvasGroupy.github.GitHubGroup.create_repo': ( 'api/github.html#githubgroup.create_repo', - 'CanvasGroupy/github.py'), - 'CanvasGroupy.github.GitHubGroup.get_org_repo': ( 'api/github.html#githubgroup.get_org_repo', - 'CanvasGroupy/github.py'), - 'CanvasGroupy.github.GitHubGroup.get_repo': ( 'api/github.html#githubgroup.get_repo', - 'CanvasGroupy/github.py'), - 'CanvasGroupy.github.GitHubGroup.get_team': ( 'api/github.html#githubgroup.get_team', - 'CanvasGroupy/github.py'), - 'CanvasGroupy.github.GitHubGroup.release_feedback': ( 'api/github.html#githubgroup.release_feedback', - 'CanvasGroupy/github.py'), - 'CanvasGroupy.github.GitHubGroup.remove_collaborator': ( 'api/github.html#githubgroup.remove_collaborator', - 'CanvasGroupy/github.py'), - 'CanvasGroupy.github.GitHubGroup.rename_files': ( 'api/github.html#githubgroup.rename_files', - 'CanvasGroupy/github.py'), - 'CanvasGroupy.github.GitHubGroup.resend_invitations': ( 'api/github.html#githubgroup.resend_invitations', - 'CanvasGroupy/github.py'), - 'CanvasGroupy.github.GitHubGroup.resent_invitations_team_repos': ( 'api/github.html#githubgroup.resent_invitations_team_repos', - 'CanvasGroupy/github.py'), - 'CanvasGroupy.github.GitHubGroup.set_org': ( 'api/github.html#githubgroup.set_org', - 'CanvasGroupy/github.py'), - 'CanvasGroupy.github.bcolors': ('api/github.html#bcolors', 'CanvasGroupy/github.py')}, - 'CanvasGroupy.grading': { 'CanvasGroupy.grading.Grading': ('api/project_grading.html#grading', 'CanvasGroupy/grading.py'), - 'CanvasGroupy.grading.Grading.__init__': ( 'api/project_grading.html#grading.__init__', - 'CanvasGroupy/grading.py'), - 'CanvasGroupy.grading.Grading.check_graded': ( 'api/project_grading.html#grading.check_graded', - 'CanvasGroupy/grading.py'), - 'CanvasGroupy.grading.Grading.create_issue_from_md': ( 'api/project_grading.html#grading.create_issue_from_md', - 'CanvasGroupy/grading.py'), - 'CanvasGroupy.grading.Grading.fetch_issue': ( 'api/project_grading.html#grading.fetch_issue', - 'CanvasGroupy/grading.py'), - 'CanvasGroupy.grading.Grading.grade_project': ( 'api/project_grading.html#grading.grade_project', - 'CanvasGroupy/grading.py'), - 'CanvasGroupy.grading.Grading.parse_score_from_issue': ( 'api/project_grading.html#grading.parse_score_from_issue', - 'CanvasGroupy/grading.py'), - 'CanvasGroupy.grading.Grading.update_canvas_score': ( 'api/project_grading.html#grading.update_canvas_score', - 'CanvasGroupy/grading.py'), - 'CanvasGroupy.grading.bcolors': ('api/project_grading.html#bcolors', 'CanvasGroupy/grading.py')}}} diff --git a/CanvasGroupy/assign.py b/CanvasGroupy/assign.py index 6f802f8..c92a7ea 100644 --- a/CanvasGroupy/assign.py +++ b/CanvasGroupy/assign.py @@ -1,15 +1,11 @@ -# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/01_GroupEng_assign.ipynb. - -# %% auto 0 __all__ = ['AssignGroup'] -# %% ../nbs/api/01_GroupEng_assign.ipynb 3 +import os import GroupEng import canvasapi import github from . import GitHubGroup, CanvasGroup -# %% ../nbs/api/01_GroupEng_assign.ipynb 4 class AssignGroup: def __init__(self, ghg: GitHubGroup, # authenticated GitHub object @@ -107,5 +103,3 @@ def create_github_group(self, print("") repos.append(repo) return repos - - diff --git a/CanvasGroupy/canvas.py b/CanvasGroupy/canvas.py index 1c9d6a9..bc446b3 100644 --- a/CanvasGroupy/canvas.py +++ b/CanvasGroupy/canvas.py @@ -1,9 +1,5 @@ -# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/03_canvas.ipynb. - -# %% auto 0 __all__ = ['bcolors', 'CanvasGroup'] -# %% ../nbs/api/03_canvas.ipynb 3 from canvasapi import Canvas from github import Github import canvasapi @@ -15,7 +11,6 @@ import pandas as pd from io import StringIO -# %% ../nbs/api/03_canvas.ipynb 4 class bcolors: HEADER = '\033[95m' OKBLUE = '\033[94m' @@ -27,7 +22,6 @@ class bcolors: BOLD = '\033[1m' UNDERLINE = '\033[4m' -# %% ../nbs/api/03_canvas.ipynb 5 class CanvasGroup(): def __init__(self, credentials_fp = "", # credential file path. [Template of the credentials.json](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/nbs/credentials.json) @@ -54,7 +48,7 @@ def __init__(self, self.group_to_emails = None self.assignment = None self.verbosity = verbosity - + # initialize by the input parameter if credentials_fp != "": self.auth_canvas(credentials_fp) @@ -63,7 +57,7 @@ def __init__(self, self.get_group_categories() if group_category != "": self.set_group_category(group_category) - + def auth_canvas(self, credentials_fp: str # the Authenticator key generated from canvas ): @@ -77,8 +71,8 @@ def auth_canvas(self, _ = self.canvas.get_activity_stream_summary() if self.verbosity != 0: print(f"{bcolors.OKGREEN}Authorization Successful!{bcolors.ENDC}") - - def set_course(self, + + def set_course(self, course_id: int # the course id of the target course ): "Set the target course by the course ID" @@ -145,8 +139,8 @@ def get_email_by_name(self, if name_fussy in name.lower(): return email raise ValueError(f"Name {name_fussy} Not Found.") - - + + def set_group_category(self, category_name: str # the target group category ) -> canvasapi.group.GroupCategory: # target group category object @@ -166,7 +160,7 @@ def set_group_category(self, if self.verbosity != 0: print(f"Group Category: {bcolors.OKGREEN+category_name+bcolors.ENDC} Set!") return self.group_category - + def get_groups(self, category_name="" # the target group category. If not provided, will look for self.group_category ) -> dict: # {group_name: [student_emails]} @@ -176,23 +170,23 @@ def get_groups(self, if self.group_category is None: raise ValueError("Group Category is not set") return self.group_to_emails - + def get_course(self): return self.course - + def get_group_categories(self) -> dict: # return a name / group category object "Grab all existing group categories (group set) in this course" categories = list(self.course.get_group_categories()) self.group_categories = {cat.name: cat for cat in categories} return {cat.name: cat for cat in categories} - + def create_group_category(self, params: dict # the parameter of canvas group category API @ [this link](https://canvas.instructure.com/doc/api/group_categories.html#method.group_categories.create) ) -> canvasapi.group.GroupCategory: # the generated group category object "Create group category (group set) in this course" self.group_category = self.course.create_group_category(**params) return self.group_category - + def create_group(self, params: dict, #the parameter of canvas group create API at [this link](https://canvas.instructure.com/doc/api/groups.html#method.groups.create) ) -> canvasapi.group.Group: # the generated target group object @@ -204,7 +198,7 @@ def create_group(self, print(f"In Group Set: {bcolors.OKBLUE+self.group_category.name+bcolors.ENDC},") print(f"Group {bcolors.OKGREEN+params['name']+bcolors.ENDC} Created!") return group - + def join_canvas_group(self, group: canvasapi.group.Group, # the group that students will join group_members:[str], # list of group member's SIS Login (email prefix, before the @.) @@ -222,7 +216,7 @@ def join_canvas_group(self, print(f"Error adding student {bcolors.WARNING+group_member+bcolors.ENDC} \n into group {group.name}") print(e) return unsuccessful_join - + def fetch_username_from_quiz(self, quiz_id: int, # quiz id of the username quiz col_index=7, # canvas quiz generated csv's question field column index @@ -246,7 +240,7 @@ def fetch_username_from_quiz(self, completed = True if self.verbosity != 0: print(f"\n{bcolors.OKGREEN}Report Generated!{bcolors.ENDC}") - # use requests to download the file + # use requests to download the file file_url = quiz.create_report("student_analysis").file["url"] response = requests.get(file_url, headers=header) file = StringIO(response.content.decode()) @@ -265,7 +259,7 @@ def fetch_username_from_quiz(self, small["email"] = small["id"].apply(lambda x: self.canvas_id_to_email[x]) small = small[["email", "GitHub Username"]].set_index("email") return small.to_dict()["GitHub Username"] - + def _check_single_github_username(self, email:str, # Student email github_username:str, # student input we want to test @@ -332,7 +326,7 @@ def check_github_usernames(self, if self.verbosity != 0: print(f"{bcolors.OKGREEN}Notification Sent!{bcolors.ENDC}") return unsuccessful - + def _progress(self, percentage:int # percentage of the progress ): @@ -340,7 +334,7 @@ def _progress(self, # the exact output you're looking for: sys.stdout.write("[%-20s] %d%%" % ('='*int(percentage//5), percentage)) sys.stdout.flush() - + def assign_canvas_group(self, group_name: str, # group name, display on canvas group_members:[str], # list of group member's SIS Login @@ -353,9 +347,9 @@ def assign_canvas_group(self, if self.verbosity != 0: print(f"Group {bcolors.OKGREEN+group_name+bcolors.ENDC} created!") return group, unsuccessful_join - + def create_conversation(self, - recipients:int, # recipient ids. These may be user ids or course/group ids prefixed with ‘course_’ or ‘group_’ respectively. + recipients:int, # recipient ids. These may be user ids or course/group ids prefixed with 'course_' or 'group_' respectively. subject:str, # subject of the conversation body:str, # The message to be sent ) -> canvasapi.conversation.Conversation: # created conversation @@ -367,4 +361,3 @@ def create_conversation(self, context_code=f"course_{self.course.id}", ) return conv - diff --git a/CanvasGroupy/github.py b/CanvasGroupy/github.py index 43ca282..1b159a5 100644 --- a/CanvasGroupy/github.py +++ b/CanvasGroupy/github.py @@ -1,9 +1,5 @@ -# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/02_github.ipynb. - -# %% auto 0 __all__ = ['bcolors', 'GitHubGroup'] -# %% ../nbs/api/02_github.ipynb 3 from github import Github import github import json @@ -12,7 +8,6 @@ import glob from pprint import pprint -# %% ../nbs/api/02_github.ipynb 4 class bcolors: HEADER = '\033[95m' OKBLUE = '\033[94m' @@ -24,7 +19,6 @@ class bcolors: BOLD = '\033[1m' UNDERLINE = '\033[4m' -# %% ../nbs/api/02_github.ipynb 5 class GitHubGroup: def __init__(self, credentials_fp="", # the file path to the credential json @@ -34,7 +28,7 @@ def __init__(self, self.github = None self.org = None self.verbosity = verbosity - + if credentials_fp != "": self.auth_github(credentials_fp) if org != "": @@ -52,7 +46,7 @@ def auth_github(self, if self.verbosity != 0: print(f"Successfully Authenticated. " f"GitHub account: {bcolors.OKGREEN} {self.github.get_user().login} {bcolors.ENDC}") - + def set_org(self, org: str # the target organization name ): @@ -88,7 +82,7 @@ def create_repo(self, private=private, description=description, ) - + def get_repo(self, repo_full_name: str # full name of the target repository ) -> github.Repository.Repository: @@ -97,14 +91,14 @@ def get_repo(self, return self.github.get_repo(repo_full_name) except Exception: return self.org.get_repo(repo_full_name) - + def get_org_repo(self, repo_full_name: str # full name of the target repository ) -> github.Repository.Repository: "Get a repository within the target organization" return self.org.get_repo(repo_full_name) - + def get_team(self, team_slug:str # team slug of the team ) -> github.Team.Team: @@ -112,7 +106,7 @@ def get_team(self, if self.org is None: raise ValueError("The organization has not been set. Please set it via g.set_org") return self.org.get_team_by_slug(team_slug) - + def rename_files(self, repo: github.Repository.Repository, # the repository that we want to rename file og_filename: str, # old file name @@ -126,7 +120,7 @@ def rename_files(self, print(f"File Successfully Renamed from " f" {bcolors.OKCYAN} {og_filename} {bcolors.ENDC} " f" to {bcolors.OKGREEN} {new_filename} {bcolors.ENDC}") - + def add_collaborator(self, repo: github.Repository.Repository, # target repository collaborator:str, # GitHub username of the collaborator @@ -141,14 +135,14 @@ def add_collaborator(self, print(f"Added Collaborator: {bcolors.OKGREEN} {collaborator} {bcolors.ENDC}" f" to: {bcolors.OKGREEN} {repo.name} {bcolors.ENDC} with " f"permission: {bcolors.OKGREEN} {permission} {bcolors.ENDC}") - + def remove_collaborator(self, repo: github.Repository.Repository, # target repository collaborator:str, # GitHub username of the collaborator ): "Remove collaborator privileges from the repository" repo.remove_from_collaborators(collaborator) - + def resend_invitations(self, repo: github.Repository.Repository, # target repository ) -> [github.NamedUser.NamedUser]: # list of re-invited user @@ -181,7 +175,7 @@ def resent_invitations_team_repos(self, except Exception as e: print(f"{bcolors.WARNING}Make sure to have proper rights to the target repo{bcolors.ENDC}\n") print(e) - + def add_team(self, repo: github.Repository.Repository, # target repository team_slug: str, # team slug (name) @@ -227,7 +221,7 @@ def create_issue(self, print(f"In the repo: {bcolors.OKGREEN}{repo.name}{bcolors.ENDC},") print(f"Issue {bcolors.OKGREEN}{title}{bcolors.ENDC} Created!") return issue - + def create_issue_from_md(self, repo: github.Repository.Repository, # target repository, md_fp: str # file path of the feedback markdown file @@ -239,7 +233,7 @@ def create_issue_from_md(self, title = md.split("\n")[0][2:] content = md return self.create_issue(repo, title, content) - + def release_feedback(self, md_filename: str, # feedback markdown file name feedback_dir="feedback", # feedback directory contains the markdown files @@ -254,7 +248,7 @@ def release_feedback(self, except Exception: print(f"Repo: {bcolors.WARNING}{repo_name} NOT FOUND!{bcolors.ENDC}") self.create_issue_from_md(repo, os.path.join(feedback_dir, repo_name, md_filename)) - + def create_group_repo(self, repo_name: str, # group repository name collaborators: [str], # list of collaborators GitHub id @@ -287,4 +281,3 @@ def create_group_repo(self, raise ValueError("You have to specify the template files.") self.create_feedback_dir(repo, template_fp=feedback_template_fp) return repo - diff --git a/CanvasGroupy/grading.py b/CanvasGroupy/grading.py index 13d33fd..4f30595 100644 --- a/CanvasGroupy/grading.py +++ b/CanvasGroupy/grading.py @@ -1,15 +1,10 @@ -# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/04_project_grading.ipynb. - -# %% auto 0 __all__ = ['bcolors', 'Grading'] -# %% ../nbs/api/04_project_grading.ipynb 3 from . import * import github import canvasapi from ast import literal_eval -# %% ../nbs/api/04_project_grading.ipynb 4 class bcolors: HEADER = '\033[95m' OKBLUE = '\033[94m' @@ -21,7 +16,6 @@ class bcolors: BOLD = '\033[1m' UNDERLINE = '\033[4m' -# %% ../nbs/api/04_project_grading.ipynb 5 class Grading: def __init__(self, ghg:GitHubGroup=None, # authenticated GitHub object @@ -125,5 +119,3 @@ def grade_project(self, return issue = self.fetch_issue(repo, component) self.update_canvas_score(group_name, assignment_id, score, issue, post) - - diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 5c0e7ce..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,5 +0,0 @@ -include settings.ini -include LICENSE -include CONTRIBUTING.md -include README.md -recursive-exclude * __pycache__ diff --git a/README.md b/README.md index f423def..3715fc2 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ CanvasGroupy ================ - - View our documentation [at this link](https://fleischerresearchlab.github.io/CanvasGroupy/) diff --git a/_proc/.gitignore b/_proc/.gitignore deleted file mode 100644 index 075b254..0000000 --- a/_proc/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/.quarto/ diff --git a/_proc/_docs/api/canvas.html b/_proc/_docs/api/canvas.html deleted file mode 100644 index 8a95a79..0000000 --- a/_proc/_docs/api/canvas.html +++ /dev/null @@ -1,753 +0,0 @@ - - - - - - - - - - -CanvasGroupy - Canvas Group Creation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - -
- -
- - - - - -
- -
-
-

Canvas Group Creation

-
- -
-
- Create canvas group via Canvas API -
-
- - -
- - - - -
- - -
- - -
-

source

-
-

CanvasGroup

-
-
 CanvasGroup (credentials_fp='', API_URL='https://canvas.ucsd.edu',
-              course_id='', group_category='', verbosity=1)
-
-

Initialize Canvas Group within a Group Set and its appropriate memberships

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeDefaultDetails
credentials_fpstrcredential file path. Template of the credentials.json
API_URLstrhttps://canvas.ucsd.eduthe domain name of canvas
course_idstrCourse ID, can be found in the course url
group_categorystrtarget group category (set) of interests
verbosityint1Controls the verbosity: 0 = Silent, 1 = print all messages
-
-
-

Lower Level Methods

-

Alternatively, you can manually set them after you created the CanvasGroup object

-
-

source

-
-

CanvasGroup.auth_canvas

-
-
 CanvasGroup.auth_canvas (credentials_fp:str)
-
-

Authorize the canvas module with API_KEY

- - - - - - - - - - - - - - - -
TypeDetails
credentials_fpstrthe Authenticator key generated from canvas
-
-

source

-
-
-

CanvasGroup.set_course

-
-
 CanvasGroup.set_course (course_id:int)
-
-

Set the target course by the course ID

- - - - - - - - - - - - - - - -
TypeDetails
course_idintthe course id of the target course
-

The following tutorial and examples demonstrates how to create and set a Group Category within a course context.

-
-
-

Create / Set Target Group Category (Set)

-
-

source

-
-

CanvasGroup.get_group_categories

-
-
 CanvasGroup.get_group_categories ()
-
-

Grab all existing group category (group set) in this course

-
-
# list all current group category
-list(cg.get_group_categories().keys())
-
-
['Final Project', 'Student Groups', 'Test']
-
-
-
-

source

-
-
-

CanvasGroup.create_group_category

-
-
 CanvasGroup.create_group_category (params:dict)
-
-

Create group category (group set) in this course

- - - - - - - - - - - - - - - - - - - - -
TypeDetails
paramsdictthe parameter of canvas group category API @ this link
ReturnsGroupCategorythe generated group category object
-
-
params = {
-    "name": "TEST-GroupProject",
-    "group_limit": 5
-}
-
-
-
# create a new category
-group_category = cg.create_group_category(params)
-
-
-
# Check whether we successfully create a new group
-list(cg.get_group_categories().keys())
-
-
['Final Project', 'Student Groups', 'Test', 'TEST-GroupProject']
-
-
-

When a group category is already created, we cannot create another group with the same name. To switch the group category destination of group creation, use the set_group_category methods.

-
-

source

-
-
-

CanvasGroup.set_group_category

-
-
 CanvasGroup.set_group_category (category_name:str)
-
- - - - - - - - - - - - - - - - - - - - -
TypeDetails
category_namestrthe target group category
ReturnsGroupCategorytarget group category object
-
-
group_category = cg.set_group_category("TEST-GroupProject")
-
-
-
-
-

Create a Group Inside the Target Group Category

-
-

source

-
-

CanvasGroup.create_group

-
-
 CanvasGroup.create_group (params:dict)
-
-

Create canvas group under the target group category

- - - - - - - - - - - - - - - - - - - - -
TypeDetails
paramsdictthe parameter of canvas group create API at this link
ReturnsGroupthe generated target group object
-
-
params = {
-    "name": "TEST-GROUP1",
-    "join_level": "invitation_only"
-}
-group1 = cg.create_group(params)
-print(group1)
-
-
In Group Set: TEST-GroupProject,
-Group TEST-GROUP1 Created!
-TEST-GROUP1 (122854)
-
-
-
-
-
-

Assign Student to the Group

-
-

source

-
-

CanvasGroup.join_canvas_group

-
-
 CanvasGroup.join_canvas_group (group:canvasapi.group.Group,
-                                group_members:[<class'str'>])
-
-

Add membership access of each group member into the group

- ----- - - - - - - - - - - - - - - - - - - - - - - - - -
TypeDetails
groupGroupthe group that students will join
group_members[<class ‘str’>]list of group member’s SIS Login (email prefix, before the @.)
Returns[<class ‘str’>]list of unsuccessful join
-
-
member1 = "email"
-
-cg.join_canvas_group(group1, [member1])
-
-
[]
-
-
- - -
-
-
- -
- -
- - - - \ No newline at end of file diff --git a/_proc/_docs/api/github.html b/_proc/_docs/api/github.html deleted file mode 100644 index d1fe900..0000000 --- a/_proc/_docs/api/github.html +++ /dev/null @@ -1,1360 +0,0 @@ - - - - - - - - - - -CanvasGroupy - GitHub Group Creation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - -
- -
- - - - - -
- -
-
-

GitHub Group Creation

-
- -
-
- Create GitHub group based on the provided group information -
-
- - -
- - - - -
- - -
- - -
-

source

-
-

GitHubGroup

-
-
 GitHubGroup (credentials_fp='', org='', verbosity=1)
-
-

Initialize self. See help(type(self)) for accurate signature.

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeDefaultDetails
credentials_fpstrthe file path to the credential json
orgstrthe organization name
verbosityint1Controls the verbosity: 0=silent, 1=print status
-
-
-

GitHub Authentication

-

View this document for how to set up your GitHub Personal Access Token. (TODO: be sure to specify scopes)

-

Store the token information in a json file.

-
-
credentials_fp = "credentials.json"
-with open(credentials_fp, "r") as f:
-    # take a look at the token
-    print(f.read())
-g = GitHubGroup(credentials_fp=credentials_fp, org="COGS118A", verbosity=1)
-
-
{
-  "GitHub Token": "token",
-  "Canvas Token": "token"
-}
-Successfully Authenticated. GitHub account:  scott-yj-yang 
-Target Organization Set:  COGS118A 
-
-
-

Optionally, you can instansiate a GitHubGroup object and authenticate yourself by calling the following method.

-
-

source

-
-

GitHubGroup.auth_github

-
-
 GitHubGroup.auth_github (credentials_fp:str)
-
-

Authenticate GitHub account with the provided credentials

- ----- - - - - - - - - - - - - - - -
TypeDetails
credentials_fpstrthe personal access token generated at GitHub Settings
-
-
g = GitHubGroup(verbosity=1) # you can set verbosity to 1 to see the current progress
-g.auth_github(credentials_fp)
-
-
Successfully Authenticated. GitHub account:  scott-yj-yang 
-
-
-
-
-
-

GitHub Organization Settings

-

Usually, you want to create students repositories under a course GitHub organization. To set the target organization, you can call the following function.

-
-

source

-
-

GitHubGroup.set_org

-
-
 GitHubGroup.set_org (org:str)
-
-

Set the target organization for repo creation

- - - - - - - - - - - - - - - -
TypeDetails
orgstrthe target organization name
-
-
g.set_org("COGS118A")
-
-
Target Organization Set:  COGS118A 
-
-
-
-
-
-

Create GitHub Group in one Call

-
-

source

-
-

GitHubGroup.create_group_repo

-
-
 GitHubGroup.create_group_repo (repo_name:str,
-                                collaborators:[<class'str'>],
-                                permission:str, rename_files={},
-                                repo_template='', private=True,
-                                description='', team_slug='',
-                                team_permission='', feedback_dir=False,
-                                feedback_template_fp='')
-
-

Create a Group Repository

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeDefaultDetails
repo_namestrgroup repository name
collaborators[<class ‘str’>]list of collaborators GitHub id
permissionstrthe permission of collaborators. pull, push or admin
rename_filesdict{}dictionary of files renames {:}
repo_templatestrIf empty string, an empty repo will be created. Put in the format of “/
privateboolTruevisibility of the created repository
descriptionstrdescription for the GitHub repository
team_slugstrteam slug, add to this repo
team_permissionstrteam permission to this repository pull, push or admin
feedback_dirboolFalsewhether to create a feedback directory for each repository created
feedback_template_fpstrthe directory of the feedback template
ReturnsRepositorycreated repository
-
-
repo = g.create_group_repo(
-    repo_name="API_test_repo",
-    collaborators=["jasongfleischer"],
-    permission="admin",
-    repo_template="COGS118A/group_template",
-    rename_files={
-        "Checkpoint_groupXXX.ipynb": "Checkpoint_group001.ipynb",
-        "FinalProject_groupXXX.ipynb": "FinalProject_group001.ipynb",
-        "Proposal_groupXXX.ipynb": "Proposal_group001.ipynb"
-    },
-    private=False,
-    description="Test Creation of Group Repo for COGS118A final project group",
-    team_slug="Instructors_Sp23",
-    team_permission="admin"
-)
-
-
Repo  API_test_repo  Created... Wait for 3 sec to updates
-File Successfully Renamed from   Checkpoint_groupXXX.ipynb   to  Checkpoint_group001.ipynb 
-File Successfully Renamed from   FinalProject_groupXXX.ipynb   to  FinalProject_group001.ipynb 
-File Successfully Renamed from   Proposal_groupXXX.ipynb   to  Proposal_group001.ipynb 
-Added Collaborator:  jasongfleischer  to:  API_test_repo  with permission:  admin 
-Team  Instructors_Sp23  added to  API_test_repo  with permission  admin 
-Group Repo:  API_test_repo  successfuly created!
-
-
-
-
-
-

Lower Level Methods

-

Belows are the components that faciliate the GitHubGroup.create_group_repo. If errors were prompted during group creation scripts, or simply you want more flexibility, you can call those components individually.

-
-

Create GitHub Repository

-

Note: GtiHubGroup.create_group_repo call this method to create a GitHub repository

-

personal_account argument controls the location of the repository creation. If set to False (default), it will create repository in the target organization. If set to True, the new repository will be created in the personal GitHub account.

-
-

source

-
-

GitHubGroup.create_repo

-
-
 GitHubGroup.create_repo (repo_name:str, repo_template='', private=True,
-                          description='', personal_account=False)
-
-

Create a repository, either blank, or from a template

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeDefaultDetails
repo_namestrrepository name
repo_templatestrtemplate repository that new repo will use. If empty string, an empty repo will be created. Put in the format of “/
privateboolTruevisibility of the created repository
descriptionstrdescription for the GitHub repository
personal_accountboolFalsecreate repos in personal GitHub account
ReturnsRepository
-

Note: GtiHubGroup.create_group_repo call this method to create a GitHub repository

-

personal_account argument controls the location of the repository creation. If set to False (default), it will create repository in the target organization. If set to True, the new repository will be created in the personal GitHub account.

-
-
# create a repo under the org
-repo = g.create_repo(
-    "test-repo-organizational",
-    private=True
-)
-print(repo)
-
-
Repository(full_name="COGS118A/test-repo-organizational")
-
-
-

As you can see from the full name COGS118A/test-repo, it is created under the organization of COGS118A.

-

Alternatively, I can also create a new repository under my personal account.

-
-
# create a repo under the my personal account
-repo = g.create_repo(
-    "test-repo-personal",
-    private=True,
-    personal_account=True
-)
-print(repo)
-
-
Repository(full_name="scott-yj-yang/test-repo-personal")
-
-
-
-
# create a repo under the my personal account
-repo = g.create_repo(
-    repo_name="test-repo-personal",
-    private=True,
-    personal_account=True
-)
-print(repo)
-
-
Repository(full_name="scott-yj-yang/test-repo-personal")
-
-
-
-
-

Create Repository from Template

-

You can also create a repository with a template repository. To do that, specify the full name of the template repository to the repo_template parameter. From the output, we can see that the repository test-repo-from-template is created with the template files.

-
-
# create a repo from a template
-repo = g.create_repo(
-    repo_name="test-repo-from-template",
-    repo_template="COGS118A/group_template",
-    private=True
-)
-print(repo)
-
-# wait 3 sec for repository creation.
-time.sleep(3)
-
-print("\nThis Repository contains... \n")
-pprint(repo.get_contents("."))
-
-
Repository(full_name="COGS118A/test-repo-from-template")
-
-This Repository contains... 
-
-[ContentFile(path=".gitignore"),
- ContentFile(path="Checkpoint_groupXXX.ipynb"),
- ContentFile(path="FinalProject_groupXXX.ipynb"),
- ContentFile(path="Proposal_groupXXX.ipynb"),
- ContentFile(path="README.md")]
-
-
-
-
-
-

Rename Files in the Repository

-

Usually, the template file names are generics and is not specific to a group. Under the context of group project, we want to rename each notebook files with thier group numbers. For example, for Group 1, we want to rename the file Checkpoint_groupXXX.ipynb to Checkpoint_group001.ipynb. To do that, we can use the following method.

-
-

source

-
-

GitHubGroup.rename_files

-
-
 GitHubGroup.rename_files (repo:github.Repository.Repository,
-                           og_filename:str, new_filename:str)
-
-

Rename the file by delete the old file and commit the new file

- - - - - - - - - - - - - - - - - - - - - - - - - -
TypeDetails
repoRepositorythe repository that we want to rename file
og_filenamestrold file name
new_filenamestrnew file name
-

Note: This method simply delete the old files and create new files with the updated file name. Therefore, 2 commits for each file is expected. (1 for delete, 1 for re-upload). For example, if I want to rename 5 files, I will have 10 commits need to do in total.

-
-
g.rename_files(
-    repo=repo,
-    og_filename="Checkpoint_groupXXX.ipynb",
-    new_filename="Checkpoint_group001.ipynb"
-)
-# take a look at new files
-print("\nThis Repository contains... \n")
-pprint(repo.get_contents("."))
-
-
File Successfully Renamed from   Checkpoint_groupXXX.ipynb   to  Checkpoint_group001.ipynb 
-
-This Repository contains... 
-
-[ContentFile(path=".gitignore"),
- ContentFile(path="Checkpoint_group001.ipynb"),
- ContentFile(path="FinalProject_groupXXX.ipynb"),
- ContentFile(path="Proposal_groupXXX.ipynb"),
- ContentFile(path="README.md")]
-
-
-

Notice that the files were renamed as expected.

-
-
-
-

Collaborators and Teams Access Repository

-

Once the repository was created, we need to give the student team members proper permission to write to the repository and instructional team to be the admin of the repository. Those two functionalities are achieve by the following two methods.

-
-

source

-
-

GitHubGroup.add_collaborator

-
-
 GitHubGroup.add_collaborator (repo:github.Repository.Repository,
-                               collaborator:str, permission:str)
-
-

Add collaborator to the repository with specified permission

- - - - - - - - - - - - - - - - - - - - - - - - - -
TypeDetails
repoRepositorytarget repository
collaboratorstrGitHub username of the collaborator
permissionstrpull, push or admin
-
-
# add collaborator to the repository with push permission
-g.add_collaborator(
-    repo=repo,
-    collaborator="Andrina-iris",
-    permission="write"
-)
-
-
Added Collaborator:  Andrina-iris  to:  test-repo-from-template  with permission:  write 
-
-
-

In almost every quarter, at least 10 students forgot to accept the invitation to join the group repository. Historially, our instructional team handle those student’s GitHub account on a case-by-case basis. However, with this module, it is possible to resent all pending and expired invitations to those students with one call.

-
-

source

-
-
-

GitHubGroup.resend_invitations

-
-
 GitHubGroup.resend_invitations (repo:github.Repository.Repository)
-
-

Resent Invitation to invitee who did not accept the invitation

- ----- - - - - - - - - - - - - - - - - - - - -
TypeDetails
repoRepositorytarget repository
Returns[<class ‘github.NamedUser.NamedUser’>]list of re-invited user
-

Students will receive an email from GitHub with the freshly made, unexpired invitation to their group repository.

-
-
g.resend_invitations(repo)
-
-
The list of pending invitation:
-[NamedUser(login="Andrina-iris")]
- Andrina-iris Invite Revoked Andrina-iris 
-Added Collaborator:  Andrina-iris  to:  test-repo-from-template  with permission:  write 
- Invite Resent to Andrina-iris 
-
-
-
[NamedUser(login="Andrina-iris")]
-
-
-

Additionally, course staffs should be in a team in the GitHub Organization in order to manage student repositories.

-
-

source

-
-
-

GitHubGroup.add_team

-
-
 GitHubGroup.add_team (repo:github.Repository.Repository, team_slug:str,
-                       permission:str)
-
-

Add team to the repository with specified permission

- - - - - - - - - - - - - - - - - - - - - - - - - -
TypeDetails
repoRepositorytarget repository
team_slugstrteam slug (name)
permissionstrpull, push or admin
-
-
g.add_team(
-    repo=repo,
-    team_slug="Instructors_Sp23",
-    permission="admin"
-)
-
-
Team  Instructors_Sp23  added to  test-repo-from-template  with permission  admin 
-
-
-

If you have all the students repositories under the same team, you can use the following method to resent all pending invitations from all the repositories under that team.

-
-

source

-
-
-

GitHubGroup.resent_invitations_team_repos

-
-
 GitHubGroup.resent_invitations_team_repos (team_slug:str)
-
-

For all repository under that team, Resent invitation to invitee who did not accept the invitation

- - - - - - - - - - - - - - - -
TypeDetails
team_slugstrteam slug (name) under the org
-
-
g.resent_invitations_team_repos(team_slug="Instructors_Sp23")
-
-
Repository  AssignmentNotebooksSource_SP23 :
-The list of pending invitation:
-[]
-Repository  AssignmentNotebooks_SP23 :
-The list of pending invitation:
-[]
-Repository  DiscussionSectionNotebooks :
-The list of pending invitation:
-[]
-Repository  Dockerfiles :
-The list of pending invitation:
-[]
-Repository  Lectures :
-The list of pending invitation:
-[]
-Repository  Notebooks :
-The list of pending invitation:
-[]
-Repository  test-repo-from-template :
-The list of pending invitation:
-[NamedUser(login="Andrina-iris")]
- Andrina-iris Invite Revoked Andrina-iris 
-Added Collaborator:  Andrina-iris  to:  test-repo-from-template  with permission:  write 
- Invite Resent to Andrina-iris 
-
-
-
-
-
-

Project Feedback and GitHub Issues

-

Thanks for the group nature of the created repository, we can also use the created GitHub repository to create group project feedback to students via GitHub issues.

-

We can create a directory under the instructors’ computer. Each directory will have a folder for each group repository with the template file. The template files are usually the rubrics for the project grading.

-

TODO: add file superlink to the repo to see the examples.

-
-

source

-
-

GitHubGroup.create_feedback_dir

-
-
 GitHubGroup.create_feedback_dir (repo:github.Repository.Repository,
-                                  template_fp:str, destination='feedback')
-
-

Create feedback directory on local machine

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeDefaultDetails
repoRepositorytarget repository
template_fpstr
destinationstrfeedbackdirectory path of the template file.
-
-
g.create_feedback_dir(repo, "feedback_template")
-# take a look at the generated tempalte files.
-os.listdir(f"feedback/{repo.name}")
-
-
File checkpoint_feedback.md created at feedback/test-repo-from-template
-File proposal_feedback.md created at feedback/test-repo-from-template
-File final_project_feedback.md created at feedback/test-repo-from-template
-
-
-
['checkpoint_feedback.md', 'proposal_feedback.md', 'final_project_feedback.md']
-
-
-

After created the project feedback, instructional team can modify the markdown rubrics and provide feedback to students via GitHub issue.

-
-

source

-
-
-

GitHubGroup.create_issue

-
-
 GitHubGroup.create_issue (repo:github.Repository.Repository, title:str,
-                           content:str)
-
-

Create GitHub issue to the target repository

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeDetails
repoRepositorytarget repository
titlestrtitle of the issue,
contentstrcontent of the issue
ReturnsIssueopen issue
-
-
# create a test issue
-issue = g.create_issue(repo, "Test Issue", "This is just a test issue.")
-issue
-
-
In the repo: test-repo-from-template,
-Issue Test Issue Created!
-
-
-
Issue(title="Test Issue", number=1)
-
-
-

Alternatively, you can create issue from markdown files, where it contains all the comments and rubrics for this project. The first line of the markdown file will be the title of the github issue.

-
-

source

-
-
-

GitHubGroup.create_issue_from_md

-
-
 GitHubGroup.create_issue_from_md (repo:github.Repository.Repository,
-                                   md_fp:str)
-
-

Create GitHub issue from markdown file.

- - - - - - - - - - - - - - - - - - - - - - - - - -
TypeDetails
repoRepositorytarget repository,
md_fpstrfile path of the feedback markdown file
ReturnsIssueopen issue
-
-
issue = g.create_issue_from_md(repo, "feedback_template/proposal_feedback.md")
-issue
-
-
In the repo: test-repo-from-template,
-Issue  Project Proposal Feedback Created!
-
-
-
Issue(title=" Project Proposal Feedback", number=2)
-
-
-
-
-

Release Feedback in Batch

-

During projct grading, we will handle numerous groups at once. Once the instructor team finish modifying the markdown file for each group, we can release feedback to each of the project repository, as long as they have the same file name. For example, we have finish grading the final project, in the file name of feedback/<repo_name>/final_project_feedback.md, and they are ready to be publish, we can call the following function to create issue in batch.

-
-

source

-
-
-

GitHubGroup.release_feedback

-
-
 GitHubGroup.release_feedback (md_filename:str, feedback_dir='feedback')
-
-

Release feedback via GitHub issue from all the feedbacks in the feedback directory

- ------ - - - - - - - - - - - - - - - - - - - - - - -
TypeDefaultDetails
md_filenamestrfeedback markdown file name
feedback_dirstrfeedbackfeedback directory contains the markdown files
-
-
g.release_feedback("final_project_feedback.md")
-
-
In the repo: test-repo-from-template,
-Issue  Final Project Feedback Created!
-
-
- - -
-
-
- -
- -
- - - - \ No newline at end of file diff --git a/_proc/_docs/api/groupeng_assign.html b/_proc/_docs/api/groupeng_assign.html deleted file mode 100644 index 4a301c8..0000000 --- a/_proc/_docs/api/groupeng_assign.html +++ /dev/null @@ -1,707 +0,0 @@ - - - - - - - - - - -CanvasGroupy - GroupEngAssign - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - -
- -
- - - - - -
- -
-
-

GroupEngAssign

-
- -
-
- Invoke Package Group Eng to Assign Students in Groups -
-
- - -
- - - - -
- - -
- - -
-

source

-
-

AssignGroup

-
-
 AssignGroup (ghg:CanvasGroupy.github.GitHubGroup,
-              cg:CanvasGroupy.canvas.CanvasGroup, groupeng_config='')
-
-

Initializer for Assign Group

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeDefaultDetails
ghgGitHubGroupauthenticated GitHub object
cgCanvasGroupauthenticated canvas object
groupeng_configstrDirectory for the GroupEng config yml file
-
-
-

API

-
-

source

-
-

AssignGroup.assign_groups

-
-
 AssignGroup.assign_groups (groupeng_config:str,
-                            assign_canvas_group=False,
-                            create_gh_repo=False, username_quiz_id=-1,
-                            in_group_category='', suffix='')
-
- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeDefaultDetails
groupeng_configstrDirectory for the GroupEng config yml file
assign_canvas_groupboolFalsedirectly assign canvas groups
create_gh_repoboolFalsedirectly create GitHub repos
username_quiz_idint-1username quiz id from canvas course
in_group_categorystrspecify which group category the group belongs to
suffixstrsuffix to the group name
Returns(<class ‘bool’>, <class ‘str’>)Status and output directory of the compiled file.
-
-

source

-
-
-

AssignGroup.create_canvas_group

-
-
 AssignGroup.create_canvas_group (in_group_category='', suffix='')
-
-

Create canvas groups based on the generated group configuration

- ------ - - - - - - - - - - - - - - - - - - - - - - -
TypeDefaultDetails
in_group_categorystrspecify which group category the group belongs to
suffixstrsuffix to the group name
-
-

source

-
-
-

AssignGroup.create_github_group

-
-
 AssignGroup.create_github_group (username_quiz_id:int)
-
- - - - - - - - - - - - - - - -
TypeDetails
username_quiz_idintusername quiz id from canvas course
-
-
# Create authenticated objects
-ghg = GitHubGroup("../../../credentials.json",
-                 "COGS118A"
-                 )
-cg = CanvasGroup("../../../credentials.json",
-                 course_id=45532,
-                 )
-# create assign group object
-ag = AssignGroup(ghg, cg)
-
-
Successfully Authenticated. GitHub account:  scott-yj-yang 
-Target Organization Set:  COGS118A 
-Authorization Successful!
-Course Set:  COGS 195 - Instructional Apprenticeship - Fleischer [SP23] 
-Getting List of Users... This might take a while...
-Users Fetch Complete! The course has 5 students.
-
-
-
-
# create a group category to hold students
-cg.create_group_category({"name": "Project 1"})
-
-
GroupCategory(_requester=<canvasapi.requester.Requester object>, id=16456, name=Project 1, role=None, self_signup=None, group_limit=None, auto_leader=None, created_at=2023-05-17T20:38:56Z, created_at_date=2023-05-17 20:38:56+00:00, context_type=Course, course_id=45532, groups_count=0, unassigned_users_count=5, protected=False, allows_multiple_memberships=False, is_member=False)
-
-
-
-
# assign, create both Canvas and GitHub Group in one call
-status, out_dir = ag.assign_groups("../data/195_group_specification.groupeng",
-                                   assign_canvas_group=True,
-                                   create_gh_repo=True,
-                                   username_quiz_id=139925,
-                                   in_group_category="Project 1",
-                                   suffix="-SP23-Testing"
-                                   )
-
-
['H', 'B', '-']
-['B', 'H', '-']
-['-', 'H', 'B']
-['B', 'H', '-']
-['B', '-', 'H']
-['-', 'B', 'H']
-[None, 3.9, 3.1]
-[3.9, 3.1, None]
-[3.4, 2.5, 2.1]
-[3.9, None, 3.1]
-[3.4, 2.1, 2.5]
-[3.4, 2.1, 2.5]
-In Group Set: Project 1,
-Group Group1-SP23-Testing Created!
-Member dol005 Joined group Group1-SP23-Testing
-Member xiw013 Joined group Group1-SP23-Testing
-In Group Set: Project 1,
-Group Group2-SP23-Testing Created!
-Member jiz088 Joined group Group2-SP23-Testing
-Member jiz100 Joined group Group2-SP23-Testing
-Member nmackler Joined group Group2-SP23-Testing
-Quiz: GitHub Username fetch! 
-Generating Student Analaysis...
-[====================] 100%
-Report Generated!
-The Question asked is 1399692: In plain text, what is your GitHub Username? Absolutely no typo, no extra space, no hyperlink please.. 
-Make sure this is the correct question where you asked student for their GitHub id.
-If you need to change the index of columns, change the col_index argument of this call.
-dol005's GitHub Username not found
-xiw013's GitHub Username not found
-Repo  Group1-SP23-Testing  Created... Wait for 3 sec to updates
-File Successfully Renamed from   Checkpoint_groupXXX.ipynb   to  Checkpoint_Group1-SP23-Testing.ipynb 
-File Successfully Renamed from   FinalProject_groupXXX.ipynb   to  FinalProject_Group1-SP23-Testing.ipynb 
-File Successfully Renamed from   Proposal_groupXXX.ipynb   to  Proposal_Group1-SP23-Testing.ipynb 
-Team  Instructors_Sp23  added to  Group1-SP23-Testing  with permission  admin 
-Group Repo:  Group1-SP23-Testing  successfuly created!
-Repo URL: https://github.com/COGS118A/Group1-SP23-Testing
-
-jiz100's GitHub Username not found
-jiz088's GitHub Username not found
-Repo  Group2-SP23-Testing  Created... Wait for 3 sec to updates
-File Successfully Renamed from   Checkpoint_groupXXX.ipynb   to  Checkpoint_Group2-SP23-Testing.ipynb 
-File Successfully Renamed from   FinalProject_groupXXX.ipynb   to  FinalProject_Group2-SP23-Testing.ipynb 
-File Successfully Renamed from   Proposal_groupXXX.ipynb   to  Proposal_Group2-SP23-Testing.ipynb 
-Added Collaborator:  nmackler  to:  Group2-SP23-Testing  with permission:  write 
-Team  Instructors_Sp23  added to  Group2-SP23-Testing  with permission  admin 
-Group Repo:  Group2-SP23-Testing  successfuly created!
-Repo URL: https://github.com/COGS118A/Group2-SP23-Testing
-
-
-
-

The false means that at least one requirement is not satisfied. We can take a look at the file that was generated.

- - -
-
- -
- -
- - - - \ No newline at end of file diff --git a/_proc/_docs/api/index.html b/_proc/_docs/api/index.html deleted file mode 100644 index 030c9e3..0000000 --- a/_proc/_docs/api/index.html +++ /dev/null @@ -1,440 +0,0 @@ - - - - - - - - - -CanvasGroupy - api - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - -
- -
- - - - - -
- -
-
-

api

-
- - - -
- - - - -
- - -
- -

This section contains API details for each of CanvasGroupy python submodules. This reference documentation is mainly useful for people looking to customise or build on top of nbdev, or wanting detailed information about how this module works.

- - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
-Title - -Description -
-Canvas Group Creation - -Create canvas group via Canvas API -
-GitHub Group Creation - -Create GitHub group based on the provided group information -
-GroupEngAssign - -Invoke Package Group Eng to Assign Students in Groups -
-Project Grading - -Manage grading rubrics and grade posting on the groups grade. -
-
-No matching items -
-
- -
- - - - \ No newline at end of file diff --git a/_proc/_docs/api/project_grading.html b/_proc/_docs/api/project_grading.html deleted file mode 100644 index 2bafdff..0000000 --- a/_proc/_docs/api/project_grading.html +++ /dev/null @@ -1,632 +0,0 @@ - - - - - - - - - - -CanvasGroupy - Project Grading - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - -
- -
- - - - - -
- -
-
-

Project Grading

-
- -
-
- Manage grading rubrics and grade posting on the groups grade. -
-
- - -
- - - - -
- - -
- - -
-

source

-
-

Grading

-
-
 Grading (ghg=None, cg=None)
-
-

Initialize self. See help(type(self)) for accurate signature.

- - - - - - - - - - - - - - - - - - - - - - - -
TypeDefaultDetails
ghgNoneTypeNoneauthenticated GitHub object
cgNoneTypeNoneauthenticated canvas object
-
-
-
-

Grading.create_issue_from_md

-
-
 Grading.create_issue_from_md (repo:github.Repository.Repository,
-                               md_fp:str)
-
-

Create GitHub issue from markdown file.

- - - - - - - - - - - - - - - - - - - - - - - - - -
TypeDetails
repoRepositorytarget repository to create issue
md_fpstrfile path of the feedback markdown file
ReturnsIssueopen issue
-
-

source

-
-
-

Grading.fetch_issue

-
-
 Grading.fetch_issue (repo:github.Repository.Repository, component:str)
-
-

Fetch the issue on GitHub repo and choose related one

- ----- - - - - - - - - - - - - - - - - - - - - - - - - -
TypeDetails
repoRepositorytarget repository to fetch issue
componentstrthe component of the project grading, let it be proposal/checkpoint/final. Need to match the issue’s title
ReturnsIssue
-
-

source

-
-
-

Grading.parse_score_from_issue

-
-
 Grading.parse_score_from_issue (repo:github.Repository.Repository,
-                                 component:str)
-
-

parse score from the template issue

- ----- - - - - - - - - - - - - - - - - - - - - - - - - -
TypeDetails
repoRepositorytarget repository to create issue
componentstrthe component of the project grading, let it be proposal/checkpoint/final. Need to match the issue’s title
Returnsintthe fetched score of that component
-
-

source

-
-
-

Grading.update_canvas_score

-
-
 Grading.update_canvas_score (group_name:str, assignment_id, score:float,
-                              post=False)
-
-

Post score to canvas

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeDefaultDetails
group_namestrtarget group name on canvas group
assignment_idassignment id of the related component
scorefloatscore of that component
postboolFalsewhether to post score via api. for testing purposes
-
-

source

-
-
-

Grading.grade_project

-
-
 Grading.grade_project (repo:github.Repository.Repository, component:str,
-                        assignment_id:int, canvas_group_name=None,
-                        post=False)
-
-

grade github project components

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeDefaultDetails
repoRepositorytarget repository to grade
componentstrthe component of the project grading, let it be proposal/checkpoint/final. Need to match the issue’s title
assignment_idintassignment id that link to that component of the project
canvas_group_nameNoneTypeNonemapping from GitHub repo name to Group name. If not specified, the repository name will be used.
postboolFalsewhether to post score via api. For testing purposes
- - -
- -
- -
- - - - \ No newline at end of file diff --git a/_proc/_docs/feedback_template/checkpoint_feedback.html b/_proc/_docs/feedback_template/checkpoint_feedback.html deleted file mode 100644 index 6302662..0000000 --- a/_proc/_docs/feedback_template/checkpoint_feedback.html +++ /dev/null @@ -1,495 +0,0 @@ - - - - - - - - - -CanvasGroupy – checkpoint_feedback - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - -
- -
- - - - - -
- - - -
-

Project Checkpoint Feedback

-

Total:

-
-

Feedback

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CategoryFull PointYour ScoreComment
Abstract0.5
Background0.5
Problem Statement0.5
Data1
Proposed Solution1
Metrics1
Preliminary Results1
Ethics & Privacy1
Team expectations0.25
Timeline0.25
-
-
-

Rubric

- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CategoryFull PointExplanation
Abstract0.5Abstract is informative, succinct, and clear. It offers specific details about the educational issue, variables (data), context, proposed methods, and measurement of performance/success of the study.
Background0.5Use a minimum of 2 or 3 citations Include a general introduction to your topic Narrative integrates critical and logical details from the peer-reviewed theoretical and research literature. Each key research component is grounded to the literature. Attention is given to different perspectives, threats to validity, and opinion vs. evidence.
Problem Statement0.5Presents a well-defined and significant research problem Include at least one ML-relevant potential solution. Articulates clear, reasonable research questions given the purpose, design, and methods of the project. All variables and controls have been appropriately defined. Proposals are clearly supported from the research and theoretical literature. All elements are mutually supportive.
Data1Multiple data sources for each aspect of the project. All data sources are fully described and referenced. Data is appropriate to the question/goal and large enough data points >1k observations and >5 variable The details of the descriptions also make it clear how they support the needs of the project.
Proposed Solution1The elements of the process were described succinctly and with clarity about how they are connected to each other Included description how the solution will be tested.
Metrics1The metrics are described clearly and succinctly. Their appropriateness for addressing the research problem is clearly described. Provided the mathematical representations of metrics
Preliminary Results1Analyzing the suitability of a dataset or algorithm for prediction/solving your problem Performing feature selection or hand-designing features from the raw data. Describe the features available/created and/or show the code for selection/creation Dataset actually clean and usable after feature selection is carried out Showing the performance of a base model/hyper-parameter setting. Solve the task with one “default” algorithm and characterize the performance level of that base model. At least one of the three: Learning curves or validation curves for a particular model Tables/graphs showing the performance of different models/hyper-parameters
Ethics & Privacy1Thoughtful discussion of ethical concerns included. Ethical concerns consider the whole data science process (question asked, data collected, data being used, the bias in data, analysis, post-analysis, etc.). How your group handled bias/ethical concerns clearly described
Team expectations0.25The list clearly was the subject of a thoughtful approach and already indicates a well-working team
Timeline0.25The timeline was clearly the subject of a thoughtful approach and indicates that the team has a detailed plan that seems appropriate and completable in the allotted time.
-
-
-

Comments

- - -
-
- -
- -
- - - - \ No newline at end of file diff --git a/_proc/_docs/feedback_template/final_project_feedback.html b/_proc/_docs/feedback_template/final_project_feedback.html deleted file mode 100644 index 915b9f3..0000000 --- a/_proc/_docs/feedback_template/final_project_feedback.html +++ /dev/null @@ -1,505 +0,0 @@ - - - - - - - - - -CanvasGroupy – final_project_feedback - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - -
- -
- - - - - -
- - - -
-

Final Project Feedback

-
-

Feedback

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CategoryFull PointYour ScoreComment
Title & Abstract1
Background1
Problem Statement1
Data1
Proposed Solution1.25
Metrics1.25
Results1.25
Interpreting the result1.25
Limitations1.25
Ethics & Privacy0.75
Conclusion1
-
-
-

Rubric

- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CategoryFull PointExpectation
Title & Abstract1Title & abstract is informative, succinct, and clear.
It offers specific details about the educational issue, variables (data), context, proposed methods, and measurement of performance/success of the study.
Background1Use a minimum of 2 or 3 citations
Include a general introduction to your topic
Narrative integrates critical and logical details from the peer-reviewed theoretical and research literature.
Each key research component is grounded to the literature. Attention is given to different perspectives, threats to validity, and opinion vs. evidence.
Problem Statement1Presents a well-defined and significant research problem
Articulates clear, reasonable research questions given the purpose, design, and methods of the project.
All variables and controls have been appropriately defined. Proposals are clearly supported from the research and theoretical literature. All elements are mutually supportive.
Data1Multiple data sources for each aspect of the project. All data sources are fully described and referenced.
Data is appropriate to the question/goal and large enough data points >1k observations and >5 variable
The details of the descriptions of the data also make it clear how they support the needs of the project.
Details of data storage and cleaning
Proposed Solution1.25The elements of the process were described succinctly and with clarity about how they are connected to each other
Included description how the solution will be tested. (0.5 pt)
Metrics1.25The metrics are described clearly and succinctly.
Their appropriateness for addressing the research problem is clearly described.
Results1.25Does a good model/hyper-parameter selection using more than one model and hyperparameter in hyperparameter search.
Include the detailed code and analysis results of the main points
Performs multiple secondary analysis such as learning curves, heat maps looking at where in the parameter space things are good/bad, uses statistical testing
Interpreting the result1.25Think clearly about the results and obtain one main point and 2-4 secondary points (2-5 sentences per point).
Highlight HOW the results support those points. Understand what they are doing in the previous “Results” section
Limitations1.25Has a sense of what to do next, and has good explorations of the limitations.
Ethics & Privacy0.75Thoughtful discussion of ethical concerns included.
Ethical concerns consider the whole data science process (question asked, data collected, data being used, the bias in data, analysis, post-analysis, etc.).
How your group handled bias/ethical concerns clearly described
Conclusion1Clearly recapitulates the results and provides context, perhaps including the literature of the field.
-
-
-

Comments

- - -
-
- -
- -
- - - - \ No newline at end of file diff --git a/_proc/_docs/feedback_template/proposal_feedback.html b/_proc/_docs/feedback_template/proposal_feedback.html deleted file mode 100644 index 45bf117..0000000 --- a/_proc/_docs/feedback_template/proposal_feedback.html +++ /dev/null @@ -1,497 +0,0 @@ - - - - - - - - - -CanvasGroupy – proposal_feedback - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - -
- -
- - - - - -
- - - -
-

Project Proposal Feedback

-
-

Score (out of 9)

-

Score = …

-
-
-

Feedback:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
QualityReasons
Abstract
Research question
Background
Hypothesis
Data
Ethics
Team expectations
Timeline
-
-
-

Rubric

- ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
UnsatisfactoryDevelopingProficientExcellent
AbstractAbstract is confusing or fails to offer important details about the issue, variables, context, or methods of the project.Abstract lacks relevance or fails to offer appropriate details about the issue, variables, context, or methods of the project.Abstract is relevant, offering details about the research project.Abstract is informative, succinct, and clear. It offers specific details about the educational issue, variables, context, and proposed methods of the study.
Research questionResearch issue remains unclear. The research purpose, questions, hypotheses, definitions or variables and controls are still largely undefined, or when they are are poorly formed, ambiguous, or not logically connected to the description of the problem. Unclear connections to the literature.Research issue is identified, but statement is too broad or fails to establish the importance of the problem. The research purpose, questions, hypotheses, definitions or variables and controls are poorly formed, ambiguous, or not logically connected to the description of the problem. Unclear connections to the literature.Identifies a relevant research issue. Research questions are succinctly stated, connected to the research issue, and supported by the literature. Variables and controls have been identified and described. Connections are established with the literature.Presents a significant research problem. Articulates clear, reasonable research questions given the purpose, design, and methods of the project. All variables and controls have been appropriately defined. Proposals are clearly supported from the research and theoretical literature. All elements are mutually supportive.
BackgroundDid not have at least 2 reliable and relevant sources. Or relevant sources were not used in relevant waysA key component was not connected to the research literature. Selected literature was from unreliable sources. Literary supports were vague or ambiguous.Key research components were connected to relevant, reliable theoretical and research literature.Narrative integrates critical and logical details from the peer-reviewed theoretical and research literature. Each key research component is grounded to the literature. Attention is given to different perspectives, threats to validity, and opinion vs. evidence.
HypothesisLacks most details; vague or interpretable in different ways. Or seems completely unrealistic.A key detail to understand the hypothesis or the rationale behind it was not described well enoughThe hypothesis is clear. All elements needed to understand the rationale were described in sufficient detailThe hypothesis and its rationale were described succinctly and with clarity about how they are connected to each other
DataDid not have references to relevant data sources for this problem. Did not describe the data obtained at those sourcesA key data source was not referenced or described in satisfactory level of detailAll relevant data sources were referenced and described in terms of their key variables and sizeMultiple data sources for each aspect of the project, All data sources are fully described and referenced. The details of the descriptions also make it clear how they support the needs ot the project.
EthicsNo effort or just says we have no ethical concernsMinimal ethical section; probably just talks about data privacy and no unintended consequences discussion. Ethical concerns raised seem irrelevant.Ethical concerns described are appropriate and described sufficientlyEthical concerns are described clearly and succinctly. This was clearly a thoughtful and nuanced approach to the issues
Team expectationsLack of expectationsThe list of expectations feels incomplete and perfunctoryIt feels like the list of expectations is complete and seems appropriateThe list clearly was the subject of a thoughtful approach and already indicates a well-working team
TimelineLack of timeline. Or timeline is completely unrealisticThe timeline feels incomplete and perfunctory. The timeline feels either too fast or too slow for the progress you expect a group can makeIt feels like the timeline is complete and appropriate. it can likely be completed as is in the available amount of timeThe timeline was clearly the subject of a thoughtful approach and indicates that the team has a detailed plan that seems appropriate and completable in the allotted time.
-

Scoring: Out of 9 points

-
    -
  • Each Developing => -0.75 pts
  • -
  • Each Unsatisfactory/Missing => -1.5 pts -
      -
    • until the score is 0
    • -
  • -
-

If students address the detailed feedback in a future checkpoint they will earn these points back

-
-
-

Comments

- - -
-
- -
- -
- - - - \ No newline at end of file diff --git a/_proc/_docs/index.html b/_proc/_docs/index.html deleted file mode 100644 index 6078267..0000000 --- a/_proc/_docs/index.html +++ /dev/null @@ -1,490 +0,0 @@ - - - - - - - - - - -CanvasGroupy - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - -
- -
- - - - - -
- -
-
-

CanvasGroupy

-
- -
-
- Canvas Grouping Python Script -
-
- - -
- - - - -
- - -
- - -

View our documentation at this link

-

This module will use GroupEng to create canvas and github groups.

-
-

Install

-
pip install CanvasGroupy
-
-
-

How to use

-

Please visit GroupEng Official Website to see the documnetation of how to use GroupEng.

-
-
assign_groups("example/sample_group_specification.groupeng")
-
-
['-', '-', 'B', '-', '-']
-['B', '-', 'H', '-', '-']
-['H', '-', '-', '-', '-']
-['-', 'H', '-', 'B', 'H']
-['-', '-', '-', 'B', '-']
-['-', '-', 'H', '-', '-']
-['nanotech', 'renewable energy', 'nanotech', 'nanotech', 'nanotech']
-['automotive', 'automotive', 'robotics', 'automotive', 'renewable energy']
-['automotive', 'statistics', 'automotive', 'renewable energy', 'renewable energy']
-['automotive', 'automotive', 'statistics', 'renewable energy', 'renewable energy']
-['automotive', 'automotive', 'renewable energy', 'statistics', 'renewable energy']
-['renewable energy', 'automotive', 'automotive', 'statistics', 'renewable energy']
-['CS', 'EE', 'Mech E', 'CS', 'EE']
-['CS', 'EE', 'Mech E', 'Mech E', 'EE']
-['CS', 'Civ E', 'EE', 'Mech E', 'Mech E']
-['EE', 'Civ E', 'Civ E', 'EE', 'Mech E']
-['EE', 'Mech E', 'CS', 'EE', 'EE']
-['Civ E', 'Mech E', 'CS', 'Mech E', 'EE']
-['y', 'y', 'y', 'y', 'y']
-['y', 'y', 'y', 'y', 'y']
-['y', 'y', 'y', 'y', 'y']
-['y', 'y', 'y', 'y', 'y']
-['y', 'y', 'y', 'y', 'y']
-['-', '-', 'y', '-', 'y']
-['y', 'y', 'y', 'y', 'y']
-['y', 'y', 'y', 'y', 'y']
-['y', 'y', 'y', 'y', 'y']
-['y', 'y', 'y', 'y', 'y']
-['y', 'y', 'y', 'y', 'y']
-['y', '-', '-', 'y', 'y']
-['y', 'y', 'y', '-', '-']
-['y', 'y', '-', 'y', '-']
-['-', '-', '-', 'y', 'y']
-['y', '-', 'y', '-', 'y']
-['-', '-', 'y', '-', 'y']
-[3.2579174234, 3.5995299693, 3.2756432963, 4.220160605, 2.5723254477]
-[3.2579174234, 3.2756432963, 2.5723254477, 3.5995299693, 4.220160605]
-[4.220160605, 3.5995299693, 3.2756432963, 3.2579174234, 2.5723254477]
-[3.2756432963, 4.220160605, 3.2579174234, 2.5723254477, 3.5995299693]
-[3.5995299693, 4.220160605, 3.2756432963, 3.2579174234, 2.5723254477]
-[3.5995299693, 3.2756432963, 2.5723254477, 3.2579174234, 4.220160605]
-
-
-
(False, 'groups_example_2023-04-18_13-00-35')
-
-
- - -
- -
- -
- - - - \ No newline at end of file diff --git a/_proc/_docs/listings.json b/_proc/_docs/listings.json deleted file mode 100644 index 2bb94ae..0000000 --- a/_proc/_docs/listings.json +++ /dev/null @@ -1,11 +0,0 @@ -[ - { - "listing": "/api/index.html", - "items": [ - "/api/canvas.html", - "/api/github.html", - "/api/groupeng_assign.html", - "/api/project_grading.html" - ] - } -] \ No newline at end of file diff --git a/_proc/_docs/robots.txt b/_proc/_docs/robots.txt deleted file mode 100644 index 60c5df7..0000000 --- a/_proc/_docs/robots.txt +++ /dev/null @@ -1 +0,0 @@ -Sitemap: https://FleischerResearchLab.github.io/CanvasGroupy/sitemap.xml diff --git a/_proc/_docs/search.json b/_proc/_docs/search.json deleted file mode 100644 index 63121cb..0000000 --- a/_proc/_docs/search.json +++ /dev/null @@ -1,205 +0,0 @@ -[ - { - "objectID": "api/github.html", - "href": "api/github.html", - "title": "GitHub Group Creation", - "section": "", - "text": "source" - }, - { - "objectID": "api/github.html#create-github-repository", - "href": "api/github.html#create-github-repository", - "title": "GitHub Group Creation", - "section": "Create GitHub Repository", - "text": "Create GitHub Repository\nNote: GtiHubGroup.create_group_repo call this method to create a GitHub repository\npersonal_account argument controls the location of the repository creation. If set to False (default), it will create repository in the target organization. If set to True, the new repository will be created in the personal GitHub account.\n\nsource\n\nGitHubGroup.create_repo\n\n GitHubGroup.create_repo (repo_name:str, repo_template='', private=True,\n description='', personal_account=False)\n\nCreate a repository, either blank, or from a template\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nrepo_name\nstr\n\nrepository name\n\n\nrepo_template\nstr\n\ntemplate repository that new repo will use. If empty string, an empty repo will be created. Put in the format of “/”\n\n\nprivate\nbool\nTrue\nvisibility of the created repository\n\n\ndescription\nstr\n\ndescription for the GitHub repository\n\n\npersonal_account\nbool\nFalse\ncreate repos in personal GitHub account\n\n\nReturns\nRepository\n\n\n\n\n\nNote: GtiHubGroup.create_group_repo call this method to create a GitHub repository\npersonal_account argument controls the location of the repository creation. If set to False (default), it will create repository in the target organization. If set to True, the new repository will be created in the personal GitHub account.\n\n# create a repo under the org\nrepo = g.create_repo(\n \"test-repo-organizational\",\n private=True\n)\nprint(repo)\n\nRepository(full_name=\"COGS118A/test-repo-organizational\")\n\n\nAs you can see from the full name COGS118A/test-repo, it is created under the organization of COGS118A.\nAlternatively, I can also create a new repository under my personal account.\n\n# create a repo under the my personal account\nrepo = g.create_repo(\n \"test-repo-personal\",\n private=True,\n personal_account=True\n)\nprint(repo)\n\nRepository(full_name=\"scott-yj-yang/test-repo-personal\")\n\n\n\n# create a repo under the my personal account\nrepo = g.create_repo(\n repo_name=\"test-repo-personal\",\n private=True,\n personal_account=True\n)\nprint(repo)\n\nRepository(full_name=\"scott-yj-yang/test-repo-personal\")\n\n\n\n\nCreate Repository from Template\nYou can also create a repository with a template repository. To do that, specify the full name of the template repository to the repo_template parameter. From the output, we can see that the repository test-repo-from-template is created with the template files.\n\n# create a repo from a template\nrepo = g.create_repo(\n repo_name=\"test-repo-from-template\",\n repo_template=\"COGS118A/group_template\",\n private=True\n)\nprint(repo)\n\n# wait 3 sec for repository creation.\ntime.sleep(3)\n\nprint(\"\\nThis Repository contains... \\n\")\npprint(repo.get_contents(\".\"))\n\nRepository(full_name=\"COGS118A/test-repo-from-template\")\n\nThis Repository contains... \n\n[ContentFile(path=\".gitignore\"),\n ContentFile(path=\"Checkpoint_groupXXX.ipynb\"),\n ContentFile(path=\"FinalProject_groupXXX.ipynb\"),\n ContentFile(path=\"Proposal_groupXXX.ipynb\"),\n ContentFile(path=\"README.md\")]" - }, - { - "objectID": "api/github.html#rename-files-in-the-repository", - "href": "api/github.html#rename-files-in-the-repository", - "title": "GitHub Group Creation", - "section": "Rename Files in the Repository", - "text": "Rename Files in the Repository\nUsually, the template file names are generics and is not specific to a group. Under the context of group project, we want to rename each notebook files with thier group numbers. For example, for Group 1, we want to rename the file Checkpoint_groupXXX.ipynb to Checkpoint_group001.ipynb. To do that, we can use the following method.\n\nsource\n\nGitHubGroup.rename_files\n\n GitHubGroup.rename_files (repo:github.Repository.Repository,\n og_filename:str, new_filename:str)\n\nRename the file by delete the old file and commit the new file\n\n\n\n\nType\nDetails\n\n\n\n\nrepo\nRepository\nthe repository that we want to rename file\n\n\nog_filename\nstr\nold file name\n\n\nnew_filename\nstr\nnew file name\n\n\n\nNote: This method simply delete the old files and create new files with the updated file name. Therefore, 2 commits for each file is expected. (1 for delete, 1 for re-upload). For example, if I want to rename 5 files, I will have 10 commits need to do in total.\n\ng.rename_files(\n repo=repo,\n og_filename=\"Checkpoint_groupXXX.ipynb\",\n new_filename=\"Checkpoint_group001.ipynb\"\n)\n# take a look at new files\nprint(\"\\nThis Repository contains... \\n\")\npprint(repo.get_contents(\".\"))\n\nFile Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_group001.ipynb \n\nThis Repository contains... \n\n[ContentFile(path=\".gitignore\"),\n ContentFile(path=\"Checkpoint_group001.ipynb\"),\n ContentFile(path=\"FinalProject_groupXXX.ipynb\"),\n ContentFile(path=\"Proposal_groupXXX.ipynb\"),\n ContentFile(path=\"README.md\")]\n\n\nNotice that the files were renamed as expected." - }, - { - "objectID": "api/github.html#collaborators-and-teams-access-repository", - "href": "api/github.html#collaborators-and-teams-access-repository", - "title": "GitHub Group Creation", - "section": "Collaborators and Teams Access Repository", - "text": "Collaborators and Teams Access Repository\nOnce the repository was created, we need to give the student team members proper permission to write to the repository and instructional team to be the admin of the repository. Those two functionalities are achieve by the following two methods.\n\nsource\n\nGitHubGroup.add_collaborator\n\n GitHubGroup.add_collaborator (repo:github.Repository.Repository,\n collaborator:str, permission:str)\n\nAdd collaborator to the repository with specified permission\n\n\n\n\nType\nDetails\n\n\n\n\nrepo\nRepository\ntarget repository\n\n\ncollaborator\nstr\nGitHub username of the collaborator\n\n\npermission\nstr\npull, push or admin\n\n\n\n\n# add collaborator to the repository with push permission\ng.add_collaborator(\n repo=repo,\n collaborator=\"Andrina-iris\",\n permission=\"write\"\n)\n\nAdded Collaborator: Andrina-iris to: test-repo-from-template with permission: write \n\n\nIn almost every quarter, at least 10 students forgot to accept the invitation to join the group repository. Historially, our instructional team handle those student’s GitHub account on a case-by-case basis. However, with this module, it is possible to resent all pending and expired invitations to those students with one call.\n\nsource\n\n\nGitHubGroup.resend_invitations\n\n GitHubGroup.resend_invitations (repo:github.Repository.Repository)\n\nResent Invitation to invitee who did not accept the invitation\n\n\n\n\n\n\n\n\n\nType\nDetails\n\n\n\n\nrepo\nRepository\ntarget repository\n\n\nReturns\n[]\nlist of re-invited user\n\n\n\nStudents will receive an email from GitHub with the freshly made, unexpired invitation to their group repository.\n\ng.resend_invitations(repo)\n\nThe list of pending invitation:\n[NamedUser(login=\"Andrina-iris\")]\n Andrina-iris Invite Revoked Andrina-iris \nAdded Collaborator: Andrina-iris to: test-repo-from-template with permission: write \n Invite Resent to Andrina-iris \n\n\n[NamedUser(login=\"Andrina-iris\")]\n\n\nAdditionally, course staffs should be in a team in the GitHub Organization in order to manage student repositories.\n\nsource\n\n\nGitHubGroup.add_team\n\n GitHubGroup.add_team (repo:github.Repository.Repository, team_slug:str,\n permission:str)\n\nAdd team to the repository with specified permission\n\n\n\n\nType\nDetails\n\n\n\n\nrepo\nRepository\ntarget repository\n\n\nteam_slug\nstr\nteam slug (name)\n\n\npermission\nstr\npull, push or admin\n\n\n\n\ng.add_team(\n repo=repo,\n team_slug=\"Instructors_Sp23\",\n permission=\"admin\"\n)\n\nTeam Instructors_Sp23 added to test-repo-from-template with permission admin \n\n\nIf you have all the students repositories under the same team, you can use the following method to resent all pending invitations from all the repositories under that team.\n\nsource\n\n\nGitHubGroup.resent_invitations_team_repos\n\n GitHubGroup.resent_invitations_team_repos (team_slug:str)\n\nFor all repository under that team, Resent invitation to invitee who did not accept the invitation\n\n\n\n\nType\nDetails\n\n\n\n\nteam_slug\nstr\nteam slug (name) under the org\n\n\n\n\ng.resent_invitations_team_repos(team_slug=\"Instructors_Sp23\")\n\nRepository AssignmentNotebooksSource_SP23 :\nThe list of pending invitation:\n[]\nRepository AssignmentNotebooks_SP23 :\nThe list of pending invitation:\n[]\nRepository DiscussionSectionNotebooks :\nThe list of pending invitation:\n[]\nRepository Dockerfiles :\nThe list of pending invitation:\n[]\nRepository Lectures :\nThe list of pending invitation:\n[]\nRepository Notebooks :\nThe list of pending invitation:\n[]\nRepository test-repo-from-template :\nThe list of pending invitation:\n[NamedUser(login=\"Andrina-iris\")]\n Andrina-iris Invite Revoked Andrina-iris \nAdded Collaborator: Andrina-iris to: test-repo-from-template with permission: write \n Invite Resent to Andrina-iris" - }, - { - "objectID": "api/github.html#project-feedback-and-github-issues", - "href": "api/github.html#project-feedback-and-github-issues", - "title": "GitHub Group Creation", - "section": "Project Feedback and GitHub Issues", - "text": "Project Feedback and GitHub Issues\nThanks for the group nature of the created repository, we can also use the created GitHub repository to create group project feedback to students via GitHub issues.\nWe can create a directory under the instructors’ computer. Each directory will have a folder for each group repository with the template file. The template files are usually the rubrics for the project grading.\nTODO: add file superlink to the repo to see the examples.\n\nsource\n\nGitHubGroup.create_feedback_dir\n\n GitHubGroup.create_feedback_dir (repo:github.Repository.Repository,\n template_fp:str, destination='feedback')\n\nCreate feedback directory on local machine\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nrepo\nRepository\n\ntarget repository\n\n\ntemplate_fp\nstr\n\n\n\n\ndestination\nstr\nfeedback\ndirectory path of the template file.\n\n\n\n\ng.create_feedback_dir(repo, \"feedback_template\")\n# take a look at the generated tempalte files.\nos.listdir(f\"feedback/{repo.name}\")\n\nFile checkpoint_feedback.md created at feedback/test-repo-from-template\nFile proposal_feedback.md created at feedback/test-repo-from-template\nFile final_project_feedback.md created at feedback/test-repo-from-template\n\n\n['checkpoint_feedback.md', 'proposal_feedback.md', 'final_project_feedback.md']\n\n\nAfter created the project feedback, instructional team can modify the markdown rubrics and provide feedback to students via GitHub issue.\n\nsource\n\n\nGitHubGroup.create_issue\n\n GitHubGroup.create_issue (repo:github.Repository.Repository, title:str,\n content:str)\n\nCreate GitHub issue to the target repository\n\n\n\n\nType\nDetails\n\n\n\n\nrepo\nRepository\ntarget repository\n\n\ntitle\nstr\ntitle of the issue,\n\n\ncontent\nstr\ncontent of the issue\n\n\nReturns\nIssue\nopen issue\n\n\n\n\n# create a test issue\nissue = g.create_issue(repo, \"Test Issue\", \"This is just a test issue.\")\nissue\n\nIn the repo: test-repo-from-template,\nIssue Test Issue Created!\n\n\nIssue(title=\"Test Issue\", number=1)\n\n\nAlternatively, you can create issue from markdown files, where it contains all the comments and rubrics for this project. The first line of the markdown file will be the title of the github issue.\n\nsource\n\n\nGitHubGroup.create_issue_from_md\n\n GitHubGroup.create_issue_from_md (repo:github.Repository.Repository,\n md_fp:str)\n\nCreate GitHub issue from markdown file.\n\n\n\n\nType\nDetails\n\n\n\n\nrepo\nRepository\ntarget repository,\n\n\nmd_fp\nstr\nfile path of the feedback markdown file\n\n\nReturns\nIssue\nopen issue\n\n\n\n\nissue = g.create_issue_from_md(repo, \"feedback_template/proposal_feedback.md\")\nissue\n\nIn the repo: test-repo-from-template,\nIssue Project Proposal Feedback Created!\n\n\nIssue(title=\" Project Proposal Feedback\", number=2)\n\n\n\n\nRelease Feedback in Batch\nDuring projct grading, we will handle numerous groups at once. Once the instructor team finish modifying the markdown file for each group, we can release feedback to each of the project repository, as long as they have the same file name. For example, we have finish grading the final project, in the file name of feedback//final_project_feedback.md, and they are ready to be publish, we can call the following function to create issue in batch.\n\nsource\n\n\nGitHubGroup.release_feedback\n\n GitHubGroup.release_feedback (md_filename:str, feedback_dir='feedback')\n\nRelease feedback via GitHub issue from all the feedbacks in the feedback directory\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nmd_filename\nstr\n\nfeedback markdown file name\n\n\nfeedback_dir\nstr\nfeedback\nfeedback directory contains the markdown files\n\n\n\n\ng.release_feedback(\"final_project_feedback.md\")\n\nIn the repo: test-repo-from-template,\nIssue Final Project Feedback Created!" - }, - { - "objectID": "api/canvas.html", - "href": "api/canvas.html", - "title": "Canvas Group Creation", - "section": "", - "text": "source" - }, - { - "objectID": "api/canvas.html#create-set-target-group-category-set", - "href": "api/canvas.html#create-set-target-group-category-set", - "title": "Canvas Group Creation", - "section": "Create / Set Target Group Category (Set)", - "text": "Create / Set Target Group Category (Set)\n\nsource\n\nCanvasGroup.get_group_categories\n\n CanvasGroup.get_group_categories ()\n\nGrab all existing group category (group set) in this course\n\n# list all current group category\nlist(cg.get_group_categories().keys())\n\n['Final Project', 'Student Groups', 'Test']\n\n\n\nsource\n\n\nCanvasGroup.create_group_category\n\n CanvasGroup.create_group_category (params:dict)\n\nCreate group category (group set) in this course\n\n\n\n\nType\nDetails\n\n\n\n\nparams\ndict\nthe parameter of canvas group category API @ this link\n\n\nReturns\nGroupCategory\nthe generated group category object\n\n\n\n\nparams = {\n \"name\": \"TEST-GroupProject\",\n \"group_limit\": 5\n}\n\n\n# create a new category\ngroup_category = cg.create_group_category(params)\n\n\n# Check whether we successfully create a new group\nlist(cg.get_group_categories().keys())\n\n['Final Project', 'Student Groups', 'Test', 'TEST-GroupProject']\n\n\nWhen a group category is already created, we cannot create another group with the same name. To switch the group category destination of group creation, use the set_group_category methods.\n\nsource\n\n\nCanvasGroup.set_group_category\n\n CanvasGroup.set_group_category (category_name:str)\n\n\n\n\n\nType\nDetails\n\n\n\n\ncategory_name\nstr\nthe target group category\n\n\nReturns\nGroupCategory\ntarget group category object\n\n\n\n\ngroup_category = cg.set_group_category(\"TEST-GroupProject\")" - }, - { - "objectID": "api/canvas.html#create-a-group-inside-the-target-group-category", - "href": "api/canvas.html#create-a-group-inside-the-target-group-category", - "title": "Canvas Group Creation", - "section": "Create a Group Inside the Target Group Category", - "text": "Create a Group Inside the Target Group Category\n\nsource\n\nCanvasGroup.create_group\n\n CanvasGroup.create_group (params:dict)\n\nCreate canvas group under the target group category\n\n\n\n\nType\nDetails\n\n\n\n\nparams\ndict\nthe parameter of canvas group create API at this link\n\n\nReturns\nGroup\nthe generated target group object\n\n\n\n\nparams = {\n \"name\": \"TEST-GROUP1\",\n \"join_level\": \"invitation_only\"\n}\ngroup1 = cg.create_group(params)\nprint(group1)\n\nIn Group Set: TEST-GroupProject,\nGroup TEST-GROUP1 Created!\nTEST-GROUP1 (122854)" - }, - { - "objectID": "api/canvas.html#assign-student-to-the-group", - "href": "api/canvas.html#assign-student-to-the-group", - "title": "Canvas Group Creation", - "section": "Assign Student to the Group", - "text": "Assign Student to the Group\n\nsource\n\nCanvasGroup.join_canvas_group\n\n CanvasGroup.join_canvas_group (group:canvasapi.group.Group,\n group_members:[])\n\nAdd membership access of each group member into the group\n\n\n\n\n\n\n\n\n\nType\nDetails\n\n\n\n\ngroup\nGroup\nthe group that students will join\n\n\ngroup_members\n[]\nlist of group member’s SIS Login (email prefix, before the @.)\n\n\nReturns\n[]\nlist of unsuccessful join\n\n\n\n\nmember1 = \"email\"\n\ncg.join_canvas_group(group1, [member1])\n\n[]" - }, - { - "objectID": "api/index.html", - "href": "api/index.html", - "title": "api", - "section": "", - "text": "This section contains API details for each of CanvasGroupy python submodules. This reference documentation is mainly useful for people looking to customise or build on top of nbdev, or wanting detailed information about how this module works.\n\n\n\n\n\n\n\n\n\n\nTitle\n\n\nDescription\n\n\n\n\n\n\nCanvas Group Creation\n\n\nCreate canvas group via Canvas API\n\n\n\n\nGitHub Group Creation\n\n\nCreate GitHub group based on the provided group information\n\n\n\n\nGroupEngAssign\n\n\nInvoke Package Group Eng to Assign Students in Groups\n\n\n\n\nProject Grading\n\n\nManage grading rubrics and grade posting on the groups grade.\n\n\n\n\n\n\nNo matching items" - }, - { - "objectID": "api/project_grading.html", - "href": "api/project_grading.html", - "title": "Project Grading", - "section": "", - "text": "source\n\nGrading\n\n Grading (ghg=None, cg=None)\n\nInitialize self. See help(type(self)) for accurate signature.\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nghg\nNoneType\nNone\nauthenticated GitHub object\n\n\ncg\nNoneType\nNone\nauthenticated canvas object\n\n\n\n\n\n\nGrading.create_issue_from_md\n\n Grading.create_issue_from_md (repo:github.Repository.Repository,\n md_fp:str)\n\nCreate GitHub issue from markdown file.\n\n\n\n\nType\nDetails\n\n\n\n\nrepo\nRepository\ntarget repository to create issue\n\n\nmd_fp\nstr\nfile path of the feedback markdown file\n\n\nReturns\nIssue\nopen issue\n\n\n\n\nsource\n\n\nGrading.fetch_issue\n\n Grading.fetch_issue (repo:github.Repository.Repository, component:str)\n\nFetch the issue on GitHub repo and choose related one\n\n\n\n\n\n\n\n\n\nType\nDetails\n\n\n\n\nrepo\nRepository\ntarget repository to fetch issue\n\n\ncomponent\nstr\nthe component of the project grading, let it be proposal/checkpoint/final. Need to match the issue’s title\n\n\nReturns\nIssue\n\n\n\n\n\nsource\n\n\nGrading.parse_score_from_issue\n\n Grading.parse_score_from_issue (repo:github.Repository.Repository,\n component:str)\n\nparse score from the template issue\n\n\n\n\n\n\n\n\n\nType\nDetails\n\n\n\n\nrepo\nRepository\ntarget repository to create issue\n\n\ncomponent\nstr\nthe component of the project grading, let it be proposal/checkpoint/final. Need to match the issue’s title\n\n\nReturns\nint\nthe fetched score of that component\n\n\n\n\nsource\n\n\nGrading.update_canvas_score\n\n Grading.update_canvas_score (group_name:str, assignment_id, score:float,\n post=False)\n\nPost score to canvas\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\ngroup_name\nstr\n\ntarget group name on canvas group\n\n\nassignment_id\n\n\nassignment id of the related component\n\n\nscore\nfloat\n\nscore of that component\n\n\npost\nbool\nFalse\nwhether to post score via api. for testing purposes\n\n\n\n\nsource\n\n\nGrading.grade_project\n\n Grading.grade_project (repo:github.Repository.Repository, component:str,\n assignment_id:int, canvas_group_name=None,\n post=False)\n\ngrade github project components\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nrepo\nRepository\n\ntarget repository to grade\n\n\ncomponent\nstr\n\nthe component of the project grading, let it be proposal/checkpoint/final. Need to match the issue’s title\n\n\nassignment_id\nint\n\nassignment id that link to that component of the project\n\n\ncanvas_group_name\nNoneType\nNone\nmapping from GitHub repo name to Group name. If not specified, the repository name will be used.\n\n\npost\nbool\nFalse\nwhether to post score via api. For testing purposes" - }, - { - "objectID": "api/groupeng_assign.html", - "href": "api/groupeng_assign.html", - "title": "GroupEngAssign", - "section": "", - "text": "source" - }, - { - "objectID": "api/groupeng_assign.html#api-doc", - "href": "api/groupeng_assign.html#api-doc", - "title": "GroupEngAssign", - "section": "API Doc", - "text": "API Doc\n\nsource\n\nAssignGroup\n\n AssignGroup (ghg:CanvasGroupy.github.GitHubGroup,\n cg:CanvasGroupy.canvas.CanvasGroup, groupeng_config='')\n\nInitializer for Assign Group\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nghg\nGitHubGroup\n\nauthenticated GitHub object\n\n\ncg\nCanvasGroup\n\nauthenticated canvas object\n\n\ngroupeng_config\nstr\n\nDirectory for the GroupEng config yml file\n\n\n\n\nsource\n\n\nAssignGroup.assign_groups\n\n AssignGroup.assign_groups (groupeng_config:str,\n assign_canvas_group=False,\n create_gh_repo=False, username_quiz_id=-1,\n in_group_category='', suffix='')\n\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\ngroupeng_config\nstr\n\nDirectory for the GroupEng config yml file\n\n\nassign_canvas_group\nbool\nFalse\ndirectly assign canvas groups\n\n\ncreate_gh_repo\nbool\nFalse\ndirectly create GitHub repos\n\n\nusername_quiz_id\nint\n-1\nusername quiz id from canvas course\n\n\nin_group_category\nstr\n\nspecify which group category the group belongs to\n\n\nsuffix\nstr\n\nsuffix to the group name\n\n\nReturns\n(, )\n\nStatus and output directory of the compiled file.\n\n\n\n\nsource\n\n\nAssignGroup.create_canvas_group\n\n AssignGroup.create_canvas_group (in_group_category='', suffix='')\n\nCreate canvas groups based on the generated group configuration\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nin_group_category\nstr\n\nspecify which group category the group belongs to\n\n\nsuffix\nstr\n\nsuffix to the group name\n\n\n\n\nsource\n\n\nAssignGroup.create_github_group\n\n AssignGroup.create_github_group (username_quiz_id:int)\n\n\n\n\n\nType\nDetails\n\n\n\n\nusername_quiz_id\nint\nusername quiz id from canvas course" - }, - { - "objectID": "tutorials/create github group from canvas group.html", - "href": "tutorials/create github group from canvas group.html", - "title": "Example Workflow - Create GitHub Group from Canvas Group", - "section": "", - "text": "This example workflow was used in Spring 2023, for COGS 118A at UC San Diego. his workflow demonstrated a component of the CanvasGroupy, where we already have group information based on the canvas group. In addition, students’ GitHub usernames were collected via a canvas quiz, where we fetched, validated, and stored the GitHub Username directly. This workflow was run after the fact that all students were successfully assigned a group, and all students have correctly completed their GitHub Username quiz.\nAs usual, to execute those API calls, you will have to provide the system with necessary credentials. You can find more information based on the ???TODO tutorial.\nNote: The output of some cells are long, and the output might affect your reading experience. I recommend you to use the hyperlink on the right hand side bar to skip to the next section if needed." - }, - { - "objectID": "tutorials/create github group from canvas group.html#canvas-get-groups-github-username", - "href": "tutorials/create github group from canvas group.html#canvas-get-groups-github-username", - "title": "Example Workflow - Create GitHub Group from Canvas Group", - "section": "Canvas Get Groups / GitHub Username", - "text": "Canvas Get Groups / GitHub Username\nWe need to get the group member information at Canvas. This is achieved by pulling the people list on the group category page.\n\nfrom CanvasGroupy.canvas import CanvasGroup\nfrom CanvasGroupy.github import GitHubGroup\n\n\ncg = CanvasGroup(\"Group_Eng/credentials.json\", course_id=45059)\n\nAuthorization Successful!\nCourse Set: COGS 118A - Supvr/Mach Learning Algorithms - Fleischer [SP23] \nGetting List of Users... This might take a while...\nUsers Fetch Complete! The course has 161 students.\n\n\n\nFetch GitHub Username from Quiz\nSee more info at CanvasGroup.fetch_username_from_quiz\n\ngithub_usernames = cg.fetch_username_from_quiz(quiz_id=139061)\n\nQuiz: GitHub Username fetch! \nGenerating Student Analaysis...\n[====================] 100%\nReport Generated!\nThe Question asked is 1389031: What is your GitHub Username? Absolutely No Typo Please.. \nMake sure this is the correct question where you asked student for their GitHub id.\nIf you need to change the index of columns, change the col_index argument of this call." - }, - { - "objectID": "tutorials/create github group from canvas group.html#check-github-username-validity", - "href": "tutorials/create github group from canvas group.html#check-github-username-validity", - "title": "Example Workflow - Create GitHub Group from Canvas Group", - "section": "Check GitHub Username Validity", - "text": "Check GitHub Username Validity\nWe use GitHub API to search for a target user. See more info at CanvasGroup.check_github_usernames\n\ncg.check_github_usernames(github_usernames,\n send_canvas_email=True,\n send_undone_reminder=True,\n quiz_url=\"https://canvas.ucsd.edu/courses/45059/quizzes/139061\"\n)\n\nStudent ax008708 did not submit their github username.\nNotification Sent!\nStudent a8chu did not submit their github username.\nNotification Sent!\nStudent j3dong did not submit their github username.\nNotification Sent!\nStudent n6garcia did not submit their github username.\nNotification Sent!\nStudent kehu did not submit their github username.\nNotification Sent!\nStudent qil016 did not submit their github username.\nNotification Sent!\nStudent ttp007 did not submit their github username.\nNotification Sent!\nStudent zshao did not submit their github username.\nNotification Sent!\n\n\n{}" - }, - { - "objectID": "tutorials/create github group from canvas group.html#get-group-member-information", - "href": "tutorials/create github group from canvas group.html#get-group-member-information", - "title": "Example Workflow - Create GitHub Group from Canvas Group", - "section": "Get Group Member Information", - "text": "Get Group Member Information\n\ngroups = cg.get_groups(\"Final Project\")\ngroups\n\n{'Group001-SP23': ['h5he', 'zmao', 'xiw013', 'j6wen', 'j5zhu'],\n 'Group002-SP23': ['cmcmanig', 'jup006', 'ssuthar', 'd3yu'],\n 'Group003-SP23': ['yic055', 'yuz191', 'xiz068'],\n 'Group004-SP23': ['dac020', 'nilu', 'tyap', 'g6zhu'],\n 'Group005-SP23': ['kechen', 'a8chu', 'cdelira', 'wolee', 'arshukla'],\n 'Group006-SP23': ['ax008707', 'ax008724', 'ax008777'],\n 'Group007-SP23': ['yuchi', 'j3dong', 'y3ge', 'x6he', 'xiz031'],\n 'Group008-SP23': ['zifeng', 'ax008740', 'jul121', 'yuy047'],\n 'Group009-SP23': ['ax008573', 'ckavanagh', 'v1lu', 'vvishnus'],\n 'Group010-SP23': ['jwc002', 'tjamal', 'jsliang', 'tdn003'],\n 'Group011-SP23': ['khchuang', 'emdavis', 'jejiang', 'nrejai'],\n 'Group012-SP23': ['afleschn', 'rlharsono', 'jjsanchez', 'asengupt'],\n 'Group013-SP23': ['kehu', 'jnhuang', 'shperry', 'alvalenc'],\n 'Group014-SP23': ['gsroberts', 'cvillafa', 'shw089', 'yiz095'],\n 'Group015-SP23': ['aanna', 'sdsilva', 'asivayog', 'nyanekch'],\n 'Group016-SP23': ['yuche', 'y3guo', 'e1hu', 'zhl023', 'qil012'],\n 'Group017-SP23': ['s3chowdhury', 'llennema', 'psodhi', 'svirk'],\n 'Group018-SP23': ['ajcagle', 'ahewig', 's2malik', 'mnodini', 'musman'],\n 'Group019-SP23': ['sasingh', 'nsit', 'dsun'],\n 'Group020-SP23': ['akaji', 'm1manzan', 'j3mendez', 'mpareek', 'atrapena'],\n 'Group021-SP23': ['jkrentse', 'jmlai', 'zel012', 'sserafin'],\n 'Group022-SP23': ['ax008708', 'anc024', 'gng', 'reyang'],\n 'Group023-SP23': ['raguinakang', 'phelcl', 'vjayanan', 'crochez', 'kpstern'],\n 'Group024-SP23': ['nabansal', 'ccaban', 'mvfang', 'asim'],\n 'Group025-SP23': ['nschaefe', 'hshaikh', 'jww001', 'bjyan'],\n 'Group026-SP23': ['e1dong', 'aaolivas', 'h5park', 'yuz821'],\n 'Group027-SP23': ['jddeleon', 'ndnguyen', 'ttp007', 'jzs002'],\n 'Group028-SP23': ['mabdilah', 'yuh045', 'ktnakai', 'jonza'],\n 'Group029-SP23': ['lmitbo', 'jsalce', 'lskerrett', 'jubamadu'],\n 'Group030-SP23': ['ruc003', 'shh035', 'btn003', 'knino'],\n 'Group031-SP23': ['shc007', 'n6garcia', 'ken010', 'mmpak'],\n 'Group032-SP23': ['zachao', 'smurase', 'j1xu', 'z5zhang'],\n 'Group033-SP23': ['sbodhisartha', 'ndeepak', 'arlu', 'trucker', 'jaxu'],\n 'Group034-SP23': ['kabalaji', 'rchaklas', 'vspillai', 'jmvillal'],\n 'Group035-SP23': ['cantoniohernandez', 'rpuranam', 'rsedano', 'zshao'],\n 'Group036-SP23': ['malkhalifah', 'djjani', 'rkohli', 'smtrived'],\n 'Group037-SP23': ['nazpeitia', 'g2hong', 'gkweon'],\n 'Group038-SP23': ['qil016', 'msherrick', 'yiw085', 'r4zhou'],\n 'Group039-SP23': ['cgutierrezgodoy', 'z8jiang', 'dar005', 'jtaolan']}" - }, - { - "objectID": "tutorials/create github group from canvas group.html#github-repository-creation", - "href": "tutorials/create github group from canvas group.html#github-repository-creation", - "title": "Example Workflow - Create GitHub Group from Canvas Group", - "section": "GitHub Repository Creation", - "text": "GitHub Repository Creation\nGiven the gathered information about both group membership and students’ GitHub Username, we are ready to create group repositories for them.\n\nggroup = GitHubGroup(\"Group_Eng/credentials.json\", verbosity=1)\nggroup.set_org(\"COGS118A\")\n\nSuccessfully Authenticated. GitHub account: scott-yj-yang \nTarget Organization Set: COGS118A \n\n\nIn the following for loop, we create the group repositories via a series of GitHubGroup.create_group_repo command. This is the place where we can get personalized (or I shall say groupalized) repositories. Be sure to change the appropriate parameters.\n\nrepos = []\nfor group_name, members in groups.items():\n group_git_usernames = []\n for email in members:\n try:\n # try to get the git username for each student.\n # not all students completed their quiz.\n group_git_usernames.append(github_usernames[email])\n except KeyError:\n print(f\"{email}'s GitHub Username not found\")\n repo = ggroup.create_group_repo(\n repo_name=group_name,\n collaborators=group_git_usernames,\n permission=\"write\",\n repo_template=\"COGS118A/group_template\",\n rename_files={\n \"Checkpoint_groupXXX.ipynb\": f\"Checkpoint_{group_name}.ipynb\",\n \"FinalProject_groupXXX.ipynb\": f\"FinalProject_{group_name}.ipynb\",\n \"Proposal_groupXXX.ipynb\": f\"Proposal_{group_name}.ipynb\"\n },\n private=False,\n description=f\"COGS118A Final Project {group_name} Repository\",\n team_slug=\"Instructors_Sp23\",\n team_permission=\"admin\"\n )\n print(\"\")\n repos.append(repo)\n\nRepo Group001-SP23 Created... Wait for 3 sec to updates\nFile Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group001-SP23.ipynb \nFile Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group001-SP23.ipynb \nFile Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group001-SP23.ipynb \nAdded Collaborator: TaraaHe to: Group001-SP23 with permission: write \nAdded Collaborator: demimao to: Group001-SP23 with permission: write \nAdded Collaborator: xiw013 to: Group001-SP23 with permission: write \nAdded Collaborator: willwen96 to: Group001-SP23 with permission: write \nAdded Collaborator: Ju-dyz to: Group001-SP23 with permission: write \nTeam Instructors_Sp23 added to Group001-SP23 with permission admin \nGroup Repo: Group001-SP23 successfuly created!\nRepo URL: https://github.com/COGS118A/Group001-SP23\n\nRepo Group002-SP23 Created... Wait for 3 sec to updates\nFile Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group002-SP23.ipynb \nFile Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group002-SP23.ipynb \nFile Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group002-SP23.ipynb \nAdded Collaborator: connormcmanigal to: Group002-SP23 with permission: write \nAdded Collaborator: jup006 to: Group002-SP23 with permission: write \nAdded Collaborator: ssutharucsd to: Group002-SP23 with permission: write \nAdded Collaborator: d3yu to: Group002-SP23 with permission: write \nTeam Instructors_Sp23 added to Group002-SP23 with permission admin \nGroup Repo: Group002-SP23 successfuly created!\nRepo URL: https://github.com/COGS118A/Group002-SP23\n\nRepo Group003-SP23 Created... Wait for 3 sec to updates\nFile Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group003-SP23.ipynb \nFile Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group003-SP23.ipynb \nFile Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group003-SP23.ipynb \nAdded Collaborator: Cyl200215 to: Group003-SP23 with permission: write \nAdded Collaborator: scottieboyzhang to: Group003-SP23 with permission: write \nAdded Collaborator: Orang1s to: Group003-SP23 with permission: write \nTeam Instructors_Sp23 added to Group003-SP23 with permission admin \nGroup Repo: Group003-SP23 successfuly created!\nRepo URL: https://github.com/COGS118A/Group003-SP23\n\nRepo Group004-SP23 Created... Wait for 3 sec to updates\nFile Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group004-SP23.ipynb \nFile Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group004-SP23.ipynb \nFile Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group004-SP23.ipynb \nAdded Collaborator: CDavid99 to: Group004-SP23 with permission: write \nAdded Collaborator: nilu0311 to: Group004-SP23 with permission: write \nAdded Collaborator: cookingoil88 to: Group004-SP23 with permission: write \nAdded Collaborator: g6zhu to: Group004-SP23 with permission: write \nTeam Instructors_Sp23 added to Group004-SP23 with permission admin \nGroup Repo: Group004-SP23 successfuly created!\nRepo URL: https://github.com/COGS118A/Group004-SP23\n\na8chu's GitHub Username not found\nRepo Group005-SP23 Created... Wait for 3 sec to updates\nFile Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group005-SP23.ipynb \nFile Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group005-SP23.ipynb \nFile Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group005-SP23.ipynb \nAdded Collaborator: kchen283 to: Group005-SP23 with permission: write \nAdded Collaborator: cdelira9 to: Group005-SP23 with permission: write \nAdded Collaborator: wj6801 to: Group005-SP23 with permission: write \nAdded Collaborator: arth-shukla to: Group005-SP23 with permission: write \nTeam Instructors_Sp23 added to Group005-SP23 with permission admin \nGroup Repo: Group005-SP23 successfuly created!\nRepo URL: https://github.com/COGS118A/Group005-SP23\n\nRepo Group006-SP23 Created... Wait for 3 sec to updates\nFile Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group006-SP23.ipynb \nFile Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group006-SP23.ipynb \nFile Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group006-SP23.ipynb \nAdded Collaborator: Leoooo333 to: Group006-SP23 with permission: write \nAdded Collaborator: qdh-2002 to: Group006-SP23 with permission: write \nAdded Collaborator: Chihhsinli to: Group006-SP23 with permission: write \nTeam Instructors_Sp23 added to Group006-SP23 with permission admin \nGroup Repo: Group006-SP23 successfuly created!\nRepo URL: https://github.com/COGS118A/Group006-SP23\n\nj3dong's GitHub Username not found\nRepo Group007-SP23 Created... Wait for 3 sec to updates\nFile Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group007-SP23.ipynb \nFile Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group007-SP23.ipynb \nFile Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group007-SP23.ipynb \nAdded Collaborator: YunxiangChi to: Group007-SP23 with permission: write \nAdded Collaborator: alien-invader to: Group007-SP23 with permission: write \nAdded Collaborator: XiaoyanHe0713 to: Group007-SP23 with permission: write \nAdded Collaborator: Andrina-iris to: Group007-SP23 with permission: write \nTeam Instructors_Sp23 added to Group007-SP23 with permission admin \nGroup Repo: Group007-SP23 successfuly created!\nRepo URL: https://github.com/COGS118A/Group007-SP23\n\nRepo Group008-SP23 Created... Wait for 3 sec to updates\nFile Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group008-SP23.ipynb \nFile Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group008-SP23.ipynb \nFile Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group008-SP23.ipynb \nAdded Collaborator: wwjasperww to: Group008-SP23 with permission: write \nAdded Collaborator: ChengqinLi1206 to: Group008-SP23 with permission: write \nAdded Collaborator: junyuelin to: Group008-SP23 with permission: write \nAdded Collaborator: fergusyyang to: Group008-SP23 with permission: write \nTeam Instructors_Sp23 added to Group008-SP23 with permission admin \nGroup Repo: Group008-SP23 successfuly created!\nRepo URL: https://github.com/COGS118A/Group008-SP23\n\n\n\nRepo Group009-SP23 Created... Wait for 3 sec to updates\nFile Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group009-SP23.ipynb \nFile Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group009-SP23.ipynb \nFile Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group009-SP23.ipynb \nAdded Collaborator: thaiscodafond to: Group009-SP23 with permission: write \nAdded Collaborator: ckavanagh21 to: Group009-SP23 with permission: write \nAdded Collaborator: 404EZRA to: Group009-SP23 with permission: write \nAdded Collaborator: vvishnus to: Group009-SP23 with permission: write \nTeam Instructors_Sp23 added to Group009-SP23 with permission admin \nGroup Repo: Group009-SP23 successfuly created!\nRepo URL: https://github.com/COGS118A/Group009-SP23\n\nRepo Group010-SP23 Created... Wait for 3 sec to updates\nFile Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group010-SP23.ipynb \nFile Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group010-SP23.ipynb \nFile Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group010-SP23.ipynb \nAdded Collaborator: strawhatwilson23 to: Group010-SP23 with permission: write \nAdded Collaborator: tjamalcodes to: Group010-SP23 with permission: write \nAdded Collaborator: jaysunl to: Group010-SP23 with permission: write \nAdded Collaborator: idereknguyen to: Group010-SP23 with permission: write \nTeam Instructors_Sp23 added to Group010-SP23 with permission admin \nGroup Repo: Group010-SP23 successfuly created!\nRepo URL: https://github.com/COGS118A/Group010-SP23\n\nRepo Group011-SP23 Created... Wait for 3 sec to updates\nFile Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group011-SP23.ipynb \nFile Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group011-SP23.ipynb \nFile Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group011-SP23.ipynb \nAdded Collaborator: khchuang12 to: Group011-SP23 with permission: write \nAdded Collaborator: Emdavis02 to: Group011-SP23 with permission: write \nAdded Collaborator: jennifer-jiang to: Group011-SP23 with permission: write \nAdded Collaborator: nrejai to: Group011-SP23 with permission: write \nTeam Instructors_Sp23 added to Group011-SP23 with permission admin \nGroup Repo: Group011-SP23 successfuly created!\nRepo URL: https://github.com/COGS118A/Group011-SP23\n\nRepo Group012-SP23 Created... Wait for 3 sec to updates\nFile Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group012-SP23.ipynb \nFile Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group012-SP23.ipynb \nFile Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group012-SP23.ipynb \nAdded Collaborator: afleschner to: Group012-SP23 with permission: write \nAdded Collaborator: githubharsono to: Group012-SP23 with permission: write \nAdded Collaborator: JJSanchez23 to: Group012-SP23 with permission: write \nAdded Collaborator: antarasengupta26 to: Group012-SP23 with permission: write \nTeam Instructors_Sp23 added to Group012-SP23 with permission admin \nGroup Repo: Group012-SP23 successfuly created!\nRepo URL: https://github.com/COGS118A/Group012-SP23\n\nkehu's GitHub Username not found\nRepo Group013-SP23 Created... Wait for 3 sec to updates\nFile Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group013-SP23.ipynb \nFile Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group013-SP23.ipynb \nFile Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group013-SP23.ipynb \nAdded Collaborator: jnhuang02 to: Group013-SP23 with permission: write \nAdded Collaborator: Sean1572 to: Group013-SP23 with permission: write \nAdded Collaborator: valenciaaalberto to: Group013-SP23 with permission: write \nTeam Instructors_Sp23 added to Group013-SP23 with permission admin \nGroup Repo: Group013-SP23 successfuly created!\nRepo URL: https://github.com/COGS118A/Group013-SP23\n\nRepo Group014-SP23 Created... Wait for 3 sec to updates\nFile Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group014-SP23.ipynb \nFile Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group014-SP23.ipynb \nFile Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group014-SP23.ipynb \nAdded Collaborator: empire-penguin to: Group014-SP23 with permission: write \nAdded Collaborator: villafun to: Group014-SP23 with permission: write \nAdded Collaborator: 50ShadesOfShawn to: Group014-SP23 with permission: write \nAdded Collaborator: ericzyl to: Group014-SP23 with permission: write \nTeam Instructors_Sp23 added to Group014-SP23 with permission admin \nGroup Repo: Group014-SP23 successfuly created!\nRepo URL: https://github.com/COGS118A/Group014-SP23\n\nRepo Group015-SP23 Created... Wait for 3 sec to updates\nFile Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group015-SP23.ipynb \nFile Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group015-SP23.ipynb \nFile Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group015-SP23.ipynb \nAdded Collaborator: arjunanna to: Group015-SP23 with permission: write \nAdded Collaborator: sdsilva1 to: Group015-SP23 with permission: write \nAdded Collaborator: abi2020 to: Group015-SP23 with permission: write \nAdded Collaborator: nikothomas to: Group015-SP23 with permission: write \nTeam Instructors_Sp23 added to Group015-SP23 with permission admin \nGroup Repo: Group015-SP23 successfuly created!\nRepo URL: https://github.com/COGS118A/Group015-SP23\n\nRepo Group016-SP23 Created... Wait for 3 sec to updates\nFile Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group016-SP23.ipynb \nFile Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group016-SP23.ipynb \nFile Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group016-SP23.ipynb \nAdded Collaborator: shendu11 to: Group016-SP23 with permission: write \nAdded Collaborator: Y3GUO to: Group016-SP23 with permission: write \nAdded Collaborator: EthanHu0 to: Group016-SP23 with permission: write \nAdded Collaborator: claireZHL to: Group016-SP23 with permission: write \nAdded Collaborator: QilunLiu5216 to: Group016-SP23 with permission: write \nTeam Instructors_Sp23 added to Group016-SP23 with permission admin \nGroup Repo: Group016-SP23 successfuly created!\nRepo URL: https://github.com/COGS118A/Group016-SP23\n\n\n\nRepo Group017-SP23 Created... Wait for 3 sec to updates\nFile Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group017-SP23.ipynb \nFile Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group017-SP23.ipynb \nFile Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group017-SP23.ipynb \nAdded Collaborator: sreetama02 to: Group017-SP23 with permission: write \nAdded Collaborator: llennemann to: Group017-SP23 with permission: write \nAdded Collaborator: pabbi5 to: Group017-SP23 with permission: write \nAdded Collaborator: AstuteFern to: Group017-SP23 with permission: write \nTeam Instructors_Sp23 added to Group017-SP23 with permission admin \nGroup Repo: Group017-SP23 successfuly created!\nRepo URL: https://github.com/COGS118A/Group017-SP23\n\nRepo Group018-SP23 Created... Wait for 3 sec to updates\nFile Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group018-SP23.ipynb \nFile Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group018-SP23.ipynb \nFile Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group018-SP23.ipynb \nAdded Collaborator: ajcagle8 to: Group018-SP23 with permission: write \nAdded Collaborator: aHewig to: Group018-SP23 with permission: write \nAdded Collaborator: notSaranshMalik to: Group018-SP23 with permission: write \nAdded Collaborator: mnodini to: Group018-SP23 with permission: write \nAdded Collaborator: maryamkusman to: Group018-SP23 with permission: write \nTeam Instructors_Sp23 added to Group018-SP23 with permission admin \nGroup Repo: Group018-SP23 successfuly created!\nRepo URL: https://github.com/COGS118A/Group018-SP23\n\nRepo Group019-SP23 Created... Wait for 3 sec to updates\nFile Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group019-SP23.ipynb \nFile Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group019-SP23.ipynb \nFile Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group019-SP23.ipynb \nAdded Collaborator: SSingh44343 to: Group019-SP23 with permission: write \nAdded Collaborator: nathansit to: Group019-SP23 with permission: write \nAdded Collaborator: DaimengSun to: Group019-SP23 with permission: write \nTeam Instructors_Sp23 added to Group019-SP23 with permission admin \nGroup Repo: Group019-SP23 successfuly created!\nRepo URL: https://github.com/COGS118A/Group019-SP23\n\nRepo Group020-SP23 Created... Wait for 3 sec to updates\nFile Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group020-SP23.ipynb \nFile Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group020-SP23.ipynb \nFile Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group020-SP23.ipynb \nAdded Collaborator: ashesh8500 to: Group020-SP23 with permission: write" - }, - { - "objectID": "tutorials/create github group from canvas group.html#resent-invitations", - "href": "tutorials/create github group from canvas group.html#resent-invitations", - "title": "Example Workflow - Create GitHub Group from Canvas Group", - "section": "Resent Invitations", - "text": "Resent Invitations\nGitHub collaboration invites will be expired automatically when the user did not accept the invite after a certain period of time. After all the group repositories are created, the command GitHubGroup.resent_invitations_team_repos will rescind all pending invitations and resent invitation to that collaborators.\nThis command is particularly useful when managing a large volume of repositories as it painlessly re-validated and re-sent all pending invitations of all repositories under a team. We ran this command daily to constantly remind student to accept their GitHub invitations, until all students have a valid permission to the target repository.\n\nggroup.resent_invitations_team_repos(\n team_slug=\"Instructors_Sp23\"\n)\n\nRepository AssignmentNotebooksSource_SP23 :\nThe list of pending invitation:\n[]\nRepository AssignmentNotebooks_SP23 :\nThe list of pending invitation:\n[]\nRepository DiscussionSectionNotebooks :\nThe list of pending invitation:\n[]\nRepository Dockerfiles :\nThe list of pending invitation:\n[]\nRepository Group001-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"demimao\"),\n NamedUser(login=\"xiw013\"),\n NamedUser(login=\"Ju-dyz\"),\n NamedUser(login=\"TaraaHe\")]\ndemimao Invite Revoked \nAdded Collaborator: demimao to: Group001-SP23 with permission: write \n Invite Resent to demimao \nxiw013 Invite Revoked \nAdded Collaborator: xiw013 to: Group001-SP23 with permission: write \n Invite Resent to xiw013 \nJu-dyz Invite Revoked \nAdded Collaborator: Ju-dyz to: Group001-SP23 with permission: write \n Invite Resent to Ju-dyz \nTaraaHe Invite Revoked \nAdded Collaborator: TaraaHe to: Group001-SP23 with permission: write \n Invite Resent to TaraaHe \nRepository Group002-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"jup006\"),\n NamedUser(login=\"ssutharucsd\"),\n NamedUser(login=\"connormcmanigal\"),\n NamedUser(login=\"d3yu\")]\njup006 Invite Revoked \nAdded Collaborator: jup006 to: Group002-SP23 with permission: write \n Invite Resent to jup006 \nssutharucsd Invite Revoked \nAdded Collaborator: ssutharucsd to: Group002-SP23 with permission: write \n Invite Resent to ssutharucsd \nconnormcmanigal Invite Revoked \nAdded Collaborator: connormcmanigal to: Group002-SP23 with permission: write \n Invite Resent to connormcmanigal \nd3yu Invite Revoked \nAdded Collaborator: d3yu to: Group002-SP23 with permission: write \n Invite Resent to d3yu \nRepository Group003-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"scottieboyzhang\"), NamedUser(login=\"Orang1s\")]\nscottieboyzhang Invite Revoked \nAdded Collaborator: scottieboyzhang to: Group003-SP23 with permission: write \n Invite Resent to scottieboyzhang \nOrang1s Invite Revoked \nAdded Collaborator: Orang1s to: Group003-SP23 with permission: write \n Invite Resent to Orang1s \nRepository Group004-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"CDavid99\"),\n NamedUser(login=\"nilu0311\"),\n NamedUser(login=\"g6zhu\")]\nCDavid99 Invite Revoked \nAdded Collaborator: CDavid99 to: Group004-SP23 with permission: write \n Invite Resent to CDavid99 \nnilu0311 Invite Revoked \nAdded Collaborator: nilu0311 to: Group004-SP23 with permission: write \n Invite Resent to nilu0311 \ng6zhu Invite Revoked \nAdded Collaborator: g6zhu to: Group004-SP23 with permission: write \n Invite Resent to g6zhu \nRepository Group005-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"arth-shukla\"),\n NamedUser(login=\"kchen283\"),\n NamedUser(login=\"cdelira9\")]\narth-shukla Invite Revoked \nAdded Collaborator: arth-shukla to: Group005-SP23 with permission: write \n Invite Resent to arth-shukla \nkchen283 Invite Revoked \nAdded Collaborator: kchen283 to: Group005-SP23 with permission: write \n Invite Resent to kchen283 \ncdelira9 Invite Revoked \nAdded Collaborator: cdelira9 to: Group005-SP23 with permission: write \n Invite Resent to cdelira9 \nRepository Group006-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"qdh-2002\"), NamedUser(login=\"Chihhsinli\")]\nqdh-2002 Invite Revoked \nAdded Collaborator: qdh-2002 to: Group006-SP23 with permission: write \n Invite Resent to qdh-2002 \nChihhsinli Invite Revoked \nAdded Collaborator: Chihhsinli to: Group006-SP23 with permission: write \n Invite Resent to Chihhsinli \nRepository Group007-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"YunxiangChi\"),\n NamedUser(login=\"Andrina-iris\"),\n NamedUser(login=\"alien-invader\")]\nYunxiangChi Invite Revoked \nAdded Collaborator: YunxiangChi to: Group007-SP23 with permission: write \n Invite Resent to YunxiangChi \nAndrina-iris Invite Revoked \nAdded Collaborator: Andrina-iris to: Group007-SP23 with permission: write \n Invite Resent to Andrina-iris \nalien-invader Invite Revoked \nAdded Collaborator: alien-invader to: Group007-SP23 with permission: write \n Invite Resent to alien-invader \nRepository Group008-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"wwjasperww\")]\nwwjasperww Invite Revoked \nAdded Collaborator: wwjasperww to: Group008-SP23 with permission: write \n Invite Resent to wwjasperww \nRepository Group009-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"404EZRA\"), NamedUser(login=\"vvishnus\")]\n404EZRA Invite Revoked \nAdded Collaborator: 404EZRA to: Group009-SP23 with permission: write \n Invite Resent to 404EZRA \nvvishnus Invite Revoked \nAdded Collaborator: vvishnus to: Group009-SP23 with permission: write \n Invite Resent to vvishnus \nRepository Group010-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"idereknguyen\"),\n NamedUser(login=\"jaysunl\"),\n NamedUser(login=\"tjamalcodes\"),\n NamedUser(login=\"strawhatwilson23\")]\nidereknguyen Invite Revoked \nAdded Collaborator: idereknguyen to: Group010-SP23 with permission: write \n Invite Resent to idereknguyen \njaysunl Invite Revoked \nAdded Collaborator: jaysunl to: Group010-SP23 with permission: write \n Invite Resent to jaysunl \ntjamalcodes Invite Revoked \nAdded Collaborator: tjamalcodes to: Group010-SP23 with permission: write \n Invite Resent to tjamalcodes \nstrawhatwilson23 Invite Revoked \nAdded Collaborator: strawhatwilson23 to: Group010-SP23 with permission: write \n Invite Resent to strawhatwilson23 \nRepository Group011-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"khchuang12\"),\n NamedUser(login=\"nrejai\"),\n NamedUser(login=\"emdavis02\")]\nkhchuang12 Invite Revoked \nAdded Collaborator: khchuang12 to: Group011-SP23 with permission: write \n Invite Resent to khchuang12 \nnrejai Invite Revoked \nAdded Collaborator: nrejai to: Group011-SP23 with permission: write \n Invite Resent to nrejai \nemdavis02 Invite Revoked \nAdded Collaborator: emdavis02 to: Group011-SP23 with permission: write \n Invite Resent to emdavis02 \nRepository Group012-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"githubharsono\"),\n NamedUser(login=\"JJSanchez23\"),\n NamedUser(login=\"antarasengupta26\")]\ngithubharsono Invite Revoked \nAdded Collaborator: githubharsono to: Group012-SP23 with permission: write \n Invite Resent to githubharsono \n\n\nJJSanchez23 Invite Revoked \nAdded Collaborator: JJSanchez23 to: Group012-SP23 with permission: write \n Invite Resent to JJSanchez23 \nantarasengupta26 Invite Revoked \nAdded Collaborator: antarasengupta26 to: Group012-SP23 with permission: write \n Invite Resent to antarasengupta26 \nRepository Group013-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"Sean1572\"), NamedUser(login=\"jnhuang02\")]\nSean1572 Invite Revoked \nAdded Collaborator: Sean1572 to: Group013-SP23 with permission: write \n Invite Resent to Sean1572 \njnhuang02 Invite Revoked \nAdded Collaborator: jnhuang02 to: Group013-SP23 with permission: write \n Invite Resent to jnhuang02 \nRepository Group014-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"ericzyl\"),\n NamedUser(login=\"50ShadesOfShawn\"),\n NamedUser(login=\"villafun\")]\nericzyl Invite Revoked \nAdded Collaborator: ericzyl to: Group014-SP23 with permission: write \n Invite Resent to ericzyl \n50ShadesOfShawn Invite Revoked \nAdded Collaborator: 50ShadesOfShawn to: Group014-SP23 with permission: write \n Invite Resent to 50ShadesOfShawn \nvillafun Invite Revoked \nAdded Collaborator: villafun to: Group014-SP23 with permission: write \n Invite Resent to villafun \nRepository Group015-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"nikothomas\"),\n NamedUser(login=\"abi2020\"),\n NamedUser(login=\"arjunanna\"),\n NamedUser(login=\"sdsilva1\")]\nnikothomas Invite Revoked \nAdded Collaborator: nikothomas to: Group015-SP23 with permission: write \n Invite Resent to nikothomas \nabi2020 Invite Revoked \nAdded Collaborator: abi2020 to: Group015-SP23 with permission: write \n Invite Resent to abi2020 \narjunanna Invite Revoked \nAdded Collaborator: arjunanna to: Group015-SP23 with permission: write \n Invite Resent to arjunanna \nsdsilva1 Invite Revoked \nAdded Collaborator: sdsilva1 to: Group015-SP23 with permission: write \n Invite Resent to sdsilva1 \nRepository Group016-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"shendu11\"),\n NamedUser(login=\"EthanHu0\"),\n NamedUser(login=\"QilunLiu5216\"),\n NamedUser(login=\"Y3GUO\"),\n NamedUser(login=\"claireZHL\")]\nshendu11 Invite Revoked \nAdded Collaborator: shendu11 to: Group016-SP23 with permission: write \n Invite Resent to shendu11 \nEthanHu0 Invite Revoked \nAdded Collaborator: EthanHu0 to: Group016-SP23 with permission: write \n Invite Resent to EthanHu0 \nQilunLiu5216 Invite Revoked \nAdded Collaborator: QilunLiu5216 to: Group016-SP23 with permission: write \n Invite Resent to QilunLiu5216 \nY3GUO Invite Revoked \nAdded Collaborator: Y3GUO to: Group016-SP23 with permission: write \n Invite Resent to Y3GUO \nclaireZHL Invite Revoked \nAdded Collaborator: claireZHL to: Group016-SP23 with permission: write \n Invite Resent to claireZHL \nRepository Group017-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"AstuteFern\"),\n NamedUser(login=\"sreetama02\"),\n NamedUser(login=\"pabbi5\")]\nAstuteFern Invite Revoked \nAdded Collaborator: AstuteFern to: Group017-SP23 with permission: write \n Invite Resent to AstuteFern \nsreetama02 Invite Revoked \nAdded Collaborator: sreetama02 to: Group017-SP23 with permission: write \n Invite Resent to sreetama02 \npabbi5 Invite Revoked \nAdded Collaborator: pabbi5 to: Group017-SP23 with permission: write \n Invite Resent to pabbi5 \nRepository Group018-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"notSaranshMalik\"),\n NamedUser(login=\"mnodini\"),\n NamedUser(login=\"Maryamkusman\"),\n NamedUser(login=\"ajcagle8\")]\nnotSaranshMalik Invite Revoked \nAdded Collaborator: notSaranshMalik to: Group018-SP23 with permission: write \n Invite Resent to notSaranshMalik \nmnodini Invite Revoked \nAdded Collaborator: mnodini to: Group018-SP23 with permission: write \n Invite Resent to mnodini \nMaryamkusman Invite Revoked \nAdded Collaborator: Maryamkusman to: Group018-SP23 with permission: write \n Invite Resent to Maryamkusman \najcagle8 Invite Revoked \nAdded Collaborator: ajcagle8 to: Group018-SP23 with permission: write \n Invite Resent to ajcagle8 \nRepository Group019-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"nathansit\"),\n NamedUser(login=\"SSingh44343\"),\n NamedUser(login=\"DaimengSun\")]\nnathansit Invite Revoked \nAdded Collaborator: nathansit to: Group019-SP23 with permission: write \n Invite Resent to nathansit \nSSingh44343 Invite Revoked \nAdded Collaborator: SSingh44343 to: Group019-SP23 with permission: write \n Invite Resent to SSingh44343 \nDaimengSun Invite Revoked \nAdded Collaborator: DaimengSun to: Group019-SP23 with permission: write \n Invite Resent to DaimengSun \nRepository Group020-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"ashesh8500\"),\n NamedUser(login=\"ATrapenard\"),\n NamedUser(login=\"meghapareek2003\")]\nashesh8500 Invite Revoked \nAdded Collaborator: ashesh8500 to: Group020-SP23 with permission: write \n Invite Resent to ashesh8500 \nATrapenard Invite Revoked \nAdded Collaborator: ATrapenard to: Group020-SP23 with permission: write \n Invite Resent to ATrapenard \nmeghapareek2003 Invite Revoked \nAdded Collaborator: meghapareek2003 to: Group020-SP23 with permission: write \n Invite Resent to meghapareek2003 \nRepository Group021-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"JasonKrentsel\"),\n NamedUser(login=\"shantellemeganserafin\"),\n NamedUser(login=\"orangejustin\"),\n NamedUser(login=\"jmlai08\")]\nJasonKrentsel Invite Revoked \nAdded Collaborator: JasonKrentsel to: Group021-SP23 with permission: write \n Invite Resent to JasonKrentsel \nshantellemeganserafin Invite Revoked \nAdded Collaborator: shantellemeganserafin to: Group021-SP23 with permission: write \n Invite Resent to shantellemeganserafin \norangejustin Invite Revoked \nAdded Collaborator: orangejustin to: Group021-SP23 with permission: write \n Invite Resent to orangejustin \njmlai08 Invite Revoked \nAdded Collaborator: jmlai08 to: Group021-SP23 with permission: write \n Invite Resent to jmlai08 \nRepository Group022-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"anchen31\"), NamedUser(login=\"nggalen\")]\nanchen31 Invite Revoked \nAdded Collaborator: anchen31 to: Group022-SP23 with permission: write \n Invite Resent to anchen31 \n\n\nnggalen Invite Revoked \nAdded Collaborator: nggalen to: Group022-SP23 with permission: write \n Invite Resent to nggalen \nRepository Group023-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"VigneshJ14\"),\n NamedUser(login=\"helclp\"),\n NamedUser(login=\"rioak\"),\n NamedUser(login=\"chrishrochez\"),\n NamedUser(login=\"kpstern\")]\nVigneshJ14 Invite Revoked \nAdded Collaborator: VigneshJ14 to: Group023-SP23 with permission: write \n Invite Resent to VigneshJ14 \nhelclp Invite Revoked \nAdded Collaborator: helclp to: Group023-SP23 with permission: write \n Invite Resent to helclp \nrioak Invite Revoked \nAdded Collaborator: rioak to: Group023-SP23 with permission: write \n Invite Resent to rioak \nchrishrochez Invite Revoked \nAdded Collaborator: chrishrochez to: Group023-SP23 with permission: write \n Invite Resent to chrishrochez \nkpstern Invite Revoked \nAdded Collaborator: kpstern to: Group023-SP23 with permission: write \n Invite Resent to kpstern \nRepository Group024-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"MoMo339610\"),\n NamedUser(login=\"ccaban6\"),\n NamedUser(login=\"Nakshatra120\"),\n NamedUser(login=\"sim-anna\")]\nMoMo339610 Invite Revoked \nAdded Collaborator: MoMo339610 to: Group024-SP23 with permission: write \n Invite Resent to MoMo339610 \nccaban6 Invite Revoked \nAdded Collaborator: ccaban6 to: Group024-SP23 with permission: write \n Invite Resent to ccaban6 \nNakshatra120 Invite Revoked \nAdded Collaborator: Nakshatra120 to: Group024-SP23 with permission: write \n Invite Resent to Nakshatra120 \nsim-anna Invite Revoked \nAdded Collaborator: sim-anna to: Group024-SP23 with permission: write \n Invite Resent to sim-anna \nRepository Group025-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"Shayfe\"),\n NamedUser(login=\"jenniferwong1808\"),\n NamedUser(login=\"belindayan1000\"),\n NamedUser(login=\"hibask\")]\nShayfe Invite Revoked \nAdded Collaborator: Shayfe to: Group025-SP23 with permission: write \n Invite Resent to Shayfe \njenniferwong1808 Invite Revoked \nAdded Collaborator: jenniferwong1808 to: Group025-SP23 with permission: write \n Invite Resent to jenniferwong1808 \nbelindayan1000 Invite Revoked \nAdded Collaborator: belindayan1000 to: Group025-SP23 with permission: write \n Invite Resent to belindayan1000 \nhibask Invite Revoked \nAdded Collaborator: hibask to: Group025-SP23 with permission: write \n Invite Resent to hibask \nRepository Group026-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"e81786\"),\n NamedUser(login=\"hyun04p\"),\n NamedUser(login=\"Yuanzhen-Zhu\"),\n NamedUser(login=\"aaolivas\")]\ne81786 Invite Revoked \nAdded Collaborator: e81786 to: Group026-SP23 with permission: write \n Invite Resent to e81786 \nhyun04p Invite Revoked \nAdded Collaborator: hyun04p to: Group026-SP23 with permission: write \n Invite Resent to hyun04p \nYuanzhen-Zhu Invite Revoked \nAdded Collaborator: Yuanzhen-Zhu to: Group026-SP23 with permission: write \n Invite Resent to Yuanzhen-Zhu \naaolivas Invite Revoked \nAdded Collaborator: aaolivas to: Group026-SP23 with permission: write \n Invite Resent to aaolivas \nRepository Group027-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"Jddeleon1981\"),\n NamedUser(login=\"natenyul\"),\n NamedUser(login=\"jifsus\")]\nJddeleon1981 Invite Revoked \nAdded Collaborator: Jddeleon1981 to: Group027-SP23 with permission: write \n Invite Resent to Jddeleon1981 \nnatenyul Invite Revoked \nAdded Collaborator: natenyul to: Group027-SP23 with permission: write \n Invite Resent to natenyul \njifsus Invite Revoked \nAdded Collaborator: jifsus to: Group027-SP23 with permission: write \n Invite Resent to jifsus \nRepository Group028-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"mabdilahCSE\"),\n NamedUser(login=\"kylenakai\"),\n NamedUser(login=\"valar23\"),\n NamedUser(login=\"johnpaulonza\")]\nmabdilahCSE Invite Revoked \nAdded Collaborator: mabdilahCSE to: Group028-SP23 with permission: write \n Invite Resent to mabdilahCSE \nkylenakai Invite Revoked \nAdded Collaborator: kylenakai to: Group028-SP23 with permission: write \n Invite Resent to kylenakai \nvalar23 Invite Revoked \nAdded Collaborator: valar23 to: Group028-SP23 with permission: write \n Invite Resent to valar23 \njohnpaulonza Invite Revoked \nAdded Collaborator: johnpaulonza to: Group028-SP23 with permission: write \n Invite Resent to johnpaulonza \nRepository Group029-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"joshsalce\"),\n NamedUser(login=\"lmitbo\"),\n NamedUser(login=\"LukeSkerrett\"),\n NamedUser(login=\"jubamadu\")]\njoshsalce Invite Revoked \nAdded Collaborator: joshsalce to: Group029-SP23 with permission: write \n Invite Resent to joshsalce \nlmitbo Invite Revoked \nAdded Collaborator: lmitbo to: Group029-SP23 with permission: write \n Invite Resent to lmitbo \nLukeSkerrett Invite Revoked \nAdded Collaborator: LukeSkerrett to: Group029-SP23 with permission: write \n Invite Resent to LukeSkerrett \njubamadu Invite Revoked \nAdded Collaborator: jubamadu to: Group029-SP23 with permission: write \n Invite Resent to jubamadu \nRepository Group030-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"kmbnino\"),\n NamedUser(login=\"hiiminbush\"),\n NamedUser(login=\"RafferyChen\"),\n NamedUser(login=\"bonzonwin\")]\nkmbnino Invite Revoked \nAdded Collaborator: kmbnino to: Group030-SP23 with permission: write \n Invite Resent to kmbnino \nhiiminbush Invite Revoked \nAdded Collaborator: hiiminbush to: Group030-SP23 with permission: write \n Invite Resent to hiiminbush \nRafferyChen Invite Revoked \nAdded Collaborator: RafferyChen to: Group030-SP23 with permission: write \n Invite Resent to RafferyChen \nbonzonwin Invite Revoked \nAdded Collaborator: bonzonwin to: Group030-SP23 with permission: write \n Invite Resent to bonzonwin \nRepository Group031-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"kendrick010\"), NamedUser(login=\"getpakt\")]\nkendrick010 Invite Revoked \nAdded Collaborator: kendrick010 to: Group031-SP23 with permission: write \n Invite Resent to kendrick010 \ngetpakt Invite Revoked \nAdded Collaborator: getpakt to: Group031-SP23 with permission: write \n Invite Resent to getpakt \nRepository Group032-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"CharlesXu-Jingyue\"),\n NamedUser(login=\"hinyzee\"),\n NamedUser(login=\"Zachary-chao\"),\n NamedUser(login=\"smurase\")]\n\n\nCharlesXu-Jingyue Invite Revoked \nAdded Collaborator: CharlesXu-Jingyue to: Group032-SP23 with permission: write \n Invite Resent to CharlesXu-Jingyue \nhinyzee Invite Revoked \nAdded Collaborator: hinyzee to: Group032-SP23 with permission: write \n Invite Resent to hinyzee \nZachary-chao Invite Revoked \nAdded Collaborator: Zachary-chao to: Group032-SP23 with permission: write \n Invite Resent to Zachary-chao \nsmurase Invite Revoked \nAdded Collaborator: smurase to: Group032-SP23 with permission: write \n Invite Resent to smurase \nRepository Group033-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"jason886595\"),\n NamedUser(login=\"cqrnik\"),\n NamedUser(login=\"AnyaBoo\"),\n NamedUser(login=\"areenlu\"),\n NamedUser(login=\"TydenRucker\")]\njason886595 Invite Revoked \nAdded Collaborator: jason886595 to: Group033-SP23 with permission: write \n Invite Resent to jason886595 \ncqrnik Invite Revoked \nAdded Collaborator: cqrnik to: Group033-SP23 with permission: write \n Invite Resent to cqrnik \nAnyaBoo Invite Revoked \nAdded Collaborator: AnyaBoo to: Group033-SP23 with permission: write \n Invite Resent to AnyaBoo \nareenlu Invite Revoked \nAdded Collaborator: areenlu to: Group033-SP23 with permission: write \n Invite Resent to areenlu \nTydenRucker Invite Revoked \nAdded Collaborator: TydenRucker to: Group033-SP23 with permission: write \n Invite Resent to TydenRucker \nRepository Group034-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"vinaypillai\"),\n NamedUser(login=\"rchaklas\"),\n NamedUser(login=\"kavyaabalaji\"),\n NamedUser(login=\"Juan2002V\")]\nvinaypillai Invite Revoked \nAdded Collaborator: vinaypillai to: Group034-SP23 with permission: write \n Invite Resent to vinaypillai \nrchaklas Invite Revoked \nAdded Collaborator: rchaklas to: Group034-SP23 with permission: write \n Invite Resent to rchaklas \nkavyaabalaji Invite Revoked \nAdded Collaborator: kavyaabalaji to: Group034-SP23 with permission: write \n Invite Resent to kavyaabalaji \nJuan2002V Invite Revoked \nAdded Collaborator: Juan2002V to: Group034-SP23 with permission: write \n Invite Resent to Juan2002V \nRepository Group035-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"hyperburn777\"),\n NamedUser(login=\"CristianAH\"),\n NamedUser(login=\"rihusedesign\")]\nhyperburn777 Invite Revoked \nAdded Collaborator: hyperburn777 to: Group035-SP23 with permission: write \n Invite Resent to hyperburn777 \nCristianAH Invite Revoked \nAdded Collaborator: CristianAH to: Group035-SP23 with permission: write \n Invite Resent to CristianAH \nrihusedesign Invite Revoked \nAdded Collaborator: rihusedesign to: Group035-SP23 with permission: write \n Invite Resent to rihusedesign \nRepository Group036-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"kohlir2020\"),\n NamedUser(login=\"dhavaljjani\"),\n NamedUser(login=\"Mkhlf\"),\n NamedUser(login=\"esti28\")]\nkohlir2020 Invite Revoked \nAdded Collaborator: kohlir2020 to: Group036-SP23 with permission: write \n Invite Resent to kohlir2020 \ndhavaljjani Invite Revoked \nAdded Collaborator: dhavaljjani to: Group036-SP23 with permission: write \n Invite Resent to dhavaljjani \nMkhlf Invite Revoked \nAdded Collaborator: Mkhlf to: Group036-SP23 with permission: write \n Invite Resent to Mkhlf \nesti28 Invite Revoked \nAdded Collaborator: esti28 to: Group036-SP23 with permission: write \n Invite Resent to esti28 \nRepository Group037-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"gyuj\"),\n NamedUser(login=\"NickAzp\"),\n NamedUser(login=\"kleumas\")]\ngyuj Invite Revoked \nAdded Collaborator: gyuj to: Group037-SP23 with permission: write \n Invite Resent to gyuj \nNickAzp Invite Revoked \nAdded Collaborator: NickAzp to: Group037-SP23 with permission: write \n Invite Resent to NickAzp \nkleumas Invite Revoked \nAdded Collaborator: kleumas to: Group037-SP23 with permission: write \n Invite Resent to kleumas \nRepository Group038-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"crickwang\"),\n NamedUser(login=\"the-bruz\"),\n NamedUser(login=\"m-sherrick\")]\ncrickwang Invite Revoked \nAdded Collaborator: crickwang to: Group038-SP23 with permission: write \n Invite Resent to crickwang \nthe-bruz Invite Revoked \nAdded Collaborator: the-bruz to: Group038-SP23 with permission: write \n Invite Resent to the-bruz \nm-sherrick Invite Revoked \nAdded Collaborator: m-sherrick to: Group038-SP23 with permission: write \n Invite Resent to m-sherrick \nRepository Group039-SP23 :\nThe list of pending invitation:\n[NamedUser(login=\"dannyr742\"),\n NamedUser(login=\"clarissagtz\"),\n NamedUser(login=\"z8jiang\"),\n NamedUser(login=\"jtaolan\")]\ndannyr742 Invite Revoked \nAdded Collaborator: dannyr742 to: Group039-SP23 with permission: write \n Invite Resent to dannyr742 \nclarissagtz Invite Revoked \nAdded Collaborator: clarissagtz to: Group039-SP23 with permission: write \n Invite Resent to clarissagtz \nz8jiang Invite Revoked \nAdded Collaborator: z8jiang to: Group039-SP23 with permission: write \n Invite Resent to z8jiang \njtaolan Invite Revoked \nAdded Collaborator: jtaolan to: Group039-SP23 with permission: write \n Invite Resent to jtaolan \nRepository Lectures :\nThe list of pending invitation:\n[]\nRepository Notebooks :\nThe list of pending invitation:\n[]" - }, - { - "objectID": "tutorials/create github group from canvas group.html#the-end-of-the-workflow", - "href": "tutorials/create github group from canvas group.html#the-end-of-the-workflow", - "title": "Example Workflow - Create GitHub Group from Canvas Group", - "section": "The End of the Workflow", - "text": "The End of the Workflow\nIf you still have concerns, please reach out via GitHub Issue (on the RHS bar) or reach out me directly via email: yuy004@ucsd.edu" - }, - { - "objectID": "tutorials/authentications.html", - "href": "tutorials/authentications.html", - "title": "GitHub / Canvas Authentications", - "section": "", - "text": "To use this software, which essentially is a wrapper package under a API wrapper, you need to provide the program with the appropriate credentials file. We will use the provided credential to authenticate toward both Canvas and GitHub, in order to fetch and manipulate appropriate information.\nNote: The program itself won’t either store nor stole your credentials information. This program will stay on your personal computer and will only use your credentials when needed. For more information about the implementation details, please refer to the GitHub repository to view the source code.\n\nfrom CanvasGroupy.canvas import CanvasGroup\nfrom CanvasGroupy.github import GitHubGroup\n\nThe credentials are stored in the following format. You can download a credential template at the following link.\n\n\n{'GitHub Token': 'token', 'Canvas Token': 'token'}" - }, - { - "objectID": "tutorials/create template issues.html", - "href": "tutorials/create template issues.html", - "title": "Populate Grading Rubric Template on GitHub Repositories", - "section": "", - "text": "import time\nfrom CanvasGroupy import *\ncredentials_fp = \"../credentials.json\"\nghg = GitHubGroup(credentials_fp, org=\"COGS118A\")\n\nSuccessfully Authenticated. GitHub account: scott-yj-yang \nTarget Organization Set: COGS118A \n\n\n\nfor i in range(1, 40):\n repo_name = f\"Group{i:03}-SP23\"\n repo = ghg.get_repo(repo_name)\n ghg.create_issue_from_md(repo, \"../CanvasGroupy/nbs/feedback_template/proposal_feedback.md\")\n time.sleep(1)\n\nIn the repo: Group021-SP23,\nIssue Project Proposal Feedback Created!\nIn the repo: Group022-SP23,\nIssue Project Proposal Feedback Created!\nIn the repo: Group023-SP23,\nIssue Project Proposal Feedback Created!\nIn the repo: Group024-SP23,\nIssue Project Proposal Feedback Created!\nIn the repo: Group025-SP23,\nIssue Project Proposal Feedback Created!\nIn the repo: Group026-SP23,\nIssue Project Proposal Feedback Created!\nIn the repo: Group027-SP23,\nIssue Project Proposal Feedback Created!\nIn the repo: Group028-SP23,\nIssue Project Proposal Feedback Created!\nIn the repo: Group029-SP23,\nIssue Project Proposal Feedback Created!\nIn the repo: Group030-SP23,\nIssue Project Proposal Feedback Created!\nIn the repo: Group031-SP23,\nIssue Project Proposal Feedback Created!\nIn the repo: Group032-SP23,\nIssue Project Proposal Feedback Created!\nIn the repo: Group033-SP23,\nIssue Project Proposal Feedback Created!\nIn the repo: Group034-SP23,\nIssue Project Proposal Feedback Created!\nIn the repo: Group035-SP23,\nIssue Project Proposal Feedback Created!\nIn the repo: Group036-SP23,\nIssue Project Proposal Feedback Created!\nIn the repo: Group037-SP23,\nIssue Project Proposal Feedback Created!\nIn the repo: Group038-SP23,\nIssue Project Proposal Feedback Created!\nIn the repo: Group039-SP23,\nIssue Project Proposal Feedback Created!" - }, - { - "objectID": "index.html", - "href": "index.html", - "title": "CanvasGroupy", - "section": "", - "text": "View our documentation at this link\nThis module will use GroupEng to create canvas and github groups." - }, - { - "objectID": "index.html#install", - "href": "index.html#install", - "title": "CanvasGroupy", - "section": "Install", - "text": "Install\npip install CanvasGroupy" - }, - { - "objectID": "index.html#how-to-use", - "href": "index.html#how-to-use", - "title": "CanvasGroupy", - "section": "How to use", - "text": "How to use\nPlease visit GroupEng Official Website to see the documnetation of how to use GroupEng.\n\nassign_groups(\"example/sample_group_specification.groupeng\")\n\n['-', '-', 'B', '-', '-']\n['B', '-', 'H', '-', '-']\n['H', '-', '-', '-', '-']\n['-', 'H', '-', 'B', 'H']\n['-', '-', '-', 'B', '-']\n['-', '-', 'H', '-', '-']\n['nanotech', 'renewable energy', 'nanotech', 'nanotech', 'nanotech']\n['automotive', 'automotive', 'robotics', 'automotive', 'renewable energy']\n['automotive', 'statistics', 'automotive', 'renewable energy', 'renewable energy']\n['automotive', 'automotive', 'statistics', 'renewable energy', 'renewable energy']\n['automotive', 'automotive', 'renewable energy', 'statistics', 'renewable energy']\n['renewable energy', 'automotive', 'automotive', 'statistics', 'renewable energy']\n['CS', 'EE', 'Mech E', 'CS', 'EE']\n['CS', 'EE', 'Mech E', 'Mech E', 'EE']\n['CS', 'Civ E', 'EE', 'Mech E', 'Mech E']\n['EE', 'Civ E', 'Civ E', 'EE', 'Mech E']\n['EE', 'Mech E', 'CS', 'EE', 'EE']\n['Civ E', 'Mech E', 'CS', 'Mech E', 'EE']\n['y', 'y', 'y', 'y', 'y']\n['y', 'y', 'y', 'y', 'y']\n['y', 'y', 'y', 'y', 'y']\n['y', 'y', 'y', 'y', 'y']\n['y', 'y', 'y', 'y', 'y']\n['-', '-', 'y', '-', 'y']\n['y', 'y', 'y', 'y', 'y']\n['y', 'y', 'y', 'y', 'y']\n['y', 'y', 'y', 'y', 'y']\n['y', 'y', 'y', 'y', 'y']\n['y', 'y', 'y', 'y', 'y']\n['y', '-', '-', 'y', 'y']\n['y', 'y', 'y', '-', '-']\n['y', 'y', '-', 'y', '-']\n['-', '-', '-', 'y', 'y']\n['y', '-', 'y', '-', 'y']\n['-', '-', 'y', '-', 'y']\n[3.2579174234, 3.5995299693, 3.2756432963, 4.220160605, 2.5723254477]\n[3.2579174234, 3.2756432963, 2.5723254477, 3.5995299693, 4.220160605]\n[4.220160605, 3.5995299693, 3.2756432963, 3.2579174234, 2.5723254477]\n[3.2756432963, 4.220160605, 3.2579174234, 2.5723254477, 3.5995299693]\n[3.5995299693, 4.220160605, 3.2756432963, 3.2579174234, 2.5723254477]\n[3.5995299693, 3.2756432963, 2.5723254477, 3.2579174234, 4.220160605]\n\n\n(False, 'groups_example_2023-04-18_13-00-35')" - }, - { - "objectID": "feedback_template/checkpoint_feedback.html", - "href": "feedback_template/checkpoint_feedback.html", - "title": "CanvasGroupy", - "section": "", - "text": "Total:\n\n\n\n\n\nCategory\nFull Point\nYour Score\nComment\n\n\n\n\nAbstract\n0.5\n\n\n\n\nBackground\n0.5\n\n\n\n\nProblem Statement\n0.5\n\n\n\n\nData\n1\n\n\n\n\nProposed Solution\n1\n\n\n\n\nMetrics\n1\n\n\n\n\nPreliminary Results\n1\n\n\n\n\nEthics & Privacy\n1\n\n\n\n\nTeam expectations\n0.25\n\n\n\n\nTimeline\n0.25\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nCategory\nFull Point\nExplanation\n\n\n\n\nAbstract\n0.5\nAbstract is informative, succinct, and clear. It offers specific details about the educational issue, variables (data), context, proposed methods, and measurement of performance/success of the study.\n\n\nBackground\n0.5\nUse a minimum of 2 or 3 citations Include a general introduction to your topic Narrative integrates critical and logical details from the peer-reviewed theoretical and research literature. Each key research component is grounded to the literature. Attention is given to different perspectives, threats to validity, and opinion vs. evidence.\n\n\nProblem Statement\n0.5\nPresents a well-defined and significant research problem Include at least one ML-relevant potential solution. Articulates clear, reasonable research questions given the purpose, design, and methods of the project. All variables and controls have been appropriately defined. Proposals are clearly supported from the research and theoretical literature. All elements are mutually supportive.\n\n\nData\n1\nMultiple data sources for each aspect of the project. All data sources are fully described and referenced. Data is appropriate to the question/goal and large enough data points >1k observations and >5 variable The details of the descriptions also make it clear how they support the needs of the project.\n\n\nProposed Solution\n1\nThe elements of the process were described succinctly and with clarity about how they are connected to each other Included description how the solution will be tested.\n\n\nMetrics\n1\nThe metrics are described clearly and succinctly. Their appropriateness for addressing the research problem is clearly described. Provided the mathematical representations of metrics\n\n\nPreliminary Results\n1\nAnalyzing the suitability of a dataset or algorithm for prediction/solving your problem Performing feature selection or hand-designing features from the raw data. Describe the features available/created and/or show the code for selection/creation Dataset actually clean and usable after feature selection is carried out Showing the performance of a base model/hyper-parameter setting. Solve the task with one “default” algorithm and characterize the performance level of that base model. At least one of the three: Learning curves or validation curves for a particular model Tables/graphs showing the performance of different models/hyper-parameters\n\n\nEthics & Privacy\n1\nThoughtful discussion of ethical concerns included. Ethical concerns consider the whole data science process (question asked, data collected, data being used, the bias in data, analysis, post-analysis, etc.). How your group handled bias/ethical concerns clearly described\n\n\nTeam expectations\n0.25\nThe list clearly was the subject of a thoughtful approach and already indicates a well-working team\n\n\nTimeline\n0.25\nThe timeline was clearly the subject of a thoughtful approach and indicates that the team has a detailed plan that seems appropriate and completable in the allotted time." - }, - { - "objectID": "feedback_template/proposal_feedback.html", - "href": "feedback_template/proposal_feedback.html", - "title": "CanvasGroupy", - "section": "", - "text": "Score = …\n\n\n\n\n\n\n\nQuality\nReasons\n\n\n\n\nAbstract\n\n\n\n\nResearch question\n\n\n\n\nBackground\n\n\n\n\nHypothesis\n\n\n\n\nData\n\n\n\n\nEthics\n\n\n\n\nTeam expectations\n\n\n\n\nTimeline\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nUnsatisfactory\nDeveloping\nProficient\nExcellent\n\n\n\n\nAbstract\nAbstract is confusing or fails to offer important details about the issue, variables, context, or methods of the project.\nAbstract lacks relevance or fails to offer appropriate details about the issue, variables, context, or methods of the project.\nAbstract is relevant, offering details about the research project.\nAbstract is informative, succinct, and clear. It offers specific details about the educational issue, variables, context, and proposed methods of the study.\n\n\nResearch question\nResearch issue remains unclear. The research purpose, questions, hypotheses, definitions or variables and controls are still largely undefined, or when they are are poorly formed, ambiguous, or not logically connected to the description of the problem. Unclear connections to the literature.\nResearch issue is identified, but statement is too broad or fails to establish the importance of the problem. The research purpose, questions, hypotheses, definitions or variables and controls are poorly formed, ambiguous, or not logically connected to the description of the problem. Unclear connections to the literature.\nIdentifies a relevant research issue. Research questions are succinctly stated, connected to the research issue, and supported by the literature. Variables and controls have been identified and described. Connections are established with the literature.\nPresents a significant research problem. Articulates clear, reasonable research questions given the purpose, design, and methods of the project. All variables and controls have been appropriately defined. Proposals are clearly supported from the research and theoretical literature. All elements are mutually supportive.\n\n\nBackground\nDid not have at least 2 reliable and relevant sources. Or relevant sources were not used in relevant ways\nA key component was not connected to the research literature. Selected literature was from unreliable sources. Literary supports were vague or ambiguous.\nKey research components were connected to relevant, reliable theoretical and research literature.\nNarrative integrates critical and logical details from the peer-reviewed theoretical and research literature. Each key research component is grounded to the literature. Attention is given to different perspectives, threats to validity, and opinion vs. evidence.\n\n\nHypothesis\nLacks most details; vague or interpretable in different ways. Or seems completely unrealistic.\nA key detail to understand the hypothesis or the rationale behind it was not described well enough\nThe hypothesis is clear. All elements needed to understand the rationale were described in sufficient detail\nThe hypothesis and its rationale were described succinctly and with clarity about how they are connected to each other\n\n\nData\nDid not have references to relevant data sources for this problem. Did not describe the data obtained at those sources\nA key data source was not referenced or described in satisfactory level of detail\nAll relevant data sources were referenced and described in terms of their key variables and size\nMultiple data sources for each aspect of the project, All data sources are fully described and referenced. The details of the descriptions also make it clear how they support the needs ot the project.\n\n\nEthics\nNo effort or just says we have no ethical concerns\nMinimal ethical section; probably just talks about data privacy and no unintended consequences discussion. Ethical concerns raised seem irrelevant.\nEthical concerns described are appropriate and described sufficiently\nEthical concerns are described clearly and succinctly. This was clearly a thoughtful and nuanced approach to the issues\n\n\nTeam expectations\nLack of expectations\nThe list of expectations feels incomplete and perfunctory\nIt feels like the list of expectations is complete and seems appropriate\nThe list clearly was the subject of a thoughtful approach and already indicates a well-working team\n\n\nTimeline\nLack of timeline. Or timeline is completely unrealistic\nThe timeline feels incomplete and perfunctory. The timeline feels either too fast or too slow for the progress you expect a group can make\nIt feels like the timeline is complete and appropriate. it can likely be completed as is in the available amount of time\nThe timeline was clearly the subject of a thoughtful approach and indicates that the team has a detailed plan that seems appropriate and completable in the allotted time.\n\n\n\nScoring: Out of 9 points\n\nEach Developing => -0.75 pts\nEach Unsatisfactory/Missing => -1.5 pts\n\nuntil the score is 0\n\n\nIf students address the detailed feedback in a future checkpoint they will earn these points back" - }, - { - "objectID": "feedback_template/final_project_feedback.html", - "href": "feedback_template/final_project_feedback.html", - "title": "CanvasGroupy", - "section": "", - "text": "Category\nFull Point\nYour Score\nComment\n\n\n\n\nTitle & Abstract\n1\n\n\n\n\nBackground\n1\n\n\n\n\nProblem Statement\n1\n\n\n\n\nData\n1\n\n\n\n\nProposed Solution\n1.25\n\n\n\n\nMetrics\n1.25\n\n\n\n\nResults\n1.25\n\n\n\n\nInterpreting the result\n1.25\n\n\n\n\nLimitations\n1.25\n\n\n\n\nEthics & Privacy\n0.75\n\n\n\n\nConclusion\n1\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nCategory\nFull Point\nExpectation\n\n\n\n\nTitle & Abstract\n1\nTitle & abstract is informative, succinct, and clear. It offers specific details about the educational issue, variables (data), context, proposed methods, and measurement of performance/success of the study.\n\n\nBackground\n1\nUse a minimum of 2 or 3 citationsInclude a general introduction to your topicNarrative integrates critical and logical details from the peer-reviewed theoretical and research literature. Each key research component is grounded to the literature. Attention is given to different perspectives, threats to validity, and opinion vs. evidence.\n\n\nProblem Statement\n1\nPresents a well-defined and significant research problem Articulates clear, reasonable research questions given the purpose, design, and methods of the project. All variables and controls have been appropriately defined. Proposals are clearly supported from the research and theoretical literature. All elements are mutually supportive.\n\n\nData\n1\nMultiple data sources for each aspect of the project. All data sources are fully described and referenced. Data is appropriate to the question/goal and large enough data points >1k observations and >5 variable The details of the descriptions of the data also make it clear how they support the needs of the project. Details of data storage and cleaning\n\n\nProposed Solution\n1.25\nThe elements of the process were described succinctly and with clarity about how they are connected to each otherIncluded description how the solution will be tested. (0.5 pt)\n\n\nMetrics\n1.25\nThe metrics are described clearly and succinctly. Their appropriateness for addressing the research problem is clearly described.\n\n\nResults\n1.25\nDoes a good model/hyper-parameter selection using more than one model and hyperparameter in hyperparameter search. Include the detailed code and analysis results of the main pointsPerforms multiple secondary analysis such as learning curves, heat maps looking at where in the parameter space things are good/bad, uses statistical testing\n\n\nInterpreting the result\n1.25\nThink clearly about the results and obtain one main point and 2-4 secondary points (2-5 sentences per point).Highlight HOW the results support those points. Understand what they are doing in the previous “Results” section\n\n\nLimitations\n1.25\nHas a sense of what to do next, and has good explorations of the limitations.\n\n\nEthics & Privacy\n0.75\nThoughtful discussion of ethical concerns included. Ethical concerns consider the whole data science process (question asked, data collected, data being used, the bias in data, analysis, post-analysis, etc.).How your group handled bias/ethical concerns clearly described\n\n\nConclusion\n1\nClearly recapitulates the results and provides context, perhaps including the literature of the field." - }, - { - "objectID": "api/groupeng_assign.html#api", - "href": "api/groupeng_assign.html#api", - "title": "GroupEngAssign", - "section": "API", - "text": "API\n\nsource\n\nAssignGroup.assign_groups\n\n AssignGroup.assign_groups (groupeng_config:str,\n assign_canvas_group=False,\n create_gh_repo=False, username_quiz_id=-1,\n in_group_category='', suffix='')\n\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\ngroupeng_config\nstr\n\nDirectory for the GroupEng config yml file\n\n\nassign_canvas_group\nbool\nFalse\ndirectly assign canvas groups\n\n\ncreate_gh_repo\nbool\nFalse\ndirectly create GitHub repos\n\n\nusername_quiz_id\nint\n-1\nusername quiz id from canvas course\n\n\nin_group_category\nstr\n\nspecify which group category the group belongs to\n\n\nsuffix\nstr\n\nsuffix to the group name\n\n\nReturns\n(, )\n\nStatus and output directory of the compiled file.\n\n\n\n\nsource\n\n\nAssignGroup.create_canvas_group\n\n AssignGroup.create_canvas_group (in_group_category='', suffix='')\n\nCreate canvas groups based on the generated group configuration\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nin_group_category\nstr\n\nspecify which group category the group belongs to\n\n\nsuffix\nstr\n\nsuffix to the group name\n\n\n\n\nsource\n\n\nAssignGroup.create_github_group\n\n AssignGroup.create_github_group (username_quiz_id:int)\n\n\n\n\n\nType\nDetails\n\n\n\n\nusername_quiz_id\nint\nusername quiz id from canvas course\n\n\n\n\n# Create authenticated objects\nghg = GitHubGroup(\"../../../credentials.json\",\n \"COGS118A\"\n )\ncg = CanvasGroup(\"../../../credentials.json\",\n course_id=45532,\n )\n# create assign group object\nag = AssignGroup(ghg, cg)\n\nSuccessfully Authenticated. GitHub account: scott-yj-yang \nTarget Organization Set: COGS118A \nAuthorization Successful!\nCourse Set: COGS 195 - Instructional Apprenticeship - Fleischer [SP23] \nGetting List of Users... This might take a while...\nUsers Fetch Complete! The course has 5 students.\n\n\n\n# create a group category to hold students\ncg.create_group_category({\"name\": \"Project 1\"})\n\nGroupCategory(_requester=, id=16456, name=Project 1, role=None, self_signup=None, group_limit=None, auto_leader=None, created_at=2023-05-17T20:38:56Z, created_at_date=2023-05-17 20:38:56+00:00, context_type=Course, course_id=45532, groups_count=0, unassigned_users_count=5, protected=False, allows_multiple_memberships=False, is_member=False)\n\n\n\n# assign, create both Canvas and GitHub Group in one call\nstatus, out_dir = ag.assign_groups(\"../data/195_group_specification.groupeng\",\n assign_canvas_group=True,\n create_gh_repo=True,\n username_quiz_id=139925,\n in_group_category=\"Project 1\",\n suffix=\"-SP23-Testing\"\n )\n\n['H', 'B', '-']\n['B', 'H', '-']\n['-', 'H', 'B']\n['B', 'H', '-']\n['B', '-', 'H']\n['-', 'B', 'H']\n[None, 3.9, 3.1]\n[3.9, 3.1, None]\n[3.4, 2.5, 2.1]\n[3.9, None, 3.1]\n[3.4, 2.1, 2.5]\n[3.4, 2.1, 2.5]\nIn Group Set: Project 1,\nGroup Group1-SP23-Testing Created!\nMember dol005 Joined group Group1-SP23-Testing\nMember xiw013 Joined group Group1-SP23-Testing\nIn Group Set: Project 1,\nGroup Group2-SP23-Testing Created!\nMember jiz088 Joined group Group2-SP23-Testing\nMember jiz100 Joined group Group2-SP23-Testing\nMember nmackler Joined group Group2-SP23-Testing\nQuiz: GitHub Username fetch! \nGenerating Student Analaysis...\n[====================] 100%\nReport Generated!\nThe Question asked is 1399692: In plain text, what is your GitHub Username? Absolutely no typo, no extra space, no hyperlink please.. \nMake sure this is the correct question where you asked student for their GitHub id.\nIf you need to change the index of columns, change the col_index argument of this call.\ndol005's GitHub Username not found\nxiw013's GitHub Username not found\nRepo Group1-SP23-Testing Created... Wait for 3 sec to updates\nFile Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group1-SP23-Testing.ipynb \nFile Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group1-SP23-Testing.ipynb \nFile Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group1-SP23-Testing.ipynb \nTeam Instructors_Sp23 added to Group1-SP23-Testing with permission admin \nGroup Repo: Group1-SP23-Testing successfuly created!\nRepo URL: https://github.com/COGS118A/Group1-SP23-Testing\n\njiz100's GitHub Username not found\njiz088's GitHub Username not found\nRepo Group2-SP23-Testing Created... Wait for 3 sec to updates\nFile Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group2-SP23-Testing.ipynb \nFile Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group2-SP23-Testing.ipynb \nFile Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group2-SP23-Testing.ipynb \nAdded Collaborator: nmackler to: Group2-SP23-Testing with permission: write \nTeam Instructors_Sp23 added to Group2-SP23-Testing with permission admin \nGroup Repo: Group2-SP23-Testing successfuly created!\nRepo URL: https://github.com/COGS118A/Group2-SP23-Testing\n\n\n\nThe false means that at least one requirement is not satisfied. We can take a look at the file that was generated." - } -] \ No newline at end of file diff --git a/_proc/_docs/site_libs/bootstrap/bootstrap-icons.css b/_proc/_docs/site_libs/bootstrap/bootstrap-icons.css deleted file mode 100644 index f51d04b..0000000 --- a/_proc/_docs/site_libs/bootstrap/bootstrap-icons.css +++ /dev/null @@ -1,1704 +0,0 @@ -@font-face { - font-family: "bootstrap-icons"; - src: -url("./bootstrap-icons.woff?524846017b983fc8ded9325d94ed40f3") format("woff"); -} - -.bi::before, -[class^="bi-"]::before, -[class*=" bi-"]::before { - display: inline-block; - font-family: bootstrap-icons !important; - font-style: normal; - font-weight: normal !important; - font-variant: normal; - text-transform: none; - line-height: 1; - vertical-align: -.125em; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -.bi-123::before { content: "\f67f"; } -.bi-alarm-fill::before { content: "\f101"; } -.bi-alarm::before { content: "\f102"; } -.bi-align-bottom::before { content: "\f103"; } -.bi-align-center::before { content: "\f104"; } -.bi-align-end::before { content: "\f105"; } -.bi-align-middle::before { content: "\f106"; } -.bi-align-start::before { content: "\f107"; } -.bi-align-top::before { content: "\f108"; } -.bi-alt::before { content: "\f109"; } -.bi-app-indicator::before { content: "\f10a"; } -.bi-app::before { content: "\f10b"; } -.bi-archive-fill::before { content: "\f10c"; } -.bi-archive::before { content: "\f10d"; } -.bi-arrow-90deg-down::before { content: "\f10e"; } -.bi-arrow-90deg-left::before { content: "\f10f"; } -.bi-arrow-90deg-right::before { content: "\f110"; } -.bi-arrow-90deg-up::before { content: "\f111"; } -.bi-arrow-bar-down::before { content: "\f112"; } -.bi-arrow-bar-left::before { content: "\f113"; } -.bi-arrow-bar-right::before { content: "\f114"; } -.bi-arrow-bar-up::before { content: "\f115"; } -.bi-arrow-clockwise::before { content: "\f116"; } -.bi-arrow-counterclockwise::before { content: "\f117"; } -.bi-arrow-down-circle-fill::before { content: "\f118"; } -.bi-arrow-down-circle::before { content: "\f119"; } -.bi-arrow-down-left-circle-fill::before { content: "\f11a"; } -.bi-arrow-down-left-circle::before { content: "\f11b"; } -.bi-arrow-down-left-square-fill::before { content: "\f11c"; } -.bi-arrow-down-left-square::before { content: "\f11d"; } -.bi-arrow-down-left::before { content: "\f11e"; } -.bi-arrow-down-right-circle-fill::before { content: "\f11f"; } -.bi-arrow-down-right-circle::before { content: "\f120"; } -.bi-arrow-down-right-square-fill::before { content: "\f121"; } -.bi-arrow-down-right-square::before { content: "\f122"; } -.bi-arrow-down-right::before { content: "\f123"; } -.bi-arrow-down-short::before { content: "\f124"; } -.bi-arrow-down-square-fill::before { content: "\f125"; } -.bi-arrow-down-square::before { content: "\f126"; } -.bi-arrow-down-up::before { content: "\f127"; } -.bi-arrow-down::before { content: "\f128"; } -.bi-arrow-left-circle-fill::before { content: "\f129"; } -.bi-arrow-left-circle::before { content: "\f12a"; } -.bi-arrow-left-right::before { content: "\f12b"; } -.bi-arrow-left-short::before { content: "\f12c"; } -.bi-arrow-left-square-fill::before { content: "\f12d"; } -.bi-arrow-left-square::before { content: "\f12e"; } -.bi-arrow-left::before { content: "\f12f"; } -.bi-arrow-repeat::before { content: "\f130"; } -.bi-arrow-return-left::before { content: "\f131"; } -.bi-arrow-return-right::before { content: "\f132"; } -.bi-arrow-right-circle-fill::before { content: "\f133"; } -.bi-arrow-right-circle::before { content: "\f134"; } -.bi-arrow-right-short::before { content: "\f135"; } -.bi-arrow-right-square-fill::before { content: "\f136"; } -.bi-arrow-right-square::before { content: "\f137"; } -.bi-arrow-right::before { content: "\f138"; } -.bi-arrow-up-circle-fill::before { content: "\f139"; } -.bi-arrow-up-circle::before { content: "\f13a"; } -.bi-arrow-up-left-circle-fill::before { content: "\f13b"; } -.bi-arrow-up-left-circle::before { content: "\f13c"; } -.bi-arrow-up-left-square-fill::before { content: "\f13d"; } -.bi-arrow-up-left-square::before { content: "\f13e"; } -.bi-arrow-up-left::before { content: "\f13f"; } -.bi-arrow-up-right-circle-fill::before { content: "\f140"; } -.bi-arrow-up-right-circle::before { content: "\f141"; } -.bi-arrow-up-right-square-fill::before { content: "\f142"; } -.bi-arrow-up-right-square::before { content: "\f143"; } -.bi-arrow-up-right::before { content: "\f144"; } -.bi-arrow-up-short::before { content: "\f145"; } -.bi-arrow-up-square-fill::before { content: "\f146"; } -.bi-arrow-up-square::before { content: "\f147"; } -.bi-arrow-up::before { content: "\f148"; } -.bi-arrows-angle-contract::before { content: "\f149"; } -.bi-arrows-angle-expand::before { content: "\f14a"; } -.bi-arrows-collapse::before { content: "\f14b"; } -.bi-arrows-expand::before { content: "\f14c"; } -.bi-arrows-fullscreen::before { content: "\f14d"; } -.bi-arrows-move::before { content: "\f14e"; } -.bi-aspect-ratio-fill::before { content: "\f14f"; } -.bi-aspect-ratio::before { content: "\f150"; } -.bi-asterisk::before { content: "\f151"; } -.bi-at::before { content: "\f152"; } -.bi-award-fill::before { content: "\f153"; } -.bi-award::before { content: "\f154"; } -.bi-back::before { content: "\f155"; } -.bi-backspace-fill::before { content: "\f156"; } -.bi-backspace-reverse-fill::before { content: "\f157"; } -.bi-backspace-reverse::before { content: "\f158"; } -.bi-backspace::before { content: "\f159"; } -.bi-badge-3d-fill::before { content: "\f15a"; } -.bi-badge-3d::before { content: "\f15b"; } -.bi-badge-4k-fill::before { content: "\f15c"; } -.bi-badge-4k::before { content: "\f15d"; } -.bi-badge-8k-fill::before { content: "\f15e"; } -.bi-badge-8k::before { content: "\f15f"; } -.bi-badge-ad-fill::before { content: "\f160"; } -.bi-badge-ad::before { content: "\f161"; } -.bi-badge-ar-fill::before { content: "\f162"; } -.bi-badge-ar::before { content: "\f163"; } -.bi-badge-cc-fill::before { content: "\f164"; } -.bi-badge-cc::before { content: "\f165"; } -.bi-badge-hd-fill::before { content: "\f166"; } -.bi-badge-hd::before { content: "\f167"; } -.bi-badge-tm-fill::before { content: "\f168"; } -.bi-badge-tm::before { content: "\f169"; } -.bi-badge-vo-fill::before { content: "\f16a"; } -.bi-badge-vo::before { content: "\f16b"; } -.bi-badge-vr-fill::before { content: "\f16c"; } -.bi-badge-vr::before { content: "\f16d"; } -.bi-badge-wc-fill::before { content: "\f16e"; } -.bi-badge-wc::before { content: "\f16f"; } -.bi-bag-check-fill::before { content: "\f170"; } -.bi-bag-check::before { content: "\f171"; } -.bi-bag-dash-fill::before { content: "\f172"; } -.bi-bag-dash::before { content: "\f173"; } -.bi-bag-fill::before { content: "\f174"; } -.bi-bag-plus-fill::before { content: "\f175"; } -.bi-bag-plus::before { content: "\f176"; } -.bi-bag-x-fill::before { content: "\f177"; } -.bi-bag-x::before { content: "\f178"; } -.bi-bag::before { content: "\f179"; } -.bi-bar-chart-fill::before { content: "\f17a"; } -.bi-bar-chart-line-fill::before { content: "\f17b"; } -.bi-bar-chart-line::before { content: "\f17c"; } -.bi-bar-chart-steps::before { content: "\f17d"; } -.bi-bar-chart::before { content: "\f17e"; } -.bi-basket-fill::before { content: "\f17f"; } -.bi-basket::before { content: "\f180"; } -.bi-basket2-fill::before { content: "\f181"; } -.bi-basket2::before { content: "\f182"; } -.bi-basket3-fill::before { content: "\f183"; } -.bi-basket3::before { content: "\f184"; } -.bi-battery-charging::before { content: "\f185"; } -.bi-battery-full::before { content: "\f186"; } -.bi-battery-half::before { content: "\f187"; } -.bi-battery::before { content: "\f188"; } -.bi-bell-fill::before { content: "\f189"; } -.bi-bell::before { content: "\f18a"; } -.bi-bezier::before { content: "\f18b"; } -.bi-bezier2::before { content: "\f18c"; } -.bi-bicycle::before { content: "\f18d"; } -.bi-binoculars-fill::before { content: "\f18e"; } -.bi-binoculars::before { content: "\f18f"; } -.bi-blockquote-left::before { content: "\f190"; } -.bi-blockquote-right::before { content: "\f191"; } -.bi-book-fill::before { content: "\f192"; } -.bi-book-half::before { content: "\f193"; } -.bi-book::before { content: "\f194"; } -.bi-bookmark-check-fill::before { content: "\f195"; } -.bi-bookmark-check::before { content: "\f196"; } -.bi-bookmark-dash-fill::before { content: "\f197"; } -.bi-bookmark-dash::before { content: "\f198"; } -.bi-bookmark-fill::before { content: "\f199"; } -.bi-bookmark-heart-fill::before { content: "\f19a"; } -.bi-bookmark-heart::before { content: "\f19b"; } -.bi-bookmark-plus-fill::before { content: "\f19c"; } -.bi-bookmark-plus::before { content: "\f19d"; } -.bi-bookmark-star-fill::before { content: "\f19e"; } -.bi-bookmark-star::before { content: "\f19f"; } -.bi-bookmark-x-fill::before { content: "\f1a0"; } -.bi-bookmark-x::before { content: "\f1a1"; } -.bi-bookmark::before { content: "\f1a2"; } -.bi-bookmarks-fill::before { content: "\f1a3"; } -.bi-bookmarks::before { content: "\f1a4"; } -.bi-bookshelf::before { content: "\f1a5"; } -.bi-bootstrap-fill::before { content: "\f1a6"; } -.bi-bootstrap-reboot::before { content: "\f1a7"; } -.bi-bootstrap::before { content: "\f1a8"; } -.bi-border-all::before { content: "\f1a9"; } -.bi-border-bottom::before { content: "\f1aa"; } -.bi-border-center::before { content: "\f1ab"; } -.bi-border-inner::before { content: "\f1ac"; } -.bi-border-left::before { content: "\f1ad"; } -.bi-border-middle::before { content: "\f1ae"; } -.bi-border-outer::before { content: "\f1af"; } -.bi-border-right::before { content: "\f1b0"; } -.bi-border-style::before { content: "\f1b1"; } -.bi-border-top::before { content: "\f1b2"; } -.bi-border-width::before { content: "\f1b3"; } -.bi-border::before { content: "\f1b4"; } -.bi-bounding-box-circles::before { content: "\f1b5"; } -.bi-bounding-box::before { content: "\f1b6"; } -.bi-box-arrow-down-left::before { content: "\f1b7"; } -.bi-box-arrow-down-right::before { content: "\f1b8"; } -.bi-box-arrow-down::before { content: "\f1b9"; } -.bi-box-arrow-in-down-left::before { content: "\f1ba"; } -.bi-box-arrow-in-down-right::before { content: "\f1bb"; } -.bi-box-arrow-in-down::before { content: "\f1bc"; } -.bi-box-arrow-in-left::before { content: "\f1bd"; } -.bi-box-arrow-in-right::before { content: "\f1be"; } -.bi-box-arrow-in-up-left::before { content: "\f1bf"; } -.bi-box-arrow-in-up-right::before { content: "\f1c0"; } -.bi-box-arrow-in-up::before { content: "\f1c1"; } -.bi-box-arrow-left::before { content: "\f1c2"; } -.bi-box-arrow-right::before { content: "\f1c3"; } -.bi-box-arrow-up-left::before { content: "\f1c4"; } -.bi-box-arrow-up-right::before { content: "\f1c5"; } -.bi-box-arrow-up::before { content: "\f1c6"; } -.bi-box-seam::before { content: "\f1c7"; } -.bi-box::before { content: "\f1c8"; } -.bi-braces::before { content: "\f1c9"; } -.bi-bricks::before { content: "\f1ca"; } -.bi-briefcase-fill::before { content: "\f1cb"; } -.bi-briefcase::before { content: "\f1cc"; } -.bi-brightness-alt-high-fill::before { content: "\f1cd"; } -.bi-brightness-alt-high::before { content: "\f1ce"; } -.bi-brightness-alt-low-fill::before { content: "\f1cf"; } -.bi-brightness-alt-low::before { content: "\f1d0"; } -.bi-brightness-high-fill::before { content: "\f1d1"; } -.bi-brightness-high::before { content: "\f1d2"; } -.bi-brightness-low-fill::before { content: "\f1d3"; } -.bi-brightness-low::before { content: "\f1d4"; } -.bi-broadcast-pin::before { content: "\f1d5"; } -.bi-broadcast::before { content: "\f1d6"; } -.bi-brush-fill::before { content: "\f1d7"; } -.bi-brush::before { content: "\f1d8"; } -.bi-bucket-fill::before { content: "\f1d9"; } -.bi-bucket::before { content: "\f1da"; } -.bi-bug-fill::before { content: "\f1db"; } -.bi-bug::before { content: "\f1dc"; } -.bi-building::before { content: "\f1dd"; } -.bi-bullseye::before { content: "\f1de"; } -.bi-calculator-fill::before { content: "\f1df"; } -.bi-calculator::before { content: "\f1e0"; } -.bi-calendar-check-fill::before { content: "\f1e1"; } -.bi-calendar-check::before { content: "\f1e2"; } -.bi-calendar-date-fill::before { content: "\f1e3"; } -.bi-calendar-date::before { content: "\f1e4"; } -.bi-calendar-day-fill::before { content: "\f1e5"; } -.bi-calendar-day::before { content: "\f1e6"; } -.bi-calendar-event-fill::before { content: "\f1e7"; } -.bi-calendar-event::before { content: "\f1e8"; } -.bi-calendar-fill::before { content: "\f1e9"; } -.bi-calendar-minus-fill::before { content: "\f1ea"; } -.bi-calendar-minus::before { content: "\f1eb"; } -.bi-calendar-month-fill::before { content: "\f1ec"; } -.bi-calendar-month::before { content: "\f1ed"; } -.bi-calendar-plus-fill::before { content: "\f1ee"; } -.bi-calendar-plus::before { content: "\f1ef"; } -.bi-calendar-range-fill::before { content: "\f1f0"; } -.bi-calendar-range::before { content: "\f1f1"; } -.bi-calendar-week-fill::before { content: "\f1f2"; } -.bi-calendar-week::before { content: "\f1f3"; } -.bi-calendar-x-fill::before { content: "\f1f4"; } -.bi-calendar-x::before { content: "\f1f5"; } -.bi-calendar::before { content: "\f1f6"; } -.bi-calendar2-check-fill::before { content: "\f1f7"; } -.bi-calendar2-check::before { content: "\f1f8"; } -.bi-calendar2-date-fill::before { content: "\f1f9"; } -.bi-calendar2-date::before { content: "\f1fa"; } -.bi-calendar2-day-fill::before { content: "\f1fb"; } -.bi-calendar2-day::before { content: "\f1fc"; } -.bi-calendar2-event-fill::before { content: "\f1fd"; } -.bi-calendar2-event::before { content: "\f1fe"; } -.bi-calendar2-fill::before { content: "\f1ff"; } -.bi-calendar2-minus-fill::before { content: "\f200"; } -.bi-calendar2-minus::before { content: "\f201"; } -.bi-calendar2-month-fill::before { content: "\f202"; } -.bi-calendar2-month::before { content: "\f203"; } -.bi-calendar2-plus-fill::before { content: "\f204"; } -.bi-calendar2-plus::before { content: "\f205"; } -.bi-calendar2-range-fill::before { content: "\f206"; } -.bi-calendar2-range::before { content: "\f207"; } -.bi-calendar2-week-fill::before { content: "\f208"; } -.bi-calendar2-week::before { content: "\f209"; } -.bi-calendar2-x-fill::before { content: "\f20a"; } -.bi-calendar2-x::before { content: "\f20b"; } -.bi-calendar2::before { content: "\f20c"; } -.bi-calendar3-event-fill::before { content: "\f20d"; } -.bi-calendar3-event::before { content: "\f20e"; } -.bi-calendar3-fill::before { content: "\f20f"; } -.bi-calendar3-range-fill::before { content: "\f210"; } -.bi-calendar3-range::before { content: "\f211"; } -.bi-calendar3-week-fill::before { content: "\f212"; } -.bi-calendar3-week::before { content: "\f213"; } -.bi-calendar3::before { content: "\f214"; } -.bi-calendar4-event::before { content: "\f215"; } -.bi-calendar4-range::before { content: "\f216"; } -.bi-calendar4-week::before { content: "\f217"; } -.bi-calendar4::before { content: "\f218"; } -.bi-camera-fill::before { content: "\f219"; } -.bi-camera-reels-fill::before { content: "\f21a"; } -.bi-camera-reels::before { content: "\f21b"; } -.bi-camera-video-fill::before { content: "\f21c"; } -.bi-camera-video-off-fill::before { content: "\f21d"; } -.bi-camera-video-off::before { content: "\f21e"; } -.bi-camera-video::before { content: "\f21f"; } -.bi-camera::before { content: "\f220"; } -.bi-camera2::before { content: "\f221"; } -.bi-capslock-fill::before { content: "\f222"; } -.bi-capslock::before { content: "\f223"; } -.bi-card-checklist::before { content: "\f224"; } -.bi-card-heading::before { content: "\f225"; } -.bi-card-image::before { content: "\f226"; } -.bi-card-list::before { content: "\f227"; } -.bi-card-text::before { content: "\f228"; } -.bi-caret-down-fill::before { content: "\f229"; } -.bi-caret-down-square-fill::before { content: "\f22a"; } -.bi-caret-down-square::before { content: "\f22b"; } -.bi-caret-down::before { content: "\f22c"; } -.bi-caret-left-fill::before { content: "\f22d"; } -.bi-caret-left-square-fill::before { content: "\f22e"; } -.bi-caret-left-square::before { content: "\f22f"; } -.bi-caret-left::before { content: "\f230"; } -.bi-caret-right-fill::before { content: "\f231"; } -.bi-caret-right-square-fill::before { content: "\f232"; } -.bi-caret-right-square::before { content: "\f233"; } -.bi-caret-right::before { content: "\f234"; } -.bi-caret-up-fill::before { content: "\f235"; } -.bi-caret-up-square-fill::before { content: "\f236"; } -.bi-caret-up-square::before { content: "\f237"; } -.bi-caret-up::before { content: "\f238"; } -.bi-cart-check-fill::before { content: "\f239"; } -.bi-cart-check::before { content: "\f23a"; } -.bi-cart-dash-fill::before { content: "\f23b"; } -.bi-cart-dash::before { content: "\f23c"; } -.bi-cart-fill::before { content: "\f23d"; } -.bi-cart-plus-fill::before { content: "\f23e"; } -.bi-cart-plus::before { content: "\f23f"; } -.bi-cart-x-fill::before { content: "\f240"; } -.bi-cart-x::before { content: "\f241"; } -.bi-cart::before { content: "\f242"; } -.bi-cart2::before { content: "\f243"; } -.bi-cart3::before { content: "\f244"; } -.bi-cart4::before { content: "\f245"; } -.bi-cash-stack::before { content: "\f246"; } -.bi-cash::before { content: "\f247"; } -.bi-cast::before { content: "\f248"; } -.bi-chat-dots-fill::before { content: "\f249"; } -.bi-chat-dots::before { content: "\f24a"; } -.bi-chat-fill::before { content: "\f24b"; } -.bi-chat-left-dots-fill::before { content: "\f24c"; } -.bi-chat-left-dots::before { content: "\f24d"; } -.bi-chat-left-fill::before { content: "\f24e"; } -.bi-chat-left-quote-fill::before { content: "\f24f"; } -.bi-chat-left-quote::before { content: "\f250"; } -.bi-chat-left-text-fill::before { content: "\f251"; } -.bi-chat-left-text::before { content: "\f252"; } -.bi-chat-left::before { content: "\f253"; } -.bi-chat-quote-fill::before { content: "\f254"; } -.bi-chat-quote::before { content: "\f255"; } -.bi-chat-right-dots-fill::before { content: "\f256"; } -.bi-chat-right-dots::before { content: "\f257"; } -.bi-chat-right-fill::before { content: "\f258"; } -.bi-chat-right-quote-fill::before { content: "\f259"; } -.bi-chat-right-quote::before { content: "\f25a"; } -.bi-chat-right-text-fill::before { content: "\f25b"; } -.bi-chat-right-text::before { content: "\f25c"; } -.bi-chat-right::before { content: "\f25d"; } -.bi-chat-square-dots-fill::before { content: "\f25e"; } -.bi-chat-square-dots::before { content: "\f25f"; } -.bi-chat-square-fill::before { content: "\f260"; } -.bi-chat-square-quote-fill::before { content: "\f261"; } -.bi-chat-square-quote::before { content: "\f262"; } -.bi-chat-square-text-fill::before { content: "\f263"; } -.bi-chat-square-text::before { content: "\f264"; } -.bi-chat-square::before { content: "\f265"; } -.bi-chat-text-fill::before { content: "\f266"; } -.bi-chat-text::before { content: "\f267"; } -.bi-chat::before { content: "\f268"; } -.bi-check-all::before { content: "\f269"; } -.bi-check-circle-fill::before { content: "\f26a"; } -.bi-check-circle::before { content: "\f26b"; } -.bi-check-square-fill::before { content: "\f26c"; } -.bi-check-square::before { content: "\f26d"; } -.bi-check::before { content: "\f26e"; } -.bi-check2-all::before { content: "\f26f"; } -.bi-check2-circle::before { content: "\f270"; } -.bi-check2-square::before { content: "\f271"; } -.bi-check2::before { content: "\f272"; } -.bi-chevron-bar-contract::before { content: "\f273"; } -.bi-chevron-bar-down::before { content: "\f274"; } -.bi-chevron-bar-expand::before { content: "\f275"; } -.bi-chevron-bar-left::before { content: "\f276"; } -.bi-chevron-bar-right::before { content: "\f277"; } -.bi-chevron-bar-up::before { content: "\f278"; } -.bi-chevron-compact-down::before { content: "\f279"; } -.bi-chevron-compact-left::before { content: "\f27a"; } -.bi-chevron-compact-right::before { content: "\f27b"; } -.bi-chevron-compact-up::before { content: "\f27c"; } -.bi-chevron-contract::before { content: "\f27d"; } -.bi-chevron-double-down::before { content: "\f27e"; } -.bi-chevron-double-left::before { content: "\f27f"; } -.bi-chevron-double-right::before { content: "\f280"; } -.bi-chevron-double-up::before { content: "\f281"; } -.bi-chevron-down::before { content: "\f282"; } -.bi-chevron-expand::before { content: "\f283"; } -.bi-chevron-left::before { content: "\f284"; } -.bi-chevron-right::before { content: "\f285"; } -.bi-chevron-up::before { content: "\f286"; } -.bi-circle-fill::before { content: "\f287"; } -.bi-circle-half::before { content: "\f288"; } -.bi-circle-square::before { content: "\f289"; } -.bi-circle::before { content: "\f28a"; } -.bi-clipboard-check::before { content: "\f28b"; } -.bi-clipboard-data::before { content: "\f28c"; } -.bi-clipboard-minus::before { content: "\f28d"; } -.bi-clipboard-plus::before { content: "\f28e"; } -.bi-clipboard-x::before { content: "\f28f"; } -.bi-clipboard::before { content: "\f290"; } -.bi-clock-fill::before { content: "\f291"; } -.bi-clock-history::before { content: "\f292"; } -.bi-clock::before { content: "\f293"; } -.bi-cloud-arrow-down-fill::before { content: "\f294"; } -.bi-cloud-arrow-down::before { content: "\f295"; } -.bi-cloud-arrow-up-fill::before { content: "\f296"; } -.bi-cloud-arrow-up::before { content: "\f297"; } -.bi-cloud-check-fill::before { content: "\f298"; } -.bi-cloud-check::before { content: "\f299"; } -.bi-cloud-download-fill::before { content: "\f29a"; } -.bi-cloud-download::before { content: "\f29b"; } -.bi-cloud-drizzle-fill::before { content: "\f29c"; } -.bi-cloud-drizzle::before { content: "\f29d"; } -.bi-cloud-fill::before { content: "\f29e"; } -.bi-cloud-fog-fill::before { content: "\f29f"; } -.bi-cloud-fog::before { content: "\f2a0"; } -.bi-cloud-fog2-fill::before { content: "\f2a1"; } -.bi-cloud-fog2::before { content: "\f2a2"; } -.bi-cloud-hail-fill::before { content: "\f2a3"; } -.bi-cloud-hail::before { content: "\f2a4"; } -.bi-cloud-haze-1::before { content: "\f2a5"; } -.bi-cloud-haze-fill::before { content: "\f2a6"; } -.bi-cloud-haze::before { content: "\f2a7"; } -.bi-cloud-haze2-fill::before { content: "\f2a8"; } -.bi-cloud-lightning-fill::before { content: "\f2a9"; } -.bi-cloud-lightning-rain-fill::before { content: "\f2aa"; } -.bi-cloud-lightning-rain::before { content: "\f2ab"; } -.bi-cloud-lightning::before { content: "\f2ac"; } -.bi-cloud-minus-fill::before { content: "\f2ad"; } -.bi-cloud-minus::before { content: "\f2ae"; } -.bi-cloud-moon-fill::before { content: "\f2af"; } -.bi-cloud-moon::before { content: "\f2b0"; } -.bi-cloud-plus-fill::before { content: "\f2b1"; } -.bi-cloud-plus::before { content: "\f2b2"; } -.bi-cloud-rain-fill::before { content: "\f2b3"; } -.bi-cloud-rain-heavy-fill::before { content: "\f2b4"; } -.bi-cloud-rain-heavy::before { content: "\f2b5"; } -.bi-cloud-rain::before { content: "\f2b6"; } -.bi-cloud-slash-fill::before { content: "\f2b7"; } -.bi-cloud-slash::before { content: "\f2b8"; } -.bi-cloud-sleet-fill::before { content: "\f2b9"; } -.bi-cloud-sleet::before { content: "\f2ba"; } -.bi-cloud-snow-fill::before { content: "\f2bb"; } -.bi-cloud-snow::before { content: "\f2bc"; } -.bi-cloud-sun-fill::before { content: "\f2bd"; } -.bi-cloud-sun::before { content: "\f2be"; } -.bi-cloud-upload-fill::before { content: "\f2bf"; } -.bi-cloud-upload::before { content: "\f2c0"; } -.bi-cloud::before { content: "\f2c1"; } -.bi-clouds-fill::before { content: "\f2c2"; } -.bi-clouds::before { content: "\f2c3"; } -.bi-cloudy-fill::before { content: "\f2c4"; } -.bi-cloudy::before { content: "\f2c5"; } -.bi-code-slash::before { content: "\f2c6"; } -.bi-code-square::before { content: "\f2c7"; } -.bi-code::before { content: "\f2c8"; } -.bi-collection-fill::before { content: "\f2c9"; } -.bi-collection-play-fill::before { content: "\f2ca"; } -.bi-collection-play::before { content: "\f2cb"; } -.bi-collection::before { content: "\f2cc"; } -.bi-columns-gap::before { content: "\f2cd"; } -.bi-columns::before { content: "\f2ce"; } -.bi-command::before { content: "\f2cf"; } -.bi-compass-fill::before { content: "\f2d0"; } -.bi-compass::before { content: "\f2d1"; } -.bi-cone-striped::before { content: "\f2d2"; } -.bi-cone::before { content: "\f2d3"; } -.bi-controller::before { content: "\f2d4"; } -.bi-cpu-fill::before { content: "\f2d5"; } -.bi-cpu::before { content: "\f2d6"; } -.bi-credit-card-2-back-fill::before { content: "\f2d7"; } -.bi-credit-card-2-back::before { content: "\f2d8"; } -.bi-credit-card-2-front-fill::before { content: "\f2d9"; } -.bi-credit-card-2-front::before { content: "\f2da"; } -.bi-credit-card-fill::before { content: "\f2db"; } -.bi-credit-card::before { content: "\f2dc"; } -.bi-crop::before { content: "\f2dd"; } -.bi-cup-fill::before { content: "\f2de"; } -.bi-cup-straw::before { content: "\f2df"; } -.bi-cup::before { content: "\f2e0"; } -.bi-cursor-fill::before { content: "\f2e1"; } -.bi-cursor-text::before { content: "\f2e2"; } -.bi-cursor::before { content: "\f2e3"; } -.bi-dash-circle-dotted::before { content: "\f2e4"; } -.bi-dash-circle-fill::before { content: "\f2e5"; } -.bi-dash-circle::before { content: "\f2e6"; } -.bi-dash-square-dotted::before { content: "\f2e7"; } -.bi-dash-square-fill::before { content: "\f2e8"; } -.bi-dash-square::before { content: "\f2e9"; } -.bi-dash::before { content: "\f2ea"; } -.bi-diagram-2-fill::before { content: "\f2eb"; } -.bi-diagram-2::before { content: "\f2ec"; } -.bi-diagram-3-fill::before { content: "\f2ed"; } -.bi-diagram-3::before { content: "\f2ee"; } -.bi-diamond-fill::before { content: "\f2ef"; } -.bi-diamond-half::before { content: "\f2f0"; } -.bi-diamond::before { content: "\f2f1"; } -.bi-dice-1-fill::before { content: "\f2f2"; } -.bi-dice-1::before { content: "\f2f3"; } -.bi-dice-2-fill::before { content: "\f2f4"; } -.bi-dice-2::before { content: "\f2f5"; } -.bi-dice-3-fill::before { content: "\f2f6"; } -.bi-dice-3::before { content: "\f2f7"; } -.bi-dice-4-fill::before { content: "\f2f8"; } -.bi-dice-4::before { content: "\f2f9"; } -.bi-dice-5-fill::before { content: "\f2fa"; } -.bi-dice-5::before { content: "\f2fb"; } -.bi-dice-6-fill::before { content: "\f2fc"; } -.bi-dice-6::before { content: "\f2fd"; } -.bi-disc-fill::before { content: "\f2fe"; } -.bi-disc::before { content: "\f2ff"; } -.bi-discord::before { content: "\f300"; } -.bi-display-fill::before { content: "\f301"; } -.bi-display::before { content: "\f302"; } -.bi-distribute-horizontal::before { content: "\f303"; } -.bi-distribute-vertical::before { content: "\f304"; } -.bi-door-closed-fill::before { content: "\f305"; } -.bi-door-closed::before { content: "\f306"; } -.bi-door-open-fill::before { content: "\f307"; } -.bi-door-open::before { content: "\f308"; } -.bi-dot::before { content: "\f309"; } -.bi-download::before { content: "\f30a"; } -.bi-droplet-fill::before { content: "\f30b"; } -.bi-droplet-half::before { content: "\f30c"; } -.bi-droplet::before { content: "\f30d"; } -.bi-earbuds::before { content: "\f30e"; } -.bi-easel-fill::before { content: "\f30f"; } -.bi-easel::before { content: "\f310"; } -.bi-egg-fill::before { content: "\f311"; } -.bi-egg-fried::before { content: "\f312"; } -.bi-egg::before { content: "\f313"; } -.bi-eject-fill::before { content: "\f314"; } -.bi-eject::before { content: "\f315"; } -.bi-emoji-angry-fill::before { content: "\f316"; } -.bi-emoji-angry::before { content: "\f317"; } -.bi-emoji-dizzy-fill::before { content: "\f318"; } -.bi-emoji-dizzy::before { content: "\f319"; } -.bi-emoji-expressionless-fill::before { content: "\f31a"; } -.bi-emoji-expressionless::before { content: "\f31b"; } -.bi-emoji-frown-fill::before { content: "\f31c"; } -.bi-emoji-frown::before { content: "\f31d"; } -.bi-emoji-heart-eyes-fill::before { content: "\f31e"; } -.bi-emoji-heart-eyes::before { content: "\f31f"; } -.bi-emoji-laughing-fill::before { content: "\f320"; } -.bi-emoji-laughing::before { content: "\f321"; } -.bi-emoji-neutral-fill::before { content: "\f322"; } -.bi-emoji-neutral::before { content: "\f323"; } -.bi-emoji-smile-fill::before { content: "\f324"; } -.bi-emoji-smile-upside-down-fill::before { content: "\f325"; } -.bi-emoji-smile-upside-down::before { content: "\f326"; } -.bi-emoji-smile::before { content: "\f327"; } -.bi-emoji-sunglasses-fill::before { content: "\f328"; } -.bi-emoji-sunglasses::before { content: "\f329"; } -.bi-emoji-wink-fill::before { content: "\f32a"; } -.bi-emoji-wink::before { content: "\f32b"; } -.bi-envelope-fill::before { content: "\f32c"; } -.bi-envelope-open-fill::before { content: "\f32d"; } -.bi-envelope-open::before { content: "\f32e"; } -.bi-envelope::before { content: "\f32f"; } -.bi-eraser-fill::before { content: "\f330"; } -.bi-eraser::before { content: "\f331"; } -.bi-exclamation-circle-fill::before { content: "\f332"; } -.bi-exclamation-circle::before { content: "\f333"; } -.bi-exclamation-diamond-fill::before { content: "\f334"; } -.bi-exclamation-diamond::before { content: "\f335"; } -.bi-exclamation-octagon-fill::before { content: "\f336"; } -.bi-exclamation-octagon::before { content: "\f337"; } -.bi-exclamation-square-fill::before { content: "\f338"; } -.bi-exclamation-square::before { content: "\f339"; } -.bi-exclamation-triangle-fill::before { content: "\f33a"; } -.bi-exclamation-triangle::before { content: "\f33b"; } -.bi-exclamation::before { content: "\f33c"; } -.bi-exclude::before { content: "\f33d"; } -.bi-eye-fill::before { content: "\f33e"; } -.bi-eye-slash-fill::before { content: "\f33f"; } -.bi-eye-slash::before { content: "\f340"; } -.bi-eye::before { content: "\f341"; } -.bi-eyedropper::before { content: "\f342"; } -.bi-eyeglasses::before { content: "\f343"; } -.bi-facebook::before { content: "\f344"; } -.bi-file-arrow-down-fill::before { content: "\f345"; } -.bi-file-arrow-down::before { content: "\f346"; } -.bi-file-arrow-up-fill::before { content: "\f347"; } -.bi-file-arrow-up::before { content: "\f348"; } -.bi-file-bar-graph-fill::before { content: "\f349"; } -.bi-file-bar-graph::before { content: "\f34a"; } -.bi-file-binary-fill::before { content: "\f34b"; } -.bi-file-binary::before { content: "\f34c"; } -.bi-file-break-fill::before { content: "\f34d"; } -.bi-file-break::before { content: "\f34e"; } -.bi-file-check-fill::before { content: "\f34f"; } -.bi-file-check::before { content: "\f350"; } -.bi-file-code-fill::before { content: "\f351"; } -.bi-file-code::before { content: "\f352"; } -.bi-file-diff-fill::before { content: "\f353"; } -.bi-file-diff::before { content: "\f354"; } -.bi-file-earmark-arrow-down-fill::before { content: "\f355"; } -.bi-file-earmark-arrow-down::before { content: "\f356"; } -.bi-file-earmark-arrow-up-fill::before { content: "\f357"; } -.bi-file-earmark-arrow-up::before { content: "\f358"; } -.bi-file-earmark-bar-graph-fill::before { content: "\f359"; } -.bi-file-earmark-bar-graph::before { content: "\f35a"; } -.bi-file-earmark-binary-fill::before { content: "\f35b"; } -.bi-file-earmark-binary::before { content: "\f35c"; } -.bi-file-earmark-break-fill::before { content: "\f35d"; } -.bi-file-earmark-break::before { content: "\f35e"; } -.bi-file-earmark-check-fill::before { content: "\f35f"; } -.bi-file-earmark-check::before { content: "\f360"; } -.bi-file-earmark-code-fill::before { content: "\f361"; } -.bi-file-earmark-code::before { content: "\f362"; } -.bi-file-earmark-diff-fill::before { content: "\f363"; } -.bi-file-earmark-diff::before { content: "\f364"; } -.bi-file-earmark-easel-fill::before { content: "\f365"; } -.bi-file-earmark-easel::before { content: "\f366"; } -.bi-file-earmark-excel-fill::before { content: "\f367"; } -.bi-file-earmark-excel::before { content: "\f368"; } -.bi-file-earmark-fill::before { content: "\f369"; } -.bi-file-earmark-font-fill::before { content: "\f36a"; } -.bi-file-earmark-font::before { content: "\f36b"; } -.bi-file-earmark-image-fill::before { content: "\f36c"; } -.bi-file-earmark-image::before { content: "\f36d"; } -.bi-file-earmark-lock-fill::before { content: "\f36e"; } -.bi-file-earmark-lock::before { content: "\f36f"; } -.bi-file-earmark-lock2-fill::before { content: "\f370"; } -.bi-file-earmark-lock2::before { content: "\f371"; } -.bi-file-earmark-medical-fill::before { content: "\f372"; } -.bi-file-earmark-medical::before { content: "\f373"; } -.bi-file-earmark-minus-fill::before { content: "\f374"; } -.bi-file-earmark-minus::before { content: "\f375"; } -.bi-file-earmark-music-fill::before { content: "\f376"; } -.bi-file-earmark-music::before { content: "\f377"; } -.bi-file-earmark-person-fill::before { content: "\f378"; } -.bi-file-earmark-person::before { content: "\f379"; } -.bi-file-earmark-play-fill::before { content: "\f37a"; } -.bi-file-earmark-play::before { content: "\f37b"; } -.bi-file-earmark-plus-fill::before { content: "\f37c"; } -.bi-file-earmark-plus::before { content: "\f37d"; } -.bi-file-earmark-post-fill::before { content: "\f37e"; } -.bi-file-earmark-post::before { content: "\f37f"; } -.bi-file-earmark-ppt-fill::before { content: "\f380"; } -.bi-file-earmark-ppt::before { content: "\f381"; } -.bi-file-earmark-richtext-fill::before { content: "\f382"; } -.bi-file-earmark-richtext::before { content: "\f383"; } -.bi-file-earmark-ruled-fill::before { content: "\f384"; } -.bi-file-earmark-ruled::before { content: "\f385"; } -.bi-file-earmark-slides-fill::before { content: "\f386"; } -.bi-file-earmark-slides::before { content: "\f387"; } -.bi-file-earmark-spreadsheet-fill::before { content: "\f388"; } -.bi-file-earmark-spreadsheet::before { content: "\f389"; } -.bi-file-earmark-text-fill::before { content: "\f38a"; } -.bi-file-earmark-text::before { content: "\f38b"; } -.bi-file-earmark-word-fill::before { content: "\f38c"; } -.bi-file-earmark-word::before { content: "\f38d"; } -.bi-file-earmark-x-fill::before { content: "\f38e"; } -.bi-file-earmark-x::before { content: "\f38f"; } -.bi-file-earmark-zip-fill::before { content: "\f390"; } -.bi-file-earmark-zip::before { content: "\f391"; } -.bi-file-earmark::before { content: "\f392"; } -.bi-file-easel-fill::before { content: "\f393"; } -.bi-file-easel::before { content: "\f394"; } -.bi-file-excel-fill::before { content: "\f395"; } -.bi-file-excel::before { content: "\f396"; } -.bi-file-fill::before { content: "\f397"; } -.bi-file-font-fill::before { content: "\f398"; } -.bi-file-font::before { content: "\f399"; } -.bi-file-image-fill::before { content: "\f39a"; } -.bi-file-image::before { content: "\f39b"; } -.bi-file-lock-fill::before { content: "\f39c"; } -.bi-file-lock::before { content: "\f39d"; } -.bi-file-lock2-fill::before { content: "\f39e"; } -.bi-file-lock2::before { content: "\f39f"; } -.bi-file-medical-fill::before { content: "\f3a0"; } -.bi-file-medical::before { content: "\f3a1"; } -.bi-file-minus-fill::before { content: "\f3a2"; } -.bi-file-minus::before { content: "\f3a3"; } -.bi-file-music-fill::before { content: "\f3a4"; } -.bi-file-music::before { content: "\f3a5"; } -.bi-file-person-fill::before { content: "\f3a6"; } -.bi-file-person::before { content: "\f3a7"; } -.bi-file-play-fill::before { content: "\f3a8"; } -.bi-file-play::before { content: "\f3a9"; } -.bi-file-plus-fill::before { content: "\f3aa"; } -.bi-file-plus::before { content: "\f3ab"; } -.bi-file-post-fill::before { content: "\f3ac"; } -.bi-file-post::before { content: "\f3ad"; } -.bi-file-ppt-fill::before { content: "\f3ae"; } -.bi-file-ppt::before { content: "\f3af"; } -.bi-file-richtext-fill::before { content: "\f3b0"; } -.bi-file-richtext::before { content: "\f3b1"; } -.bi-file-ruled-fill::before { content: "\f3b2"; } -.bi-file-ruled::before { content: "\f3b3"; } -.bi-file-slides-fill::before { content: "\f3b4"; } -.bi-file-slides::before { content: "\f3b5"; } -.bi-file-spreadsheet-fill::before { content: "\f3b6"; } -.bi-file-spreadsheet::before { content: "\f3b7"; } -.bi-file-text-fill::before { content: "\f3b8"; } -.bi-file-text::before { content: "\f3b9"; } -.bi-file-word-fill::before { content: "\f3ba"; } -.bi-file-word::before { content: "\f3bb"; } -.bi-file-x-fill::before { content: "\f3bc"; } -.bi-file-x::before { content: "\f3bd"; } -.bi-file-zip-fill::before { content: "\f3be"; } -.bi-file-zip::before { content: "\f3bf"; } -.bi-file::before { content: "\f3c0"; } -.bi-files-alt::before { content: "\f3c1"; } -.bi-files::before { content: "\f3c2"; } -.bi-film::before { content: "\f3c3"; } -.bi-filter-circle-fill::before { content: "\f3c4"; } -.bi-filter-circle::before { content: "\f3c5"; } -.bi-filter-left::before { content: "\f3c6"; } -.bi-filter-right::before { content: "\f3c7"; } -.bi-filter-square-fill::before { content: "\f3c8"; } -.bi-filter-square::before { content: "\f3c9"; } -.bi-filter::before { content: "\f3ca"; } -.bi-flag-fill::before { content: "\f3cb"; } -.bi-flag::before { content: "\f3cc"; } -.bi-flower1::before { content: "\f3cd"; } -.bi-flower2::before { content: "\f3ce"; } -.bi-flower3::before { content: "\f3cf"; } -.bi-folder-check::before { content: "\f3d0"; } -.bi-folder-fill::before { content: "\f3d1"; } -.bi-folder-minus::before { content: "\f3d2"; } -.bi-folder-plus::before { content: "\f3d3"; } -.bi-folder-symlink-fill::before { content: "\f3d4"; } -.bi-folder-symlink::before { content: "\f3d5"; } -.bi-folder-x::before { content: "\f3d6"; } -.bi-folder::before { content: "\f3d7"; } -.bi-folder2-open::before { content: "\f3d8"; } -.bi-folder2::before { content: "\f3d9"; } -.bi-fonts::before { content: "\f3da"; } -.bi-forward-fill::before { content: "\f3db"; } -.bi-forward::before { content: "\f3dc"; } -.bi-front::before { content: "\f3dd"; } -.bi-fullscreen-exit::before { content: "\f3de"; } -.bi-fullscreen::before { content: "\f3df"; } -.bi-funnel-fill::before { content: "\f3e0"; } -.bi-funnel::before { content: "\f3e1"; } -.bi-gear-fill::before { content: "\f3e2"; } -.bi-gear-wide-connected::before { content: "\f3e3"; } -.bi-gear-wide::before { content: "\f3e4"; } -.bi-gear::before { content: "\f3e5"; } -.bi-gem::before { content: "\f3e6"; } -.bi-geo-alt-fill::before { content: "\f3e7"; } -.bi-geo-alt::before { content: "\f3e8"; } -.bi-geo-fill::before { content: "\f3e9"; } -.bi-geo::before { content: "\f3ea"; } -.bi-gift-fill::before { content: "\f3eb"; } -.bi-gift::before { content: "\f3ec"; } -.bi-github::before { content: "\f3ed"; } -.bi-globe::before { content: "\f3ee"; } -.bi-globe2::before { content: "\f3ef"; } -.bi-google::before { content: "\f3f0"; } -.bi-graph-down::before { content: "\f3f1"; } -.bi-graph-up::before { content: "\f3f2"; } -.bi-grid-1x2-fill::before { content: "\f3f3"; } -.bi-grid-1x2::before { content: "\f3f4"; } -.bi-grid-3x2-gap-fill::before { content: "\f3f5"; } -.bi-grid-3x2-gap::before { content: "\f3f6"; } -.bi-grid-3x2::before { content: "\f3f7"; } -.bi-grid-3x3-gap-fill::before { content: "\f3f8"; } -.bi-grid-3x3-gap::before { content: "\f3f9"; } -.bi-grid-3x3::before { content: "\f3fa"; } -.bi-grid-fill::before { content: "\f3fb"; } -.bi-grid::before { content: "\f3fc"; } -.bi-grip-horizontal::before { content: "\f3fd"; } -.bi-grip-vertical::before { content: "\f3fe"; } -.bi-hammer::before { content: "\f3ff"; } -.bi-hand-index-fill::before { content: "\f400"; } -.bi-hand-index-thumb-fill::before { content: "\f401"; } -.bi-hand-index-thumb::before { content: "\f402"; } -.bi-hand-index::before { content: "\f403"; } -.bi-hand-thumbs-down-fill::before { content: "\f404"; } -.bi-hand-thumbs-down::before { content: "\f405"; } -.bi-hand-thumbs-up-fill::before { content: "\f406"; } -.bi-hand-thumbs-up::before { content: "\f407"; } -.bi-handbag-fill::before { content: "\f408"; } -.bi-handbag::before { content: "\f409"; } -.bi-hash::before { content: "\f40a"; } -.bi-hdd-fill::before { content: "\f40b"; } -.bi-hdd-network-fill::before { content: "\f40c"; } -.bi-hdd-network::before { content: "\f40d"; } -.bi-hdd-rack-fill::before { content: "\f40e"; } -.bi-hdd-rack::before { content: "\f40f"; } -.bi-hdd-stack-fill::before { content: "\f410"; } -.bi-hdd-stack::before { content: "\f411"; } -.bi-hdd::before { content: "\f412"; } -.bi-headphones::before { content: "\f413"; } -.bi-headset::before { content: "\f414"; } -.bi-heart-fill::before { content: "\f415"; } -.bi-heart-half::before { content: "\f416"; } -.bi-heart::before { content: "\f417"; } -.bi-heptagon-fill::before { content: "\f418"; } -.bi-heptagon-half::before { content: "\f419"; } -.bi-heptagon::before { content: "\f41a"; } -.bi-hexagon-fill::before { content: "\f41b"; } -.bi-hexagon-half::before { content: "\f41c"; } -.bi-hexagon::before { content: "\f41d"; } -.bi-hourglass-bottom::before { content: "\f41e"; } -.bi-hourglass-split::before { content: "\f41f"; } -.bi-hourglass-top::before { content: "\f420"; } -.bi-hourglass::before { content: "\f421"; } -.bi-house-door-fill::before { content: "\f422"; } -.bi-house-door::before { content: "\f423"; } -.bi-house-fill::before { content: "\f424"; } -.bi-house::before { content: "\f425"; } -.bi-hr::before { content: "\f426"; } -.bi-hurricane::before { content: "\f427"; } -.bi-image-alt::before { content: "\f428"; } -.bi-image-fill::before { content: "\f429"; } -.bi-image::before { content: "\f42a"; } -.bi-images::before { content: "\f42b"; } -.bi-inbox-fill::before { content: "\f42c"; } -.bi-inbox::before { content: "\f42d"; } -.bi-inboxes-fill::before { content: "\f42e"; } -.bi-inboxes::before { content: "\f42f"; } -.bi-info-circle-fill::before { content: "\f430"; } -.bi-info-circle::before { content: "\f431"; } -.bi-info-square-fill::before { content: "\f432"; } -.bi-info-square::before { content: "\f433"; } -.bi-info::before { content: "\f434"; } -.bi-input-cursor-text::before { content: "\f435"; } -.bi-input-cursor::before { content: "\f436"; } -.bi-instagram::before { content: "\f437"; } -.bi-intersect::before { content: "\f438"; } -.bi-journal-album::before { content: "\f439"; } -.bi-journal-arrow-down::before { content: "\f43a"; } -.bi-journal-arrow-up::before { content: "\f43b"; } -.bi-journal-bookmark-fill::before { content: "\f43c"; } -.bi-journal-bookmark::before { content: "\f43d"; } -.bi-journal-check::before { content: "\f43e"; } -.bi-journal-code::before { content: "\f43f"; } -.bi-journal-medical::before { content: "\f440"; } -.bi-journal-minus::before { content: "\f441"; } -.bi-journal-plus::before { content: "\f442"; } -.bi-journal-richtext::before { content: "\f443"; } -.bi-journal-text::before { content: "\f444"; } -.bi-journal-x::before { content: "\f445"; } -.bi-journal::before { content: "\f446"; } -.bi-journals::before { content: "\f447"; } -.bi-joystick::before { content: "\f448"; } -.bi-justify-left::before { content: "\f449"; } -.bi-justify-right::before { content: "\f44a"; } -.bi-justify::before { content: "\f44b"; } -.bi-kanban-fill::before { content: "\f44c"; } -.bi-kanban::before { content: "\f44d"; } -.bi-key-fill::before { content: "\f44e"; } -.bi-key::before { content: "\f44f"; } -.bi-keyboard-fill::before { content: "\f450"; } -.bi-keyboard::before { content: "\f451"; } -.bi-ladder::before { content: "\f452"; } -.bi-lamp-fill::before { content: "\f453"; } -.bi-lamp::before { content: "\f454"; } -.bi-laptop-fill::before { content: "\f455"; } -.bi-laptop::before { content: "\f456"; } -.bi-layer-backward::before { content: "\f457"; } -.bi-layer-forward::before { content: "\f458"; } -.bi-layers-fill::before { content: "\f459"; } -.bi-layers-half::before { content: "\f45a"; } -.bi-layers::before { content: "\f45b"; } -.bi-layout-sidebar-inset-reverse::before { content: "\f45c"; } -.bi-layout-sidebar-inset::before { content: "\f45d"; } -.bi-layout-sidebar-reverse::before { content: "\f45e"; } -.bi-layout-sidebar::before { content: "\f45f"; } -.bi-layout-split::before { content: "\f460"; } -.bi-layout-text-sidebar-reverse::before { content: "\f461"; } -.bi-layout-text-sidebar::before { content: "\f462"; } -.bi-layout-text-window-reverse::before { content: "\f463"; } -.bi-layout-text-window::before { content: "\f464"; } -.bi-layout-three-columns::before { content: "\f465"; } -.bi-layout-wtf::before { content: "\f466"; } -.bi-life-preserver::before { content: "\f467"; } -.bi-lightbulb-fill::before { content: "\f468"; } -.bi-lightbulb-off-fill::before { content: "\f469"; } -.bi-lightbulb-off::before { content: "\f46a"; } -.bi-lightbulb::before { content: "\f46b"; } -.bi-lightning-charge-fill::before { content: "\f46c"; } -.bi-lightning-charge::before { content: "\f46d"; } -.bi-lightning-fill::before { content: "\f46e"; } -.bi-lightning::before { content: "\f46f"; } -.bi-link-45deg::before { content: "\f470"; } -.bi-link::before { content: "\f471"; } -.bi-linkedin::before { content: "\f472"; } -.bi-list-check::before { content: "\f473"; } -.bi-list-nested::before { content: "\f474"; } -.bi-list-ol::before { content: "\f475"; } -.bi-list-stars::before { content: "\f476"; } -.bi-list-task::before { content: "\f477"; } -.bi-list-ul::before { content: "\f478"; } -.bi-list::before { content: "\f479"; } -.bi-lock-fill::before { content: "\f47a"; } -.bi-lock::before { content: "\f47b"; } -.bi-mailbox::before { content: "\f47c"; } -.bi-mailbox2::before { content: "\f47d"; } -.bi-map-fill::before { content: "\f47e"; } -.bi-map::before { content: "\f47f"; } -.bi-markdown-fill::before { content: "\f480"; } -.bi-markdown::before { content: "\f481"; } -.bi-mask::before { content: "\f482"; } -.bi-megaphone-fill::before { content: "\f483"; } -.bi-megaphone::before { content: "\f484"; } -.bi-menu-app-fill::before { content: "\f485"; } -.bi-menu-app::before { content: "\f486"; } -.bi-menu-button-fill::before { content: "\f487"; } -.bi-menu-button-wide-fill::before { content: "\f488"; } -.bi-menu-button-wide::before { content: "\f489"; } -.bi-menu-button::before { content: "\f48a"; } -.bi-menu-down::before { content: "\f48b"; } -.bi-menu-up::before { content: "\f48c"; } -.bi-mic-fill::before { content: "\f48d"; } -.bi-mic-mute-fill::before { content: "\f48e"; } -.bi-mic-mute::before { content: "\f48f"; } -.bi-mic::before { content: "\f490"; } -.bi-minecart-loaded::before { content: "\f491"; } -.bi-minecart::before { content: "\f492"; } -.bi-moisture::before { content: "\f493"; } -.bi-moon-fill::before { content: "\f494"; } -.bi-moon-stars-fill::before { content: "\f495"; } -.bi-moon-stars::before { content: "\f496"; } -.bi-moon::before { content: "\f497"; } -.bi-mouse-fill::before { content: "\f498"; } -.bi-mouse::before { content: "\f499"; } -.bi-mouse2-fill::before { content: "\f49a"; } -.bi-mouse2::before { content: "\f49b"; } -.bi-mouse3-fill::before { content: "\f49c"; } -.bi-mouse3::before { content: "\f49d"; } -.bi-music-note-beamed::before { content: "\f49e"; } -.bi-music-note-list::before { content: "\f49f"; } -.bi-music-note::before { content: "\f4a0"; } -.bi-music-player-fill::before { content: "\f4a1"; } -.bi-music-player::before { content: "\f4a2"; } -.bi-newspaper::before { content: "\f4a3"; } -.bi-node-minus-fill::before { content: "\f4a4"; } -.bi-node-minus::before { content: "\f4a5"; } -.bi-node-plus-fill::before { content: "\f4a6"; } -.bi-node-plus::before { content: "\f4a7"; } -.bi-nut-fill::before { content: "\f4a8"; } -.bi-nut::before { content: "\f4a9"; } -.bi-octagon-fill::before { content: "\f4aa"; } -.bi-octagon-half::before { content: "\f4ab"; } -.bi-octagon::before { content: "\f4ac"; } -.bi-option::before { content: "\f4ad"; } -.bi-outlet::before { content: "\f4ae"; } -.bi-paint-bucket::before { content: "\f4af"; } -.bi-palette-fill::before { content: "\f4b0"; } -.bi-palette::before { content: "\f4b1"; } -.bi-palette2::before { content: "\f4b2"; } -.bi-paperclip::before { content: "\f4b3"; } -.bi-paragraph::before { content: "\f4b4"; } -.bi-patch-check-fill::before { content: "\f4b5"; } -.bi-patch-check::before { content: "\f4b6"; } -.bi-patch-exclamation-fill::before { content: "\f4b7"; } -.bi-patch-exclamation::before { content: "\f4b8"; } -.bi-patch-minus-fill::before { content: "\f4b9"; } -.bi-patch-minus::before { content: "\f4ba"; } -.bi-patch-plus-fill::before { content: "\f4bb"; } -.bi-patch-plus::before { content: "\f4bc"; } -.bi-patch-question-fill::before { content: "\f4bd"; } -.bi-patch-question::before { content: "\f4be"; } -.bi-pause-btn-fill::before { content: "\f4bf"; } -.bi-pause-btn::before { content: "\f4c0"; } -.bi-pause-circle-fill::before { content: "\f4c1"; } -.bi-pause-circle::before { content: "\f4c2"; } -.bi-pause-fill::before { content: "\f4c3"; } -.bi-pause::before { content: "\f4c4"; } -.bi-peace-fill::before { content: "\f4c5"; } -.bi-peace::before { content: "\f4c6"; } -.bi-pen-fill::before { content: "\f4c7"; } -.bi-pen::before { content: "\f4c8"; } -.bi-pencil-fill::before { content: "\f4c9"; } -.bi-pencil-square::before { content: "\f4ca"; } -.bi-pencil::before { content: "\f4cb"; } -.bi-pentagon-fill::before { content: "\f4cc"; } -.bi-pentagon-half::before { content: "\f4cd"; } -.bi-pentagon::before { content: "\f4ce"; } -.bi-people-fill::before { content: "\f4cf"; } -.bi-people::before { content: "\f4d0"; } -.bi-percent::before { content: "\f4d1"; } -.bi-person-badge-fill::before { content: "\f4d2"; } -.bi-person-badge::before { content: "\f4d3"; } -.bi-person-bounding-box::before { content: "\f4d4"; } -.bi-person-check-fill::before { content: "\f4d5"; } -.bi-person-check::before { content: "\f4d6"; } -.bi-person-circle::before { content: "\f4d7"; } -.bi-person-dash-fill::before { content: "\f4d8"; } -.bi-person-dash::before { content: "\f4d9"; } -.bi-person-fill::before { content: "\f4da"; } -.bi-person-lines-fill::before { content: "\f4db"; } -.bi-person-plus-fill::before { content: "\f4dc"; } -.bi-person-plus::before { content: "\f4dd"; } -.bi-person-square::before { content: "\f4de"; } -.bi-person-x-fill::before { content: "\f4df"; } -.bi-person-x::before { content: "\f4e0"; } -.bi-person::before { content: "\f4e1"; } -.bi-phone-fill::before { content: "\f4e2"; } -.bi-phone-landscape-fill::before { content: "\f4e3"; } -.bi-phone-landscape::before { content: "\f4e4"; } -.bi-phone-vibrate-fill::before { content: "\f4e5"; } -.bi-phone-vibrate::before { content: "\f4e6"; } -.bi-phone::before { content: "\f4e7"; } -.bi-pie-chart-fill::before { content: "\f4e8"; } -.bi-pie-chart::before { content: "\f4e9"; } -.bi-pin-angle-fill::before { content: "\f4ea"; } -.bi-pin-angle::before { content: "\f4eb"; } -.bi-pin-fill::before { content: "\f4ec"; } -.bi-pin::before { content: "\f4ed"; } -.bi-pip-fill::before { content: "\f4ee"; } -.bi-pip::before { content: "\f4ef"; } -.bi-play-btn-fill::before { content: "\f4f0"; } -.bi-play-btn::before { content: "\f4f1"; } -.bi-play-circle-fill::before { content: "\f4f2"; } -.bi-play-circle::before { content: "\f4f3"; } -.bi-play-fill::before { content: "\f4f4"; } -.bi-play::before { content: "\f4f5"; } -.bi-plug-fill::before { content: "\f4f6"; } -.bi-plug::before { content: "\f4f7"; } -.bi-plus-circle-dotted::before { content: "\f4f8"; } -.bi-plus-circle-fill::before { content: "\f4f9"; } -.bi-plus-circle::before { content: "\f4fa"; } -.bi-plus-square-dotted::before { content: "\f4fb"; } -.bi-plus-square-fill::before { content: "\f4fc"; } -.bi-plus-square::before { content: "\f4fd"; } -.bi-plus::before { content: "\f4fe"; } -.bi-power::before { content: "\f4ff"; } -.bi-printer-fill::before { content: "\f500"; } -.bi-printer::before { content: "\f501"; } -.bi-puzzle-fill::before { content: "\f502"; } -.bi-puzzle::before { content: "\f503"; } -.bi-question-circle-fill::before { content: "\f504"; } -.bi-question-circle::before { content: "\f505"; } -.bi-question-diamond-fill::before { content: "\f506"; } -.bi-question-diamond::before { content: "\f507"; } -.bi-question-octagon-fill::before { content: "\f508"; } -.bi-question-octagon::before { content: "\f509"; } -.bi-question-square-fill::before { content: "\f50a"; } -.bi-question-square::before { content: "\f50b"; } -.bi-question::before { content: "\f50c"; } -.bi-rainbow::before { content: "\f50d"; } -.bi-receipt-cutoff::before { content: "\f50e"; } -.bi-receipt::before { content: "\f50f"; } -.bi-reception-0::before { content: "\f510"; } -.bi-reception-1::before { content: "\f511"; } -.bi-reception-2::before { content: "\f512"; } -.bi-reception-3::before { content: "\f513"; } -.bi-reception-4::before { content: "\f514"; } -.bi-record-btn-fill::before { content: "\f515"; } -.bi-record-btn::before { content: "\f516"; } -.bi-record-circle-fill::before { content: "\f517"; } -.bi-record-circle::before { content: "\f518"; } -.bi-record-fill::before { content: "\f519"; } -.bi-record::before { content: "\f51a"; } -.bi-record2-fill::before { content: "\f51b"; } -.bi-record2::before { content: "\f51c"; } -.bi-reply-all-fill::before { content: "\f51d"; } -.bi-reply-all::before { content: "\f51e"; } -.bi-reply-fill::before { content: "\f51f"; } -.bi-reply::before { content: "\f520"; } -.bi-rss-fill::before { content: "\f521"; } -.bi-rss::before { content: "\f522"; } -.bi-rulers::before { content: "\f523"; } -.bi-save-fill::before { content: "\f524"; } -.bi-save::before { content: "\f525"; } -.bi-save2-fill::before { content: "\f526"; } -.bi-save2::before { content: "\f527"; } -.bi-scissors::before { content: "\f528"; } -.bi-screwdriver::before { content: "\f529"; } -.bi-search::before { content: "\f52a"; } -.bi-segmented-nav::before { content: "\f52b"; } -.bi-server::before { content: "\f52c"; } -.bi-share-fill::before { content: "\f52d"; } -.bi-share::before { content: "\f52e"; } -.bi-shield-check::before { content: "\f52f"; } -.bi-shield-exclamation::before { content: "\f530"; } -.bi-shield-fill-check::before { content: "\f531"; } -.bi-shield-fill-exclamation::before { content: "\f532"; } -.bi-shield-fill-minus::before { content: "\f533"; } -.bi-shield-fill-plus::before { content: "\f534"; } -.bi-shield-fill-x::before { content: "\f535"; } -.bi-shield-fill::before { content: "\f536"; } -.bi-shield-lock-fill::before { content: "\f537"; } -.bi-shield-lock::before { content: "\f538"; } -.bi-shield-minus::before { content: "\f539"; } -.bi-shield-plus::before { content: "\f53a"; } -.bi-shield-shaded::before { content: "\f53b"; } -.bi-shield-slash-fill::before { content: "\f53c"; } -.bi-shield-slash::before { content: "\f53d"; } -.bi-shield-x::before { content: "\f53e"; } -.bi-shield::before { content: "\f53f"; } -.bi-shift-fill::before { content: "\f540"; } -.bi-shift::before { content: "\f541"; } -.bi-shop-window::before { content: "\f542"; } -.bi-shop::before { content: "\f543"; } -.bi-shuffle::before { content: "\f544"; } -.bi-signpost-2-fill::before { content: "\f545"; } -.bi-signpost-2::before { content: "\f546"; } -.bi-signpost-fill::before { content: "\f547"; } -.bi-signpost-split-fill::before { content: "\f548"; } -.bi-signpost-split::before { content: "\f549"; } -.bi-signpost::before { content: "\f54a"; } -.bi-sim-fill::before { content: "\f54b"; } -.bi-sim::before { content: "\f54c"; } -.bi-skip-backward-btn-fill::before { content: "\f54d"; } -.bi-skip-backward-btn::before { content: "\f54e"; } -.bi-skip-backward-circle-fill::before { content: "\f54f"; } -.bi-skip-backward-circle::before { content: "\f550"; } -.bi-skip-backward-fill::before { content: "\f551"; } -.bi-skip-backward::before { content: "\f552"; } -.bi-skip-end-btn-fill::before { content: "\f553"; } -.bi-skip-end-btn::before { content: "\f554"; } -.bi-skip-end-circle-fill::before { content: "\f555"; } -.bi-skip-end-circle::before { content: "\f556"; } -.bi-skip-end-fill::before { content: "\f557"; } -.bi-skip-end::before { content: "\f558"; } -.bi-skip-forward-btn-fill::before { content: "\f559"; } -.bi-skip-forward-btn::before { content: "\f55a"; } -.bi-skip-forward-circle-fill::before { content: "\f55b"; } -.bi-skip-forward-circle::before { content: "\f55c"; } -.bi-skip-forward-fill::before { content: "\f55d"; } -.bi-skip-forward::before { content: "\f55e"; } -.bi-skip-start-btn-fill::before { content: "\f55f"; } -.bi-skip-start-btn::before { content: "\f560"; } -.bi-skip-start-circle-fill::before { content: "\f561"; } -.bi-skip-start-circle::before { content: "\f562"; } -.bi-skip-start-fill::before { content: "\f563"; } -.bi-skip-start::before { content: "\f564"; } -.bi-slack::before { content: "\f565"; } -.bi-slash-circle-fill::before { content: "\f566"; } -.bi-slash-circle::before { content: "\f567"; } -.bi-slash-square-fill::before { content: "\f568"; } -.bi-slash-square::before { content: "\f569"; } -.bi-slash::before { content: "\f56a"; } -.bi-sliders::before { content: "\f56b"; } -.bi-smartwatch::before { content: "\f56c"; } -.bi-snow::before { content: "\f56d"; } -.bi-snow2::before { content: "\f56e"; } -.bi-snow3::before { content: "\f56f"; } -.bi-sort-alpha-down-alt::before { content: "\f570"; } -.bi-sort-alpha-down::before { content: "\f571"; } -.bi-sort-alpha-up-alt::before { content: "\f572"; } -.bi-sort-alpha-up::before { content: "\f573"; } -.bi-sort-down-alt::before { content: "\f574"; } -.bi-sort-down::before { content: "\f575"; } -.bi-sort-numeric-down-alt::before { content: "\f576"; } -.bi-sort-numeric-down::before { content: "\f577"; } -.bi-sort-numeric-up-alt::before { content: "\f578"; } -.bi-sort-numeric-up::before { content: "\f579"; } -.bi-sort-up-alt::before { content: "\f57a"; } -.bi-sort-up::before { content: "\f57b"; } -.bi-soundwave::before { content: "\f57c"; } -.bi-speaker-fill::before { content: "\f57d"; } -.bi-speaker::before { content: "\f57e"; } -.bi-speedometer::before { content: "\f57f"; } -.bi-speedometer2::before { content: "\f580"; } -.bi-spellcheck::before { content: "\f581"; } -.bi-square-fill::before { content: "\f582"; } -.bi-square-half::before { content: "\f583"; } -.bi-square::before { content: "\f584"; } -.bi-stack::before { content: "\f585"; } -.bi-star-fill::before { content: "\f586"; } -.bi-star-half::before { content: "\f587"; } -.bi-star::before { content: "\f588"; } -.bi-stars::before { content: "\f589"; } -.bi-stickies-fill::before { content: "\f58a"; } -.bi-stickies::before { content: "\f58b"; } -.bi-sticky-fill::before { content: "\f58c"; } -.bi-sticky::before { content: "\f58d"; } -.bi-stop-btn-fill::before { content: "\f58e"; } -.bi-stop-btn::before { content: "\f58f"; } -.bi-stop-circle-fill::before { content: "\f590"; } -.bi-stop-circle::before { content: "\f591"; } -.bi-stop-fill::before { content: "\f592"; } -.bi-stop::before { content: "\f593"; } -.bi-stoplights-fill::before { content: "\f594"; } -.bi-stoplights::before { content: "\f595"; } -.bi-stopwatch-fill::before { content: "\f596"; } -.bi-stopwatch::before { content: "\f597"; } -.bi-subtract::before { content: "\f598"; } -.bi-suit-club-fill::before { content: "\f599"; } -.bi-suit-club::before { content: "\f59a"; } -.bi-suit-diamond-fill::before { content: "\f59b"; } -.bi-suit-diamond::before { content: "\f59c"; } -.bi-suit-heart-fill::before { content: "\f59d"; } -.bi-suit-heart::before { content: "\f59e"; } -.bi-suit-spade-fill::before { content: "\f59f"; } -.bi-suit-spade::before { content: "\f5a0"; } -.bi-sun-fill::before { content: "\f5a1"; } -.bi-sun::before { content: "\f5a2"; } -.bi-sunglasses::before { content: "\f5a3"; } -.bi-sunrise-fill::before { content: "\f5a4"; } -.bi-sunrise::before { content: "\f5a5"; } -.bi-sunset-fill::before { content: "\f5a6"; } -.bi-sunset::before { content: "\f5a7"; } -.bi-symmetry-horizontal::before { content: "\f5a8"; } -.bi-symmetry-vertical::before { content: "\f5a9"; } -.bi-table::before { content: "\f5aa"; } -.bi-tablet-fill::before { content: "\f5ab"; } -.bi-tablet-landscape-fill::before { content: "\f5ac"; } -.bi-tablet-landscape::before { content: "\f5ad"; } -.bi-tablet::before { content: "\f5ae"; } -.bi-tag-fill::before { content: "\f5af"; } -.bi-tag::before { content: "\f5b0"; } -.bi-tags-fill::before { content: "\f5b1"; } -.bi-tags::before { content: "\f5b2"; } -.bi-telegram::before { content: "\f5b3"; } -.bi-telephone-fill::before { content: "\f5b4"; } -.bi-telephone-forward-fill::before { content: "\f5b5"; } -.bi-telephone-forward::before { content: "\f5b6"; } -.bi-telephone-inbound-fill::before { content: "\f5b7"; } -.bi-telephone-inbound::before { content: "\f5b8"; } -.bi-telephone-minus-fill::before { content: "\f5b9"; } -.bi-telephone-minus::before { content: "\f5ba"; } -.bi-telephone-outbound-fill::before { content: "\f5bb"; } -.bi-telephone-outbound::before { content: "\f5bc"; } -.bi-telephone-plus-fill::before { content: "\f5bd"; } -.bi-telephone-plus::before { content: "\f5be"; } -.bi-telephone-x-fill::before { content: "\f5bf"; } -.bi-telephone-x::before { content: "\f5c0"; } -.bi-telephone::before { content: "\f5c1"; } -.bi-terminal-fill::before { content: "\f5c2"; } -.bi-terminal::before { content: "\f5c3"; } -.bi-text-center::before { content: "\f5c4"; } -.bi-text-indent-left::before { content: "\f5c5"; } -.bi-text-indent-right::before { content: "\f5c6"; } -.bi-text-left::before { content: "\f5c7"; } -.bi-text-paragraph::before { content: "\f5c8"; } -.bi-text-right::before { content: "\f5c9"; } -.bi-textarea-resize::before { content: "\f5ca"; } -.bi-textarea-t::before { content: "\f5cb"; } -.bi-textarea::before { content: "\f5cc"; } -.bi-thermometer-half::before { content: "\f5cd"; } -.bi-thermometer-high::before { content: "\f5ce"; } -.bi-thermometer-low::before { content: "\f5cf"; } -.bi-thermometer-snow::before { content: "\f5d0"; } -.bi-thermometer-sun::before { content: "\f5d1"; } -.bi-thermometer::before { content: "\f5d2"; } -.bi-three-dots-vertical::before { content: "\f5d3"; } -.bi-three-dots::before { content: "\f5d4"; } -.bi-toggle-off::before { content: "\f5d5"; } -.bi-toggle-on::before { content: "\f5d6"; } -.bi-toggle2-off::before { content: "\f5d7"; } -.bi-toggle2-on::before { content: "\f5d8"; } -.bi-toggles::before { content: "\f5d9"; } -.bi-toggles2::before { content: "\f5da"; } -.bi-tools::before { content: "\f5db"; } -.bi-tornado::before { content: "\f5dc"; } -.bi-trash-fill::before { content: "\f5dd"; } -.bi-trash::before { content: "\f5de"; } -.bi-trash2-fill::before { content: "\f5df"; } -.bi-trash2::before { content: "\f5e0"; } -.bi-tree-fill::before { content: "\f5e1"; } -.bi-tree::before { content: "\f5e2"; } -.bi-triangle-fill::before { content: "\f5e3"; } -.bi-triangle-half::before { content: "\f5e4"; } -.bi-triangle::before { content: "\f5e5"; } -.bi-trophy-fill::before { content: "\f5e6"; } -.bi-trophy::before { content: "\f5e7"; } -.bi-tropical-storm::before { content: "\f5e8"; } -.bi-truck-flatbed::before { content: "\f5e9"; } -.bi-truck::before { content: "\f5ea"; } -.bi-tsunami::before { content: "\f5eb"; } -.bi-tv-fill::before { content: "\f5ec"; } -.bi-tv::before { content: "\f5ed"; } -.bi-twitch::before { content: "\f5ee"; } -.bi-twitter::before { content: "\f5ef"; } -.bi-type-bold::before { content: "\f5f0"; } -.bi-type-h1::before { content: "\f5f1"; } -.bi-type-h2::before { content: "\f5f2"; } -.bi-type-h3::before { content: "\f5f3"; } -.bi-type-italic::before { content: "\f5f4"; } -.bi-type-strikethrough::before { content: "\f5f5"; } -.bi-type-underline::before { content: "\f5f6"; } -.bi-type::before { content: "\f5f7"; } -.bi-ui-checks-grid::before { content: "\f5f8"; } -.bi-ui-checks::before { content: "\f5f9"; } -.bi-ui-radios-grid::before { content: "\f5fa"; } -.bi-ui-radios::before { content: "\f5fb"; } -.bi-umbrella-fill::before { content: "\f5fc"; } -.bi-umbrella::before { content: "\f5fd"; } -.bi-union::before { content: "\f5fe"; } -.bi-unlock-fill::before { content: "\f5ff"; } -.bi-unlock::before { content: "\f600"; } -.bi-upc-scan::before { content: "\f601"; } -.bi-upc::before { content: "\f602"; } -.bi-upload::before { content: "\f603"; } -.bi-vector-pen::before { content: "\f604"; } -.bi-view-list::before { content: "\f605"; } -.bi-view-stacked::before { content: "\f606"; } -.bi-vinyl-fill::before { content: "\f607"; } -.bi-vinyl::before { content: "\f608"; } -.bi-voicemail::before { content: "\f609"; } -.bi-volume-down-fill::before { content: "\f60a"; } -.bi-volume-down::before { content: "\f60b"; } -.bi-volume-mute-fill::before { content: "\f60c"; } -.bi-volume-mute::before { content: "\f60d"; } -.bi-volume-off-fill::before { content: "\f60e"; } -.bi-volume-off::before { content: "\f60f"; } -.bi-volume-up-fill::before { content: "\f610"; } -.bi-volume-up::before { content: "\f611"; } -.bi-vr::before { content: "\f612"; } -.bi-wallet-fill::before { content: "\f613"; } -.bi-wallet::before { content: "\f614"; } -.bi-wallet2::before { content: "\f615"; } -.bi-watch::before { content: "\f616"; } -.bi-water::before { content: "\f617"; } -.bi-whatsapp::before { content: "\f618"; } -.bi-wifi-1::before { content: "\f619"; } -.bi-wifi-2::before { content: "\f61a"; } -.bi-wifi-off::before { content: "\f61b"; } -.bi-wifi::before { content: "\f61c"; } -.bi-wind::before { content: "\f61d"; } -.bi-window-dock::before { content: "\f61e"; } -.bi-window-sidebar::before { content: "\f61f"; } -.bi-window::before { content: "\f620"; } -.bi-wrench::before { content: "\f621"; } -.bi-x-circle-fill::before { content: "\f622"; } -.bi-x-circle::before { content: "\f623"; } -.bi-x-diamond-fill::before { content: "\f624"; } -.bi-x-diamond::before { content: "\f625"; } -.bi-x-octagon-fill::before { content: "\f626"; } -.bi-x-octagon::before { content: "\f627"; } -.bi-x-square-fill::before { content: "\f628"; } -.bi-x-square::before { content: "\f629"; } -.bi-x::before { content: "\f62a"; } -.bi-youtube::before { content: "\f62b"; } -.bi-zoom-in::before { content: "\f62c"; } -.bi-zoom-out::before { content: "\f62d"; } -.bi-bank::before { content: "\f62e"; } -.bi-bank2::before { content: "\f62f"; } -.bi-bell-slash-fill::before { content: "\f630"; } -.bi-bell-slash::before { content: "\f631"; } -.bi-cash-coin::before { content: "\f632"; } -.bi-check-lg::before { content: "\f633"; } -.bi-coin::before { content: "\f634"; } -.bi-currency-bitcoin::before { content: "\f635"; } -.bi-currency-dollar::before { content: "\f636"; } -.bi-currency-euro::before { content: "\f637"; } -.bi-currency-exchange::before { content: "\f638"; } -.bi-currency-pound::before { content: "\f639"; } -.bi-currency-yen::before { content: "\f63a"; } -.bi-dash-lg::before { content: "\f63b"; } -.bi-exclamation-lg::before { content: "\f63c"; } -.bi-file-earmark-pdf-fill::before { content: "\f63d"; } -.bi-file-earmark-pdf::before { content: "\f63e"; } -.bi-file-pdf-fill::before { content: "\f63f"; } -.bi-file-pdf::before { content: "\f640"; } -.bi-gender-ambiguous::before { content: "\f641"; } -.bi-gender-female::before { content: "\f642"; } -.bi-gender-male::before { content: "\f643"; } -.bi-gender-trans::before { content: "\f644"; } -.bi-headset-vr::before { content: "\f645"; } -.bi-info-lg::before { content: "\f646"; } -.bi-mastodon::before { content: "\f647"; } -.bi-messenger::before { content: "\f648"; } -.bi-piggy-bank-fill::before { content: "\f649"; } -.bi-piggy-bank::before { content: "\f64a"; } -.bi-pin-map-fill::before { content: "\f64b"; } -.bi-pin-map::before { content: "\f64c"; } -.bi-plus-lg::before { content: "\f64d"; } -.bi-question-lg::before { content: "\f64e"; } -.bi-recycle::before { content: "\f64f"; } -.bi-reddit::before { content: "\f650"; } -.bi-safe-fill::before { content: "\f651"; } -.bi-safe2-fill::before { content: "\f652"; } -.bi-safe2::before { content: "\f653"; } -.bi-sd-card-fill::before { content: "\f654"; } -.bi-sd-card::before { content: "\f655"; } -.bi-skype::before { content: "\f656"; } -.bi-slash-lg::before { content: "\f657"; } -.bi-translate::before { content: "\f658"; } -.bi-x-lg::before { content: "\f659"; } -.bi-safe::before { content: "\f65a"; } -.bi-apple::before { content: "\f65b"; } -.bi-microsoft::before { content: "\f65d"; } -.bi-windows::before { content: "\f65e"; } -.bi-behance::before { content: "\f65c"; } -.bi-dribbble::before { content: "\f65f"; } -.bi-line::before { content: "\f660"; } -.bi-medium::before { content: "\f661"; } -.bi-paypal::before { content: "\f662"; } -.bi-pinterest::before { content: "\f663"; } -.bi-signal::before { content: "\f664"; } -.bi-snapchat::before { content: "\f665"; } -.bi-spotify::before { content: "\f666"; } -.bi-stack-overflow::before { content: "\f667"; } -.bi-strava::before { content: "\f668"; } -.bi-wordpress::before { content: "\f669"; } -.bi-vimeo::before { content: "\f66a"; } -.bi-activity::before { content: "\f66b"; } -.bi-easel2-fill::before { content: "\f66c"; } -.bi-easel2::before { content: "\f66d"; } -.bi-easel3-fill::before { content: "\f66e"; } -.bi-easel3::before { content: "\f66f"; } -.bi-fan::before { content: "\f670"; } -.bi-fingerprint::before { content: "\f671"; } -.bi-graph-down-arrow::before { content: "\f672"; } -.bi-graph-up-arrow::before { content: "\f673"; } -.bi-hypnotize::before { content: "\f674"; } -.bi-magic::before { content: "\f675"; } -.bi-person-rolodex::before { content: "\f676"; } -.bi-person-video::before { content: "\f677"; } -.bi-person-video2::before { content: "\f678"; } -.bi-person-video3::before { content: "\f679"; } -.bi-person-workspace::before { content: "\f67a"; } -.bi-radioactive::before { content: "\f67b"; } -.bi-webcam-fill::before { content: "\f67c"; } -.bi-webcam::before { content: "\f67d"; } -.bi-yin-yang::before { content: "\f67e"; } -.bi-bandaid-fill::before { content: "\f680"; } -.bi-bandaid::before { content: "\f681"; } -.bi-bluetooth::before { content: "\f682"; } -.bi-body-text::before { content: "\f683"; } -.bi-boombox::before { content: "\f684"; } -.bi-boxes::before { content: "\f685"; } -.bi-dpad-fill::before { content: "\f686"; } -.bi-dpad::before { content: "\f687"; } -.bi-ear-fill::before { content: "\f688"; } -.bi-ear::before { content: "\f689"; } -.bi-envelope-check-1::before { content: "\f68a"; } -.bi-envelope-check-fill::before { content: "\f68b"; } -.bi-envelope-check::before { content: "\f68c"; } -.bi-envelope-dash-1::before { content: "\f68d"; } -.bi-envelope-dash-fill::before { content: "\f68e"; } -.bi-envelope-dash::before { content: "\f68f"; } -.bi-envelope-exclamation-1::before { content: "\f690"; } -.bi-envelope-exclamation-fill::before { content: "\f691"; } -.bi-envelope-exclamation::before { content: "\f692"; } -.bi-envelope-plus-fill::before { content: "\f693"; } -.bi-envelope-plus::before { content: "\f694"; } -.bi-envelope-slash-1::before { content: "\f695"; } -.bi-envelope-slash-fill::before { content: "\f696"; } -.bi-envelope-slash::before { content: "\f697"; } -.bi-envelope-x-1::before { content: "\f698"; } -.bi-envelope-x-fill::before { content: "\f699"; } -.bi-envelope-x::before { content: "\f69a"; } -.bi-explicit-fill::before { content: "\f69b"; } -.bi-explicit::before { content: "\f69c"; } -.bi-git::before { content: "\f69d"; } -.bi-infinity::before { content: "\f69e"; } -.bi-list-columns-reverse::before { content: "\f69f"; } -.bi-list-columns::before { content: "\f6a0"; } -.bi-meta::before { content: "\f6a1"; } -.bi-mortorboard-fill::before { content: "\f6a2"; } -.bi-mortorboard::before { content: "\f6a3"; } -.bi-nintendo-switch::before { content: "\f6a4"; } -.bi-pc-display-horizontal::before { content: "\f6a5"; } -.bi-pc-display::before { content: "\f6a6"; } -.bi-pc-horizontal::before { content: "\f6a7"; } -.bi-pc::before { content: "\f6a8"; } -.bi-playstation::before { content: "\f6a9"; } -.bi-plus-slash-minus::before { content: "\f6aa"; } -.bi-projector-fill::before { content: "\f6ab"; } -.bi-projector::before { content: "\f6ac"; } -.bi-qr-code-scan::before { content: "\f6ad"; } -.bi-qr-code::before { content: "\f6ae"; } -.bi-quora::before { content: "\f6af"; } -.bi-quote::before { content: "\f6b0"; } -.bi-robot::before { content: "\f6b1"; } -.bi-send-check-fill::before { content: "\f6b2"; } -.bi-send-check::before { content: "\f6b3"; } -.bi-send-dash-fill::before { content: "\f6b4"; } -.bi-send-dash::before { content: "\f6b5"; } -.bi-send-exclamation-1::before { content: "\f6b6"; } -.bi-send-exclamation-fill::before { content: "\f6b7"; } -.bi-send-exclamation::before { content: "\f6b8"; } -.bi-send-fill::before { content: "\f6b9"; } -.bi-send-plus-fill::before { content: "\f6ba"; } -.bi-send-plus::before { content: "\f6bb"; } -.bi-send-slash-fill::before { content: "\f6bc"; } -.bi-send-slash::before { content: "\f6bd"; } -.bi-send-x-fill::before { content: "\f6be"; } -.bi-send-x::before { content: "\f6bf"; } -.bi-send::before { content: "\f6c0"; } -.bi-steam::before { content: "\f6c1"; } -.bi-terminal-dash-1::before { content: "\f6c2"; } -.bi-terminal-dash::before { content: "\f6c3"; } -.bi-terminal-plus::before { content: "\f6c4"; } -.bi-terminal-split::before { content: "\f6c5"; } -.bi-ticket-detailed-fill::before { content: "\f6c6"; } -.bi-ticket-detailed::before { content: "\f6c7"; } -.bi-ticket-fill::before { content: "\f6c8"; } -.bi-ticket-perforated-fill::before { content: "\f6c9"; } -.bi-ticket-perforated::before { content: "\f6ca"; } -.bi-ticket::before { content: "\f6cb"; } -.bi-tiktok::before { content: "\f6cc"; } -.bi-window-dash::before { content: "\f6cd"; } -.bi-window-desktop::before { content: "\f6ce"; } -.bi-window-fullscreen::before { content: "\f6cf"; } -.bi-window-plus::before { content: "\f6d0"; } -.bi-window-split::before { content: "\f6d1"; } -.bi-window-stack::before { content: "\f6d2"; } -.bi-window-x::before { content: "\f6d3"; } -.bi-xbox::before { content: "\f6d4"; } -.bi-ethernet::before { content: "\f6d5"; } -.bi-hdmi-fill::before { content: "\f6d6"; } -.bi-hdmi::before { content: "\f6d7"; } -.bi-usb-c-fill::before { content: "\f6d8"; } -.bi-usb-c::before { content: "\f6d9"; } -.bi-usb-fill::before { content: "\f6da"; } -.bi-usb-plug-fill::before { content: "\f6db"; } -.bi-usb-plug::before { content: "\f6dc"; } -.bi-usb-symbol::before { content: "\f6dd"; } -.bi-usb::before { content: "\f6de"; } -.bi-boombox-fill::before { content: "\f6df"; } -.bi-displayport-1::before { content: "\f6e0"; } -.bi-displayport::before { content: "\f6e1"; } -.bi-gpu-card::before { content: "\f6e2"; } -.bi-memory::before { content: "\f6e3"; } -.bi-modem-fill::before { content: "\f6e4"; } -.bi-modem::before { content: "\f6e5"; } -.bi-motherboard-fill::before { content: "\f6e6"; } -.bi-motherboard::before { content: "\f6e7"; } -.bi-optical-audio-fill::before { content: "\f6e8"; } -.bi-optical-audio::before { content: "\f6e9"; } -.bi-pci-card::before { content: "\f6ea"; } -.bi-router-fill::before { content: "\f6eb"; } -.bi-router::before { content: "\f6ec"; } -.bi-ssd-fill::before { content: "\f6ed"; } -.bi-ssd::before { content: "\f6ee"; } -.bi-thunderbolt-fill::before { content: "\f6ef"; } -.bi-thunderbolt::before { content: "\f6f0"; } -.bi-usb-drive-fill::before { content: "\f6f1"; } -.bi-usb-drive::before { content: "\f6f2"; } -.bi-usb-micro-fill::before { content: "\f6f3"; } -.bi-usb-micro::before { content: "\f6f4"; } -.bi-usb-mini-fill::before { content: "\f6f5"; } -.bi-usb-mini::before { content: "\f6f6"; } -.bi-cloud-haze2::before { content: "\f6f7"; } -.bi-device-hdd-fill::before { content: "\f6f8"; } -.bi-device-hdd::before { content: "\f6f9"; } -.bi-device-ssd-fill::before { content: "\f6fa"; } -.bi-device-ssd::before { content: "\f6fb"; } -.bi-displayport-fill::before { content: "\f6fc"; } -.bi-mortarboard-fill::before { content: "\f6fd"; } -.bi-mortarboard::before { content: "\f6fe"; } -.bi-terminal-x::before { content: "\f6ff"; } -.bi-arrow-through-heart-fill::before { content: "\f700"; } -.bi-arrow-through-heart::before { content: "\f701"; } -.bi-badge-sd-fill::before { content: "\f702"; } -.bi-badge-sd::before { content: "\f703"; } -.bi-bag-heart-fill::before { content: "\f704"; } -.bi-bag-heart::before { content: "\f705"; } -.bi-balloon-fill::before { content: "\f706"; } -.bi-balloon-heart-fill::before { content: "\f707"; } -.bi-balloon-heart::before { content: "\f708"; } -.bi-balloon::before { content: "\f709"; } -.bi-box2-fill::before { content: "\f70a"; } -.bi-box2-heart-fill::before { content: "\f70b"; } -.bi-box2-heart::before { content: "\f70c"; } -.bi-box2::before { content: "\f70d"; } -.bi-braces-asterisk::before { content: "\f70e"; } -.bi-calendar-heart-fill::before { content: "\f70f"; } -.bi-calendar-heart::before { content: "\f710"; } -.bi-calendar2-heart-fill::before { content: "\f711"; } -.bi-calendar2-heart::before { content: "\f712"; } -.bi-chat-heart-fill::before { content: "\f713"; } -.bi-chat-heart::before { content: "\f714"; } -.bi-chat-left-heart-fill::before { content: "\f715"; } -.bi-chat-left-heart::before { content: "\f716"; } -.bi-chat-right-heart-fill::before { content: "\f717"; } -.bi-chat-right-heart::before { content: "\f718"; } -.bi-chat-square-heart-fill::before { content: "\f719"; } -.bi-chat-square-heart::before { content: "\f71a"; } -.bi-clipboard-check-fill::before { content: "\f71b"; } -.bi-clipboard-data-fill::before { content: "\f71c"; } -.bi-clipboard-fill::before { content: "\f71d"; } -.bi-clipboard-heart-fill::before { content: "\f71e"; } -.bi-clipboard-heart::before { content: "\f71f"; } -.bi-clipboard-minus-fill::before { content: "\f720"; } -.bi-clipboard-plus-fill::before { content: "\f721"; } -.bi-clipboard-pulse::before { content: "\f722"; } -.bi-clipboard-x-fill::before { content: "\f723"; } -.bi-clipboard2-check-fill::before { content: "\f724"; } -.bi-clipboard2-check::before { content: "\f725"; } -.bi-clipboard2-data-fill::before { content: "\f726"; } -.bi-clipboard2-data::before { content: "\f727"; } -.bi-clipboard2-fill::before { content: "\f728"; } -.bi-clipboard2-heart-fill::before { content: "\f729"; } -.bi-clipboard2-heart::before { content: "\f72a"; } -.bi-clipboard2-minus-fill::before { content: "\f72b"; } -.bi-clipboard2-minus::before { content: "\f72c"; } -.bi-clipboard2-plus-fill::before { content: "\f72d"; } -.bi-clipboard2-plus::before { content: "\f72e"; } -.bi-clipboard2-pulse-fill::before { content: "\f72f"; } -.bi-clipboard2-pulse::before { content: "\f730"; } -.bi-clipboard2-x-fill::before { content: "\f731"; } -.bi-clipboard2-x::before { content: "\f732"; } -.bi-clipboard2::before { content: "\f733"; } -.bi-emoji-kiss-fill::before { content: "\f734"; } -.bi-emoji-kiss::before { content: "\f735"; } -.bi-envelope-heart-fill::before { content: "\f736"; } -.bi-envelope-heart::before { content: "\f737"; } -.bi-envelope-open-heart-fill::before { content: "\f738"; } -.bi-envelope-open-heart::before { content: "\f739"; } -.bi-envelope-paper-fill::before { content: "\f73a"; } -.bi-envelope-paper-heart-fill::before { content: "\f73b"; } -.bi-envelope-paper-heart::before { content: "\f73c"; } -.bi-envelope-paper::before { content: "\f73d"; } -.bi-filetype-aac::before { content: "\f73e"; } -.bi-filetype-ai::before { content: "\f73f"; } -.bi-filetype-bmp::before { content: "\f740"; } -.bi-filetype-cs::before { content: "\f741"; } -.bi-filetype-css::before { content: "\f742"; } -.bi-filetype-csv::before { content: "\f743"; } -.bi-filetype-doc::before { content: "\f744"; } -.bi-filetype-docx::before { content: "\f745"; } -.bi-filetype-exe::before { content: "\f746"; } -.bi-filetype-gif::before { content: "\f747"; } -.bi-filetype-heic::before { content: "\f748"; } -.bi-filetype-html::before { content: "\f749"; } -.bi-filetype-java::before { content: "\f74a"; } -.bi-filetype-jpg::before { content: "\f74b"; } -.bi-filetype-js::before { content: "\f74c"; } -.bi-filetype-jsx::before { content: "\f74d"; } -.bi-filetype-key::before { content: "\f74e"; } -.bi-filetype-m4p::before { content: "\f74f"; } -.bi-filetype-md::before { content: "\f750"; } -.bi-filetype-mdx::before { content: "\f751"; } -.bi-filetype-mov::before { content: "\f752"; } -.bi-filetype-mp3::before { content: "\f753"; } -.bi-filetype-mp4::before { content: "\f754"; } -.bi-filetype-otf::before { content: "\f755"; } -.bi-filetype-pdf::before { content: "\f756"; } -.bi-filetype-php::before { content: "\f757"; } -.bi-filetype-png::before { content: "\f758"; } -.bi-filetype-ppt-1::before { content: "\f759"; } -.bi-filetype-ppt::before { content: "\f75a"; } -.bi-filetype-psd::before { content: "\f75b"; } -.bi-filetype-py::before { content: "\f75c"; } -.bi-filetype-raw::before { content: "\f75d"; } -.bi-filetype-rb::before { content: "\f75e"; } -.bi-filetype-sass::before { content: "\f75f"; } -.bi-filetype-scss::before { content: "\f760"; } -.bi-filetype-sh::before { content: "\f761"; } -.bi-filetype-svg::before { content: "\f762"; } -.bi-filetype-tiff::before { content: "\f763"; } -.bi-filetype-tsx::before { content: "\f764"; } -.bi-filetype-ttf::before { content: "\f765"; } -.bi-filetype-txt::before { content: "\f766"; } -.bi-filetype-wav::before { content: "\f767"; } -.bi-filetype-woff::before { content: "\f768"; } -.bi-filetype-xls-1::before { content: "\f769"; } -.bi-filetype-xls::before { content: "\f76a"; } -.bi-filetype-xml::before { content: "\f76b"; } -.bi-filetype-yml::before { content: "\f76c"; } -.bi-heart-arrow::before { content: "\f76d"; } -.bi-heart-pulse-fill::before { content: "\f76e"; } -.bi-heart-pulse::before { content: "\f76f"; } -.bi-heartbreak-fill::before { content: "\f770"; } -.bi-heartbreak::before { content: "\f771"; } -.bi-hearts::before { content: "\f772"; } -.bi-hospital-fill::before { content: "\f773"; } -.bi-hospital::before { content: "\f774"; } -.bi-house-heart-fill::before { content: "\f775"; } -.bi-house-heart::before { content: "\f776"; } -.bi-incognito::before { content: "\f777"; } -.bi-magnet-fill::before { content: "\f778"; } -.bi-magnet::before { content: "\f779"; } -.bi-person-heart::before { content: "\f77a"; } -.bi-person-hearts::before { content: "\f77b"; } -.bi-phone-flip::before { content: "\f77c"; } -.bi-plugin::before { content: "\f77d"; } -.bi-postage-fill::before { content: "\f77e"; } -.bi-postage-heart-fill::before { content: "\f77f"; } -.bi-postage-heart::before { content: "\f780"; } -.bi-postage::before { content: "\f781"; } -.bi-postcard-fill::before { content: "\f782"; } -.bi-postcard-heart-fill::before { content: "\f783"; } -.bi-postcard-heart::before { content: "\f784"; } -.bi-postcard::before { content: "\f785"; } -.bi-search-heart-fill::before { content: "\f786"; } -.bi-search-heart::before { content: "\f787"; } -.bi-sliders2-vertical::before { content: "\f788"; } -.bi-sliders2::before { content: "\f789"; } -.bi-trash3-fill::before { content: "\f78a"; } -.bi-trash3::before { content: "\f78b"; } -.bi-valentine::before { content: "\f78c"; } -.bi-valentine2::before { content: "\f78d"; } -.bi-wrench-adjustable-circle-fill::before { content: "\f78e"; } -.bi-wrench-adjustable-circle::before { content: "\f78f"; } -.bi-wrench-adjustable::before { content: "\f790"; } -.bi-filetype-json::before { content: "\f791"; } -.bi-filetype-pptx::before { content: "\f792"; } -.bi-filetype-xlsx::before { content: "\f793"; } diff --git a/_proc/_docs/site_libs/bootstrap/bootstrap-icons.woff b/_proc/_docs/site_libs/bootstrap/bootstrap-icons.woff deleted file mode 100644 index b26ccd1ac9f9f1fbc980e93531398364f6f03cd2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 137124 zcma%@WmHsO*tchh85%_d=?89+ekW&jlt>5?8mK%`q5lx_q;Ktj4Z z9P&N;zt;19cs@O@b*{PhZ(sYKea<=z1I*Gx=l*>d90r5oP=AIILy!31eE%Cm<^TSt zs`o?*27?noxioY0||Y(@`+kwH7G5My@3M+~Jw$D;RuR7h1;z9n8o&IKSgF z2Wuz;`;moC(#~BZw)T~iiz^JiQwoD|?ZIHrw~Cjt{5(^wES_6f%vs*GD7CV1etkgr zY_3Crm7f z=n=G8&(x)dwD8Z`oU?O@l*ViWIyOc;v0Jcr|m||MPEPduEz$rMP{kAw6Jj zU`0;PBS!euK=D_zSw{5x)b}DJB=<#H7uxo&hSA6c18lRo0qs?@c?~?TBOpDH^MiR3 zrmkGJShh?yN47#Xud%SPz_0M)^F^6>xp@vq{X0apR%VYZ0r+*z3m#BoaVEXF_hjC4o5ZZ_~@d-KHiiui2yY}Uhm&y<=r zq$6i<-e*Jg!WO1|Yj(U%giu=}c6d<)Ut3*ocvOT`TXSUiaL;s5O?bFZgt%X$Vt7*o z*{|+0{6~bmpHBX-uZTRK0`X6!%Da3@!KjC{T4BTUm3X9?9JVyH8b44*Pa_iYZlY9Z zAMgzKR1y_w6b!FdB8t@QhY6mhjAgpn%0A5y!;sptJ1A$PtR~-x<@BRmCWER!7oqGY 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%0Av_>K5 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:inherit}kbd{padding:.4rem .4rem;font-size:0.875em;color:#fff;background-color:#212529}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}thead,tbody,tfoot,tr,td,th{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}input,button,select,optgroup,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button:not(:disabled),[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + 0.3vw);line-height:inherit}@media(min-width: 1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-text,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none !important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:0.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:0.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:0.875em;color:#6c757d}.grid{display:grid;grid-template-rows:repeat(var(--bs-rows, 1), 1fr);grid-template-columns:repeat(var(--bs-columns, 12), 1fr);gap:var(--bs-gap, 1.5rem)}.grid .g-col-1{grid-column:auto/span 1}.grid .g-col-2{grid-column:auto/span 2}.grid .g-col-3{grid-column:auto/span 3}.grid .g-col-4{grid-column:auto/span 4}.grid .g-col-5{grid-column:auto/span 5}.grid .g-col-6{grid-column:auto/span 6}.grid .g-col-7{grid-column:auto/span 7}.grid .g-col-8{grid-column:auto/span 8}.grid .g-col-9{grid-column:auto/span 9}.grid .g-col-10{grid-column:auto/span 10}.grid .g-col-11{grid-column:auto/span 11}.grid .g-col-12{grid-column:auto/span 12}.grid .g-start-1{grid-column-start:1}.grid .g-start-2{grid-column-start:2}.grid .g-start-3{grid-column-start:3}.grid .g-start-4{grid-column-start:4}.grid .g-start-5{grid-column-start:5}.grid .g-start-6{grid-column-start:6}.grid .g-start-7{grid-column-start:7}.grid .g-start-8{grid-column-start:8}.grid .g-start-9{grid-column-start:9}.grid .g-start-10{grid-column-start:10}.grid .g-start-11{grid-column-start:11}@media(min-width: 576px){.grid .g-col-sm-1{grid-column:auto/span 1}.grid .g-col-sm-2{grid-column:auto/span 2}.grid .g-col-sm-3{grid-column:auto/span 3}.grid .g-col-sm-4{grid-column:auto/span 4}.grid .g-col-sm-5{grid-column:auto/span 5}.grid .g-col-sm-6{grid-column:auto/span 6}.grid .g-col-sm-7{grid-column:auto/span 7}.grid .g-col-sm-8{grid-column:auto/span 8}.grid .g-col-sm-9{grid-column:auto/span 9}.grid .g-col-sm-10{grid-column:auto/span 10}.grid .g-col-sm-11{grid-column:auto/span 11}.grid .g-col-sm-12{grid-column:auto/span 12}.grid .g-start-sm-1{grid-column-start:1}.grid .g-start-sm-2{grid-column-start:2}.grid .g-start-sm-3{grid-column-start:3}.grid .g-start-sm-4{grid-column-start:4}.grid .g-start-sm-5{grid-column-start:5}.grid .g-start-sm-6{grid-column-start:6}.grid .g-start-sm-7{grid-column-start:7}.grid .g-start-sm-8{grid-column-start:8}.grid .g-start-sm-9{grid-column-start:9}.grid .g-start-sm-10{grid-column-start:10}.grid .g-start-sm-11{grid-column-start:11}}@media(min-width: 768px){.grid .g-col-md-1{grid-column:auto/span 1}.grid .g-col-md-2{grid-column:auto/span 2}.grid .g-col-md-3{grid-column:auto/span 3}.grid .g-col-md-4{grid-column:auto/span 4}.grid .g-col-md-5{grid-column:auto/span 5}.grid .g-col-md-6{grid-column:auto/span 6}.grid .g-col-md-7{grid-column:auto/span 7}.grid .g-col-md-8{grid-column:auto/span 8}.grid .g-col-md-9{grid-column:auto/span 9}.grid .g-col-md-10{grid-column:auto/span 10}.grid .g-col-md-11{grid-column:auto/span 11}.grid .g-col-md-12{grid-column:auto/span 12}.grid .g-start-md-1{grid-column-start:1}.grid .g-start-md-2{grid-column-start:2}.grid .g-start-md-3{grid-column-start:3}.grid .g-start-md-4{grid-column-start:4}.grid .g-start-md-5{grid-column-start:5}.grid .g-start-md-6{grid-column-start:6}.grid .g-start-md-7{grid-column-start:7}.grid .g-start-md-8{grid-column-start:8}.grid .g-start-md-9{grid-column-start:9}.grid .g-start-md-10{grid-column-start:10}.grid .g-start-md-11{grid-column-start:11}}@media(min-width: 992px){.grid .g-col-lg-1{grid-column:auto/span 1}.grid .g-col-lg-2{grid-column:auto/span 2}.grid .g-col-lg-3{grid-column:auto/span 3}.grid .g-col-lg-4{grid-column:auto/span 4}.grid .g-col-lg-5{grid-column:auto/span 5}.grid .g-col-lg-6{grid-column:auto/span 6}.grid .g-col-lg-7{grid-column:auto/span 7}.grid .g-col-lg-8{grid-column:auto/span 8}.grid .g-col-lg-9{grid-column:auto/span 9}.grid .g-col-lg-10{grid-column:auto/span 10}.grid .g-col-lg-11{grid-column:auto/span 11}.grid .g-col-lg-12{grid-column:auto/span 12}.grid .g-start-lg-1{grid-column-start:1}.grid .g-start-lg-2{grid-column-start:2}.grid .g-start-lg-3{grid-column-start:3}.grid .g-start-lg-4{grid-column-start:4}.grid .g-start-lg-5{grid-column-start:5}.grid .g-start-lg-6{grid-column-start:6}.grid .g-start-lg-7{grid-column-start:7}.grid .g-start-lg-8{grid-column-start:8}.grid .g-start-lg-9{grid-column-start:9}.grid .g-start-lg-10{grid-column-start:10}.grid .g-start-lg-11{grid-column-start:11}}@media(min-width: 1200px){.grid .g-col-xl-1{grid-column:auto/span 1}.grid .g-col-xl-2{grid-column:auto/span 2}.grid .g-col-xl-3{grid-column:auto/span 3}.grid .g-col-xl-4{grid-column:auto/span 4}.grid .g-col-xl-5{grid-column:auto/span 5}.grid .g-col-xl-6{grid-column:auto/span 6}.grid .g-col-xl-7{grid-column:auto/span 7}.grid .g-col-xl-8{grid-column:auto/span 8}.grid .g-col-xl-9{grid-column:auto/span 9}.grid .g-col-xl-10{grid-column:auto/span 10}.grid .g-col-xl-11{grid-column:auto/span 11}.grid .g-col-xl-12{grid-column:auto/span 12}.grid .g-start-xl-1{grid-column-start:1}.grid .g-start-xl-2{grid-column-start:2}.grid .g-start-xl-3{grid-column-start:3}.grid .g-start-xl-4{grid-column-start:4}.grid .g-start-xl-5{grid-column-start:5}.grid .g-start-xl-6{grid-column-start:6}.grid .g-start-xl-7{grid-column-start:7}.grid .g-start-xl-8{grid-column-start:8}.grid .g-start-xl-9{grid-column-start:9}.grid .g-start-xl-10{grid-column-start:10}.grid .g-start-xl-11{grid-column-start:11}}@media(min-width: 1400px){.grid .g-col-xxl-1{grid-column:auto/span 1}.grid .g-col-xxl-2{grid-column:auto/span 2}.grid .g-col-xxl-3{grid-column:auto/span 3}.grid .g-col-xxl-4{grid-column:auto/span 4}.grid .g-col-xxl-5{grid-column:auto/span 5}.grid .g-col-xxl-6{grid-column:auto/span 6}.grid .g-col-xxl-7{grid-column:auto/span 7}.grid .g-col-xxl-8{grid-column:auto/span 8}.grid .g-col-xxl-9{grid-column:auto/span 9}.grid .g-col-xxl-10{grid-column:auto/span 10}.grid .g-col-xxl-11{grid-column:auto/span 11}.grid .g-col-xxl-12{grid-column:auto/span 12}.grid .g-start-xxl-1{grid-column-start:1}.grid .g-start-xxl-2{grid-column-start:2}.grid .g-start-xxl-3{grid-column-start:3}.grid .g-start-xxl-4{grid-column-start:4}.grid .g-start-xxl-5{grid-column-start:5}.grid .g-start-xxl-6{grid-column-start:6}.grid .g-start-xxl-7{grid-column-start:7}.grid .g-start-xxl-8{grid-column-start:8}.grid .g-start-xxl-9{grid-column-start:9}.grid .g-start-xxl-10{grid-column-start:10}.grid .g-start-xxl-11{grid-column-start:11}}.table{--bs-table-bg: transparent;--bs-table-accent-bg: transparent;--bs-table-striped-color: #373a3c;--bs-table-striped-bg: rgba(0, 0, 0, 0.05);--bs-table-active-color: #373a3c;--bs-table-active-bg: rgba(0, 0, 0, 0.1);--bs-table-hover-color: #373a3c;--bs-table-hover-bg: rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:#373a3c;vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:first-child){border-top:2px solid currentColor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*{--bs-table-accent-bg: var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg: var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover>*{--bs-table-accent-bg: var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg: #d4e6f9;--bs-table-striped-bg: #c9dbed;--bs-table-striped-color: #000;--bs-table-active-bg: #bfcfe0;--bs-table-active-color: #000;--bs-table-hover-bg: #c4d5e6;--bs-table-hover-color: #000;color:#000;border-color:#bfcfe0}.table-secondary{--bs-table-bg: #d7d8d8;--bs-table-striped-bg: #cccdcd;--bs-table-striped-color: #000;--bs-table-active-bg: #c2c2c2;--bs-table-active-color: #000;--bs-table-hover-bg: #c7c8c8;--bs-table-hover-color: #000;color:#000;border-color:#c2c2c2}.table-success{--bs-table-bg: #d9f0d1;--bs-table-striped-bg: #cee4c7;--bs-table-striped-color: #000;--bs-table-active-bg: #c3d8bc;--bs-table-active-color: #000;--bs-table-hover-bg: #c9dec1;--bs-table-hover-color: #000;color:#000;border-color:#c3d8bc}.table-info{--bs-table-bg: #ebddf1;--bs-table-striped-bg: #dfd2e5;--bs-table-striped-color: #000;--bs-table-active-bg: #d4c7d9;--bs-table-active-color: #000;--bs-table-hover-bg: #d9ccdf;--bs-table-hover-color: #000;color:#000;border-color:#d4c7d9}.table-warning{--bs-table-bg: #ffe3d1;--bs-table-striped-bg: #f2d8c7;--bs-table-striped-color: #000;--bs-table-active-bg: #e6ccbc;--bs-table-active-color: #000;--bs-table-hover-bg: #ecd2c1;--bs-table-hover-color: #000;color:#000;border-color:#e6ccbc}.table-danger{--bs-table-bg: #ffccd7;--bs-table-striped-bg: #f2c2cc;--bs-table-striped-color: #000;--bs-table-active-bg: #e6b8c2;--bs-table-active-color: #000;--bs-table-hover-bg: #ecbdc7;--bs-table-hover-color: #000;color:#000;border-color:#e6b8c2}.table-light{--bs-table-bg: #f8f9fa;--bs-table-striped-bg: #ecedee;--bs-table-striped-color: #000;--bs-table-active-bg: #dfe0e1;--bs-table-active-color: #000;--bs-table-hover-bg: #e5e6e7;--bs-table-hover-color: #000;color:#000;border-color:#dfe0e1}.table-dark{--bs-table-bg: #373a3c;--bs-table-striped-bg: #414446;--bs-table-striped-color: #fff;--bs-table-active-bg: #4b4e50;--bs-table-active-color: #fff;--bs-table-hover-bg: #46494b;--bs-table-hover-color: #fff;color:#fff;border-color:#4b4e50}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media(max-width: 575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label,.shiny-input-container .control-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(0.375rem + 1px);padding-bottom:calc(0.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(0.5rem + 1px);padding-bottom:calc(0.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(0.25rem + 1px);padding-bottom:calc(0.25rem + 1px);font-size:0.875rem}.form-text{margin-top:.25rem;font-size:0.875em;color:#6c757d}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#373a3c;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;border-radius:0;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#373a3c;background-color:#fff;border-color:#93c0f1;outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-0.375rem -0.75rem;margin-inline-end:.75rem;color:#373a3c;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#dde0e3}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-0.375rem -0.75rem;margin-inline-end:.75rem;color:#373a3c;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control::-webkit-file-upload-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#373a3c;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-sm,.form-control-plaintext.form-control-lg{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + 0.5rem + 2px);padding:.25rem .5rem;font-size:0.875rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-0.25rem -0.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-0.25rem -0.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-0.5rem -1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-0.5rem -1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + 0.75rem + 2px)}textarea.form-control-sm{min-height:calc(1.5em + 0.5rem + 2px)}textarea.form-control-lg{min-height:calc(1.5em + 1rem + 2px)}.form-control-color{width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em}.form-control-color::-webkit-color-swatch{height:1.5em}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;-moz-padding-start:calc(0.75rem - 3px);font-size:1rem;font-weight:400;line-height:1.5;color:#373a3c;background-color:#fff;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23373a3c' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:0;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}@media(prefers-reduced-motion: reduce){.form-select{transition:none}}.form-select:focus{border-color:#93c0f1;outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #373a3c}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:0.875rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.form-check,.shiny-input-container .checkbox,.shiny-input-container .radio{display:block;min-height:1.5rem;padding-left:0;margin-bottom:.125rem}.form-check .form-check-input,.form-check .shiny-input-container .checkbox input,.form-check .shiny-input-container .radio input,.shiny-input-container .checkbox .form-check-input,.shiny-input-container .checkbox .shiny-input-container .checkbox input,.shiny-input-container .checkbox .shiny-input-container .radio input,.shiny-input-container .radio .form-check-input,.shiny-input-container .radio .shiny-input-container .checkbox input,.shiny-input-container .radio .shiny-input-container .radio input{float:left;margin-left:0}.form-check-input,.shiny-input-container .checkbox input,.shiny-input-container .checkbox-inline input,.shiny-input-container .radio input,.shiny-input-container .radio-inline input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;color-adjust:exact;-webkit-print-color-adjust:exact}.form-check-input[type=radio],.shiny-input-container .checkbox input[type=radio],.shiny-input-container .checkbox-inline input[type=radio],.shiny-input-container .radio input[type=radio],.shiny-input-container .radio-inline input[type=radio]{border-radius:50%}.form-check-input:active,.shiny-input-container .checkbox input:active,.shiny-input-container .checkbox-inline input:active,.shiny-input-container .radio input:active,.shiny-input-container .radio-inline input:active{filter:brightness(90%)}.form-check-input:focus,.shiny-input-container .checkbox input:focus,.shiny-input-container .checkbox-inline input:focus,.shiny-input-container .radio input:focus,.shiny-input-container .radio-inline input:focus{border-color:#93c0f1;outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25)}.form-check-input:checked,.shiny-input-container .checkbox input:checked,.shiny-input-container .checkbox-inline input:checked,.shiny-input-container .radio input:checked,.shiny-input-container .radio-inline input:checked{background-color:#2780e3;border-color:#2780e3}.form-check-input:checked[type=checkbox],.shiny-input-container .checkbox input:checked[type=checkbox],.shiny-input-container .checkbox-inline input:checked[type=checkbox],.shiny-input-container .radio input:checked[type=checkbox],.shiny-input-container .radio-inline input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio],.shiny-input-container .checkbox input:checked[type=radio],.shiny-input-container .checkbox-inline input:checked[type=radio],.shiny-input-container .radio input:checked[type=radio],.shiny-input-container .radio-inline input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate,.shiny-input-container .checkbox input[type=checkbox]:indeterminate,.shiny-input-container .checkbox-inline input[type=checkbox]:indeterminate,.shiny-input-container .radio input[type=checkbox]:indeterminate,.shiny-input-container .radio-inline input[type=checkbox]:indeterminate{background-color:#2780e3;border-color:#2780e3;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled,.shiny-input-container .checkbox input:disabled,.shiny-input-container .checkbox-inline input:disabled,.shiny-input-container .radio input:disabled,.shiny-input-container .radio-inline input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input[disabled]~.form-check-label,.form-check-input[disabled]~span,.form-check-input:disabled~.form-check-label,.form-check-input:disabled~span,.shiny-input-container .checkbox input[disabled]~.form-check-label,.shiny-input-container .checkbox input[disabled]~span,.shiny-input-container .checkbox input:disabled~.form-check-label,.shiny-input-container .checkbox input:disabled~span,.shiny-input-container .checkbox-inline input[disabled]~.form-check-label,.shiny-input-container .checkbox-inline input[disabled]~span,.shiny-input-container .checkbox-inline input:disabled~.form-check-label,.shiny-input-container .checkbox-inline input:disabled~span,.shiny-input-container .radio input[disabled]~.form-check-label,.shiny-input-container .radio input[disabled]~span,.shiny-input-container .radio input:disabled~.form-check-label,.shiny-input-container .radio input:disabled~span,.shiny-input-container .radio-inline input[disabled]~.form-check-label,.shiny-input-container .radio-inline input[disabled]~span,.shiny-input-container .radio-inline input:disabled~.form-check-label,.shiny-input-container .radio-inline input:disabled~span{opacity:.5}.form-check-label,.shiny-input-container .checkbox label,.shiny-input-container .checkbox-inline label,.shiny-input-container .radio label,.shiny-input-container .radio-inline label{cursor:pointer}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-position:left center;transition:background-position .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2393c0f1'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-check-inline,.shiny-input-container .checkbox-inline,.shiny-input-container .radio-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.btn-check[disabled]+.btn,.btn-check:disabled+.btn{pointer-events:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:transparent;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(39,128,227,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(39,128,227,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-0.25rem;background-color:#2780e3;border:0;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}@media(prefers-reduced-motion: reduce){.form-range::-webkit-slider-thumb{transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#bed9f7}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#2780e3;border:0;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}@media(prefers-reduced-motion: reduce){.form-range::-moz-range-thumb{transition:none}}.form-range::-moz-range-thumb:active{background-color:#bed9f7}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px);line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media(prefers-reduced-motion: reduce){.form-floating>label{transition:none}}.form-floating>.form-control{padding:1rem .75rem}.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.input-group{position:relative;display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;align-items:stretch;-webkit-align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.input-group-text{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#373a3c;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da}.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text,.input-group-lg>.btn{padding:.5rem 1rem;font-size:1.25rem}.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text,.input-group-sm>.btn{padding:.25rem .5rem;font-size:0.875rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:0.875em;color:#3fb618}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:0.875rem;color:#fff;background-color:rgba(63,182,24,.9)}.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip,.is-valid~.valid-feedback,.is-valid~.valid-tooltip{display:block}.was-validated .form-control:valid,.form-control.is-valid{border-color:#3fb618;padding-right:calc(1.5em + 0.75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%233fb618' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(0.375em + 0.1875rem) center;background-size:calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-control:valid:focus,.form-control.is-valid:focus{border-color:#3fb618;box-shadow:0 0 0 .25rem rgba(63,182,24,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + 0.75rem);background-position:top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem)}.was-validated .form-select:valid,.form-select.is-valid{border-color:#3fb618}.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"],.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23373a3c' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%233fb618' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-select:valid:focus,.form-select.is-valid:focus{border-color:#3fb618;box-shadow:0 0 0 .25rem rgba(63,182,24,.25)}.was-validated .form-check-input:valid,.form-check-input.is-valid{border-color:#3fb618}.was-validated .form-check-input:valid:checked,.form-check-input.is-valid:checked{background-color:#3fb618}.was-validated .form-check-input:valid:focus,.form-check-input.is-valid:focus{box-shadow:0 0 0 .25rem rgba(63,182,24,.25)}.was-validated .form-check-input:valid~.form-check-label,.form-check-input.is-valid~.form-check-label{color:#3fb618}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.was-validated .input-group .form-control:valid,.input-group .form-control.is-valid,.was-validated .input-group .form-select:valid,.input-group .form-select.is-valid{z-index:1}.was-validated .input-group .form-control:valid:focus,.input-group .form-control.is-valid:focus,.was-validated .input-group .form-select:valid:focus,.input-group .form-select.is-valid:focus{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:0.875em;color:#ff0039}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:0.875rem;color:#fff;background-color:rgba(255,0,57,.9)}.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip,.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip{display:block}.was-validated .form-control:invalid,.form-control.is-invalid{border-color:#ff0039;padding-right:calc(1.5em + 0.75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23ff0039'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23ff0039' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(0.375em + 0.1875rem) center;background-size:calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-control:invalid:focus,.form-control.is-invalid:focus{border-color:#ff0039;box-shadow:0 0 0 .25rem rgba(255,0,57,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + 0.75rem);background-position:top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem)}.was-validated .form-select:invalid,.form-select.is-invalid{border-color:#ff0039}.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"],.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23373a3c' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23ff0039'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23ff0039' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-select:invalid:focus,.form-select.is-invalid:focus{border-color:#ff0039;box-shadow:0 0 0 .25rem rgba(255,0,57,.25)}.was-validated .form-check-input:invalid,.form-check-input.is-invalid{border-color:#ff0039}.was-validated .form-check-input:invalid:checked,.form-check-input.is-invalid:checked{background-color:#ff0039}.was-validated .form-check-input:invalid:focus,.form-check-input.is-invalid:focus{box-shadow:0 0 0 .25rem rgba(255,0,57,.25)}.was-validated .form-check-input:invalid~.form-check-label,.form-check-input.is-invalid~.form-check-label{color:#ff0039}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.was-validated .input-group .form-control:invalid,.input-group .form-control.is-invalid,.was-validated .input-group .form-select:invalid,.input-group .form-select.is-invalid{z-index:2}.was-validated .input-group .form-control:invalid:focus,.input-group .form-control.is-invalid:focus,.was-validated .input-group .form-select:invalid:focus,.input-group .form-select.is-invalid:focus{z-index:3}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#373a3c;text-align:center;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;vertical-align:middle;cursor:pointer;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.btn{transition:none}}.btn:hover{color:#373a3c}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25)}.btn:disabled,.btn.disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65}.btn-default{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-default:hover{color:#fff;background-color:#2f3133;border-color:#2c2e30}.btn-check:focus+.btn-default,.btn-default:focus{color:#fff;background-color:#2f3133;border-color:#2c2e30;box-shadow:0 0 0 .25rem rgba(85,88,89,.5)}.btn-check:checked+.btn-default,.btn-check:active+.btn-default,.btn-default:active,.btn-default.active,.show>.btn-default.dropdown-toggle{color:#fff;background-color:#2c2e30;border-color:#292c2d}.btn-check:checked+.btn-default:focus,.btn-check:active+.btn-default:focus,.btn-default:active:focus,.btn-default.active:focus,.show>.btn-default.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(85,88,89,.5)}.btn-default:disabled,.btn-default.disabled{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-primary{color:#fff;background-color:#2780e3;border-color:#2780e3}.btn-primary:hover{color:#fff;background-color:#216dc1;border-color:#1f66b6}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#216dc1;border-color:#1f66b6;box-shadow:0 0 0 .25rem rgba(71,147,231,.5)}.btn-check:checked+.btn-primary,.btn-check:active+.btn-primary,.btn-primary:active,.btn-primary.active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#1f66b6;border-color:#1d60aa}.btn-check:checked+.btn-primary:focus,.btn-check:active+.btn-primary:focus,.btn-primary:active:focus,.btn-primary.active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(71,147,231,.5)}.btn-primary:disabled,.btn-primary.disabled{color:#fff;background-color:#2780e3;border-color:#2780e3}.btn-secondary{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-secondary:hover{color:#fff;background-color:#2f3133;border-color:#2c2e30}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#fff;background-color:#2f3133;border-color:#2c2e30;box-shadow:0 0 0 .25rem rgba(85,88,89,.5)}.btn-check:checked+.btn-secondary,.btn-check:active+.btn-secondary,.btn-secondary:active,.btn-secondary.active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#2c2e30;border-color:#292c2d}.btn-check:checked+.btn-secondary:focus,.btn-check:active+.btn-secondary:focus,.btn-secondary:active:focus,.btn-secondary.active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(85,88,89,.5)}.btn-secondary:disabled,.btn-secondary.disabled{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-success{color:#fff;background-color:#3fb618;border-color:#3fb618}.btn-success:hover{color:#fff;background-color:#369b14;border-color:#329213}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#369b14;border-color:#329213;box-shadow:0 0 0 .25rem rgba(92,193,59,.5)}.btn-check:checked+.btn-success,.btn-check:active+.btn-success,.btn-success:active,.btn-success.active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#329213;border-color:#2f8912}.btn-check:checked+.btn-success:focus,.btn-check:active+.btn-success:focus,.btn-success:active:focus,.btn-success.active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(92,193,59,.5)}.btn-success:disabled,.btn-success.disabled{color:#fff;background-color:#3fb618;border-color:#3fb618}.btn-info{color:#fff;background-color:#9954bb;border-color:#9954bb}.btn-info:hover{color:#fff;background-color:#82479f;border-color:#7a4396}.btn-check:focus+.btn-info,.btn-info:focus{color:#fff;background-color:#82479f;border-color:#7a4396;box-shadow:0 0 0 .25rem rgba(168,110,197,.5)}.btn-check:checked+.btn-info,.btn-check:active+.btn-info,.btn-info:active,.btn-info.active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#7a4396;border-color:#733f8c}.btn-check:checked+.btn-info:focus,.btn-check:active+.btn-info:focus,.btn-info:active:focus,.btn-info.active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(168,110,197,.5)}.btn-info:disabled,.btn-info.disabled{color:#fff;background-color:#9954bb;border-color:#9954bb}.btn-warning{color:#fff;background-color:#ff7518;border-color:#ff7518}.btn-warning:hover{color:#fff;background-color:#d96314;border-color:#cc5e13}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#fff;background-color:#d96314;border-color:#cc5e13;box-shadow:0 0 0 .25rem rgba(255,138,59,.5)}.btn-check:checked+.btn-warning,.btn-check:active+.btn-warning,.btn-warning:active,.btn-warning.active,.show>.btn-warning.dropdown-toggle{color:#fff;background-color:#cc5e13;border-color:#bf5812}.btn-check:checked+.btn-warning:focus,.btn-check:active+.btn-warning:focus,.btn-warning:active:focus,.btn-warning.active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(255,138,59,.5)}.btn-warning:disabled,.btn-warning.disabled{color:#fff;background-color:#ff7518;border-color:#ff7518}.btn-danger{color:#fff;background-color:#ff0039;border-color:#ff0039}.btn-danger:hover{color:#fff;background-color:#d90030;border-color:#cc002e}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#d90030;border-color:#cc002e;box-shadow:0 0 0 .25rem rgba(255,38,87,.5)}.btn-check:checked+.btn-danger,.btn-check:active+.btn-danger,.btn-danger:active,.btn-danger.active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#cc002e;border-color:#bf002b}.btn-check:checked+.btn-danger:focus,.btn-check:active+.btn-danger:focus,.btn-danger:active:focus,.btn-danger.active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(255,38,87,.5)}.btn-danger:disabled,.btn-danger.disabled{color:#fff;background-color:#ff0039;border-color:#ff0039}.btn-light{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:focus+.btn-light,.btn-light:focus{color:#000;background-color:#f9fafb;border-color:#f9fafb;box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-check:checked+.btn-light,.btn-check:active+.btn-light,.btn-light:active,.btn-light.active,.show>.btn-light.dropdown-toggle{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:checked+.btn-light:focus,.btn-check:active+.btn-light:focus,.btn-light:active:focus,.btn-light.active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-light:disabled,.btn-light.disabled{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-dark{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-dark:hover{color:#fff;background-color:#2f3133;border-color:#2c2e30}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#2f3133;border-color:#2c2e30;box-shadow:0 0 0 .25rem rgba(85,88,89,.5)}.btn-check:checked+.btn-dark,.btn-check:active+.btn-dark,.btn-dark:active,.btn-dark.active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#2c2e30;border-color:#292c2d}.btn-check:checked+.btn-dark:focus,.btn-check:active+.btn-dark:focus,.btn-dark:active:focus,.btn-dark.active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(85,88,89,.5)}.btn-dark:disabled,.btn-dark.disabled{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-outline-default{color:#373a3c;border-color:#373a3c;background-color:transparent}.btn-outline-default:hover{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-check:focus+.btn-outline-default,.btn-outline-default:focus{box-shadow:0 0 0 .25rem rgba(55,58,60,.5)}.btn-check:checked+.btn-outline-default,.btn-check:active+.btn-outline-default,.btn-outline-default:active,.btn-outline-default.active,.btn-outline-default.dropdown-toggle.show{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-check:checked+.btn-outline-default:focus,.btn-check:active+.btn-outline-default:focus,.btn-outline-default:active:focus,.btn-outline-default.active:focus,.btn-outline-default.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(55,58,60,.5)}.btn-outline-default:disabled,.btn-outline-default.disabled{color:#373a3c;background-color:transparent}.btn-outline-primary{color:#2780e3;border-color:#2780e3;background-color:transparent}.btn-outline-primary:hover{color:#fff;background-color:#2780e3;border-color:#2780e3}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(39,128,227,.5)}.btn-check:checked+.btn-outline-primary,.btn-check:active+.btn-outline-primary,.btn-outline-primary:active,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show{color:#fff;background-color:#2780e3;border-color:#2780e3}.btn-check:checked+.btn-outline-primary:focus,.btn-check:active+.btn-outline-primary:focus,.btn-outline-primary:active:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(39,128,227,.5)}.btn-outline-primary:disabled,.btn-outline-primary.disabled{color:#2780e3;background-color:transparent}.btn-outline-secondary{color:#373a3c;border-color:#373a3c;background-color:transparent}.btn-outline-secondary:hover{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(55,58,60,.5)}.btn-check:checked+.btn-outline-secondary,.btn-check:active+.btn-outline-secondary,.btn-outline-secondary:active,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-check:checked+.btn-outline-secondary:focus,.btn-check:active+.btn-outline-secondary:focus,.btn-outline-secondary:active:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(55,58,60,.5)}.btn-outline-secondary:disabled,.btn-outline-secondary.disabled{color:#373a3c;background-color:transparent}.btn-outline-success{color:#3fb618;border-color:#3fb618;background-color:transparent}.btn-outline-success:hover{color:#fff;background-color:#3fb618;border-color:#3fb618}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(63,182,24,.5)}.btn-check:checked+.btn-outline-success,.btn-check:active+.btn-outline-success,.btn-outline-success:active,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show{color:#fff;background-color:#3fb618;border-color:#3fb618}.btn-check:checked+.btn-outline-success:focus,.btn-check:active+.btn-outline-success:focus,.btn-outline-success:active:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(63,182,24,.5)}.btn-outline-success:disabled,.btn-outline-success.disabled{color:#3fb618;background-color:transparent}.btn-outline-info{color:#9954bb;border-color:#9954bb;background-color:transparent}.btn-outline-info:hover{color:#fff;background-color:#9954bb;border-color:#9954bb}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(153,84,187,.5)}.btn-check:checked+.btn-outline-info,.btn-check:active+.btn-outline-info,.btn-outline-info:active,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show{color:#fff;background-color:#9954bb;border-color:#9954bb}.btn-check:checked+.btn-outline-info:focus,.btn-check:active+.btn-outline-info:focus,.btn-outline-info:active:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(153,84,187,.5)}.btn-outline-info:disabled,.btn-outline-info.disabled{color:#9954bb;background-color:transparent}.btn-outline-warning{color:#ff7518;border-color:#ff7518;background-color:transparent}.btn-outline-warning:hover{color:#fff;background-color:#ff7518;border-color:#ff7518}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(255,117,24,.5)}.btn-check:checked+.btn-outline-warning,.btn-check:active+.btn-outline-warning,.btn-outline-warning:active,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show{color:#fff;background-color:#ff7518;border-color:#ff7518}.btn-check:checked+.btn-outline-warning:focus,.btn-check:active+.btn-outline-warning:focus,.btn-outline-warning:active:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(255,117,24,.5)}.btn-outline-warning:disabled,.btn-outline-warning.disabled{color:#ff7518;background-color:transparent}.btn-outline-danger{color:#ff0039;border-color:#ff0039;background-color:transparent}.btn-outline-danger:hover{color:#fff;background-color:#ff0039;border-color:#ff0039}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(255,0,57,.5)}.btn-check:checked+.btn-outline-danger,.btn-check:active+.btn-outline-danger,.btn-outline-danger:active,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show{color:#fff;background-color:#ff0039;border-color:#ff0039}.btn-check:checked+.btn-outline-danger:focus,.btn-check:active+.btn-outline-danger:focus,.btn-outline-danger:active:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(255,0,57,.5)}.btn-outline-danger:disabled,.btn-outline-danger.disabled{color:#ff0039;background-color:transparent}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa;background-color:transparent}.btn-outline-light:hover{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-check:checked+.btn-outline-light,.btn-check:active+.btn-outline-light,.btn-outline-light:active,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:checked+.btn-outline-light:focus,.btn-check:active+.btn-outline-light:focus,.btn-outline-light:active:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-outline-light:disabled,.btn-outline-light.disabled{color:#f8f9fa;background-color:transparent}.btn-outline-dark{color:#373a3c;border-color:#373a3c;background-color:transparent}.btn-outline-dark:hover{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(55,58,60,.5)}.btn-check:checked+.btn-outline-dark,.btn-check:active+.btn-outline-dark,.btn-outline-dark:active,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-check:checked+.btn-outline-dark:focus,.btn-check:active+.btn-outline-dark:focus,.btn-outline-dark:active:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(55,58,60,.5)}.btn-outline-dark:disabled,.btn-outline-dark.disabled{color:#373a3c;background-color:transparent}.btn-link{font-weight:400;color:#2780e3;text-decoration:underline;-webkit-text-decoration:underline;-moz-text-decoration:underline;-ms-text-decoration:underline;-o-text-decoration:underline}.btn-link:hover{color:#1f66b6}.btn-link:disabled,.btn-link.disabled{color:#6c757d}.btn-lg,.btn-group-lg>.btn{padding:.5rem 1rem;font-size:1.25rem;border-radius:0}.btn-sm,.btn-group-sm>.btn{padding:.25rem .5rem;font-size:0.875rem;border-radius:0}.fade{transition:opacity .15s linear}@media(prefers-reduced-motion: reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .2s ease}@media(prefers-reduced-motion: reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media(prefers-reduced-motion: reduce){.collapsing.collapse-horizontal{transition:none}}.dropup,.dropend,.dropdown,.dropstart{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:#373a3c;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15)}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position: start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position: end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media(min-width: 576px){.dropdown-menu-sm-start{--bs-position: start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position: end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 768px){.dropdown-menu-md-start{--bs-position: start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position: end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 992px){.dropdown-menu-lg-start{--bs-position: start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position: end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 1200px){.dropdown-menu-xl-start{--bs-position: start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position: end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 1400px){.dropdown-menu-xxl-start{--bs-position: start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position: end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid rgba(0,0,0,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#212529;text-align:inherit;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:hover,.dropdown-item:focus{color:#1e2125;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#2780e3}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:0.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#212529}.dropdown-menu-dark{color:#dee2e6;background-color:#373a3c;border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:hover,.dropdown-menu-dark .dropdown-item:focus{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#fff;background-color:#2780e3}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto}.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn:hover,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:1}.btn-toolbar{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;justify-content:flex-start;-webkit-justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn:not(:first-child),.btn-group>.btn-group:not(:first-child){margin-left:-1px}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-sm+.dropdown-toggle-split,.btn-group-sm>.btn+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-lg+.dropdown-toggle-split,.btn-group-lg>.btn+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;-webkit-flex-direction:column;align-items:flex-start;-webkit-align-items:flex-start;justify-content:center;-webkit-justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child){margin-top:-1px}.nav{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem;color:#2780e3;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media(prefers-reduced-motion: reduce){.nav-link{transition:none}}.nav-link:hover,.nav-link:focus{color:#1f66b6}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background:none;border:1px solid transparent}.nav-tabs .nav-link:hover,.nav-tabs .nav-link:focus{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-link.active,.nav-tabs .nav-item.show .nav-link{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px}.nav-pills .nav-link{background:none;border:0}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#2780e3}.nav-fill>.nav-link,.nav-fill .nav-item{flex:1 1 auto;-webkit-flex:1 1 auto;text-align:center}.nav-justified>.nav-link,.nav-justified .nav-item{flex-basis:0;-webkit-flex-basis:0;flex-grow:1;-webkit-flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding-top:.5rem;padding-bottom:.5rem}.navbar>.container-xxl,.navbar>.container-xl,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container,.navbar>.container-fluid{display:flex;display:-webkit-flex;flex-wrap:inherit;-webkit-flex-wrap:inherit;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;white-space:nowrap}.navbar-nav{display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;-webkit-flex-basis:100%;flex-grow:1;-webkit-flex-grow:1;align-items:center;-webkit-align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;transition:box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height, 75vh);overflow-y:auto}@media(min-width: 576px){.navbar-expand-sm{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas-header{display:none}.navbar-expand-sm .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-sm .offcanvas-top,.navbar-expand-sm .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-sm .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 768px){.navbar-expand-md{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas-header{display:none}.navbar-expand-md .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-md .offcanvas-top,.navbar-expand-md .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-md .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 992px){.navbar-expand-lg{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas-header{display:none}.navbar-expand-lg .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-lg .offcanvas-top,.navbar-expand-lg .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-lg .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 1200px){.navbar-expand-xl{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas-header{display:none}.navbar-expand-xl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xl .offcanvas-top,.navbar-expand-xl .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-xl .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 1400px){.navbar-expand-xxl{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xxl .offcanvas-top,.navbar-expand-xxl .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-xxl .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas-header{display:none}.navbar-expand .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand .offcanvas-top,.navbar-expand .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}.navbar-light{background-color:#2780e3}.navbar-light .navbar-brand{color:#fdfeff}.navbar-light .navbar-brand:hover,.navbar-light .navbar-brand:focus{color:#fdfeff}.navbar-light .navbar-nav .nav-link{color:#fdfeff}.navbar-light .navbar-nav .nav-link:hover,.navbar-light .navbar-nav .nav-link:focus{color:rgba(253,254,255,.8)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(253,254,255,.75)}.navbar-light .navbar-nav .show>.nav-link,.navbar-light .navbar-nav .nav-link.active{color:#fdfeff}.navbar-light .navbar-toggler{color:#fdfeff;border-color:rgba(253,254,255,.4)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23fdfeff' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:#fdfeff}.navbar-light .navbar-text a,.navbar-light .navbar-text a:hover,.navbar-light .navbar-text a:focus{color:#fdfeff}.navbar-dark{background-color:#2780e3}.navbar-dark .navbar-brand{color:#fdfeff}.navbar-dark .navbar-brand:hover,.navbar-dark .navbar-brand:focus{color:#fdfeff}.navbar-dark .navbar-nav .nav-link{color:#fdfeff}.navbar-dark .navbar-nav .nav-link:hover,.navbar-dark .navbar-nav .nav-link:focus{color:rgba(253,254,255,.8)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(253,254,255,.75)}.navbar-dark .navbar-nav .show>.nav-link,.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active{color:#fdfeff}.navbar-dark .navbar-toggler{color:#fdfeff;border-color:rgba(253,254,255,.4)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23fdfeff' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:#fdfeff}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:hover,.navbar-dark .navbar-text a:focus{color:#fdfeff}.card{position:relative;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125)}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0}.card>.list-group:last-child{border-bottom-width:0}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;-webkit-flex:1 1 auto;padding:1rem 1rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-0.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;background-color:#adb5bd;border-bottom:1px solid rgba(0,0,0,.125)}.card-footer{padding:.5rem 1rem;background-color:#adb5bd;border-top:1px solid rgba(0,0,0,.125)}.card-header-tabs{margin-right:-0.5rem;margin-bottom:-0.5rem;margin-left:-0.5rem;border-bottom:0}.card-header-pills{margin-right:-0.5rem;margin-left:-0.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem}.card-img,.card-img-top,.card-img-bottom{width:100%}.card-group>.card{margin-bottom:.75rem}@media(min-width: 576px){.card-group{display:flex;display:-webkit-flex;flex-flow:row wrap;-webkit-flex-flow:row wrap}.card-group>.card{flex:1 0 0%;-webkit-flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}}.accordion-button{position:relative;display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#373a3c;text-align:left;background-color:#fff;border:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media(prefers-reduced-motion: reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#2373cc;background-color:#e9f2fc;box-shadow:inset 0 -1px 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%232373cc'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");transform:rotate(-180deg)}.accordion-button::after{flex-shrink:0;-webkit-flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23373a3c'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:1.25rem;transition:transform .2s ease-in-out}@media(prefers-reduced-motion: reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#93c0f1;outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25)}.accordion-header{margin-bottom:0}.accordion-item{background-color:#fff;border:1px solid rgba(0,0,0,.125)}.accordion-item:not(:first-of-type){border-top:0}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.breadcrumb{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;padding:0 0;margin-bottom:1rem;list-style:none}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, "/") /* rtl: var(--bs-breadcrumb-divider, "/") */}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;display:-webkit-flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#2780e3;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;background-color:#fff;border:1px solid #dee2e6;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:#1f66b6;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;color:#1f66b6;background-color:#e9ecef;outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#fff;background-color:#2780e3;border-color:#2780e3}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:0.875rem}.badge{display:inline-block;padding:.35em .65em;font-size:0.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:0 solid transparent}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-default{color:#212324;background-color:#d7d8d8;border-color:#c3c4c5}.alert-default .alert-link{color:#1a1c1d}.alert-primary{color:#174d88;background-color:#d4e6f9;border-color:#bed9f7}.alert-primary .alert-link{color:#123e6d}.alert-secondary{color:#212324;background-color:#d7d8d8;border-color:#c3c4c5}.alert-secondary .alert-link{color:#1a1c1d}.alert-success{color:#266d0e;background-color:#d9f0d1;border-color:#c5e9ba}.alert-success .alert-link{color:#1e570b}.alert-info{color:#5c3270;background-color:#ebddf1;border-color:#e0cceb}.alert-info .alert-link{color:#4a285a}.alert-warning{color:#99460e;background-color:#ffe3d1;border-color:#ffd6ba}.alert-warning .alert-link{color:#7a380b}.alert-danger{color:#902;background-color:#ffccd7;border-color:#ffb3c4}.alert-danger .alert-link{color:#7a001b}.alert-light{color:#959596;background-color:#fefefe;border-color:#fdfdfe}.alert-light .alert-link{color:#777778}.alert-dark{color:#212324;background-color:#d7d8d8;border-color:#c3c4c5}.alert-dark .alert-link{color:#1a1c1d}@keyframes progress-bar-stripes{0%{background-position-x:.5rem}}.progress{display:flex;display:-webkit-flex;height:.5rem;overflow:hidden;font-size:0.75rem;background-color:#e9ecef}.progress-bar{display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;justify-content:center;-webkit-justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#2780e3;transition:width .6s ease}@media(prefers-reduced-motion: reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-size:.5rem .5rem}.progress-bar-animated{animation:1s linear infinite progress-bar-stripes}@media(prefers-reduced-motion: reduce){.progress-bar-animated{animation:none}}.list-group{display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;padding-left:0;margin-bottom:0}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:hover,.list-group-item-action:focus{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#373a3c;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#212529;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#2780e3;border-color:#2780e3}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media(min-width: 576px){.list-group-horizontal-sm{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 768px){.list-group-horizontal-md{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 992px){.list-group-horizontal-lg{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 1200px){.list-group-horizontal-xl{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 1400px){.list-group-horizontal-xxl{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-default{color:#212324;background-color:#d7d8d8}.list-group-item-default.list-group-item-action:hover,.list-group-item-default.list-group-item-action:focus{color:#212324;background-color:#c2c2c2}.list-group-item-default.list-group-item-action.active{color:#fff;background-color:#212324;border-color:#212324}.list-group-item-primary{color:#174d88;background-color:#d4e6f9}.list-group-item-primary.list-group-item-action:hover,.list-group-item-primary.list-group-item-action:focus{color:#174d88;background-color:#bfcfe0}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#174d88;border-color:#174d88}.list-group-item-secondary{color:#212324;background-color:#d7d8d8}.list-group-item-secondary.list-group-item-action:hover,.list-group-item-secondary.list-group-item-action:focus{color:#212324;background-color:#c2c2c2}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#212324;border-color:#212324}.list-group-item-success{color:#266d0e;background-color:#d9f0d1}.list-group-item-success.list-group-item-action:hover,.list-group-item-success.list-group-item-action:focus{color:#266d0e;background-color:#c3d8bc}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#266d0e;border-color:#266d0e}.list-group-item-info{color:#5c3270;background-color:#ebddf1}.list-group-item-info.list-group-item-action:hover,.list-group-item-info.list-group-item-action:focus{color:#5c3270;background-color:#d4c7d9}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#5c3270;border-color:#5c3270}.list-group-item-warning{color:#99460e;background-color:#ffe3d1}.list-group-item-warning.list-group-item-action:hover,.list-group-item-warning.list-group-item-action:focus{color:#99460e;background-color:#e6ccbc}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#99460e;border-color:#99460e}.list-group-item-danger{color:#902;background-color:#ffccd7}.list-group-item-danger.list-group-item-action:hover,.list-group-item-danger.list-group-item-action:focus{color:#902;background-color:#e6b8c2}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#902;border-color:#902}.list-group-item-light{color:#959596;background-color:#fefefe}.list-group-item-light.list-group-item-action:hover,.list-group-item-light.list-group-item-action:focus{color:#959596;background-color:#e5e5e5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#959596;border-color:#959596}.list-group-item-dark{color:#212324;background-color:#d7d8d8}.list-group-item-dark.list-group-item-action:hover,.list-group-item-dark.list-group-item-action:focus{color:#212324;background-color:#c2c2c2}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#212324;border-color:#212324}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#000;background:transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;opacity:.5}.btn-close:hover{color:#000;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25);opacity:1}.btn-close:disabled,.btn-close.disabled{pointer-events:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;opacity:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:0.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(0,0,0,.15)}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{width:max-content;width:-webkit-max-content;width:-moz-max-content;width:-ms-max-content;width:-o-max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:.75rem}.toast-header{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;padding:.5rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05)}.toast-header .btn-close{margin-right:-0.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal{position:fixed;top:0;left:0;z-index:1055;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0, -50px)}@media(prefers-reduced-motion: reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;min-height:calc(100% - 1rem)}.modal-content{position:relative;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1050;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;display:-webkit-flex;flex-shrink:0;-webkit-flex-shrink:0;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6}.modal-header .btn-close{padding:.5rem .5rem;margin:-0.5rem -0.5rem -0.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto;padding:1rem}.modal-footer{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;flex-shrink:0;-webkit-flex-shrink:0;align-items:center;-webkit-align-items:center;justify-content:flex-end;-webkit-justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6}.modal-footer>*{margin:.25rem}@media(min-width: 576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-sm{max-width:300px}}@media(min-width: 992px){.modal-lg,.modal-xl{max-width:800px}}@media(min-width: 1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0}.modal-fullscreen .modal-body{overflow-y:auto}@media(max-width: 575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}}@media(max-width: 767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}}@media(max-width: 991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}}@media(max-width: 1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}}@media(max-width: 1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:0.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-top,.bs-tooltip-auto[data-popper-placement^=top]{padding:.4rem 0}.bs-tooltip-top .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow{bottom:0}.bs-tooltip-top .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-end,.bs-tooltip-auto[data-popper-placement^=right]{padding:0 .4rem}.bs-tooltip-end .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-end .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-bottom,.bs-tooltip-auto[data-popper-placement^=bottom]{padding:.4rem 0}.bs-tooltip-bottom .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow{top:0}.bs-tooltip-bottom .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-start,.bs-tooltip-auto[data-popper-placement^=left]{padding:0 .4rem}.bs-tooltip-start .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-start .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000}.popover{position:absolute;top:0;left:0 /* rtl:ignore */;z-index:1070;display:block;max-width:276px;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:0.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2)}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::before,.popover .popover-arrow::after{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-top>.popover-arrow,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow{bottom:calc(-0.5rem - 1px)}.bs-popover-top>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-top>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-end>.popover-arrow,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow{left:calc(-0.5rem - 1px);width:.5rem;height:1rem}.bs-popover-end>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-end>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-bottom>.popover-arrow,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow{top:calc(-0.5rem - 1px)}.bs-popover-bottom>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-bottom>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-bottom .popover-header::before,.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-0.5rem;content:"";border-bottom:1px solid #f0f0f0}.bs-popover-start>.popover-arrow,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow{right:calc(-0.5rem - 1px);width:.5rem;height:1rem}.bs-popover-start>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-start>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;background-color:#f0f0f0;border-bottom:1px solid rgba(0,0,0,.2)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#373a3c}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y;-webkit-touch-action:pan-y;-moz-touch-action:pan-y;-ms-touch-action:pan-y;-o-touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;backface-visibility:hidden;-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;-ms-backface-visibility:hidden;-o-backface-visibility:hidden;transition:transform .6s ease-in-out}@media(prefers-reduced-motion: reduce){.carousel-item{transition:none}}.carousel-item.active,.carousel-item-next,.carousel-item-prev{display:block}.carousel-item-next:not(.carousel-item-start),.active.carousel-item-end{transform:translateX(100%)}.carousel-item-prev:not(.carousel-item-end),.active.carousel-item-start{transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item.active,.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end{z-index:1;opacity:1}.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{z-index:0;opacity:0;transition:opacity 0s .6s}@media(prefers-reduced-motion: reduce){.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{transition:none}}.carousel-control-prev,.carousel-control-next{position:absolute;top:0;bottom:0;z-index:1;display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;justify-content:center;-webkit-justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:none;border:0;opacity:.5;transition:opacity .15s ease}@media(prefers-reduced-motion: reduce){.carousel-control-prev,.carousel-control-next{transition:none}}.carousel-control-prev:hover,.carousel-control-prev:focus,.carousel-control-next:hover,.carousel-control-next:focus{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-prev-icon,.carousel-control-next-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;display:-webkit-flex;justify-content:center;-webkit-justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;-webkit-flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media(prefers-reduced-motion: reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-prev-icon,.carousel-dark .carousel-control-next-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@keyframes spinner-border{to{transform:rotate(360deg) /* rtl:ignore */}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-0.125em;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-0.125em;background-color:currentColor;border-radius:50%;opacity:0;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media(prefers-reduced-motion: reduce){.spinner-border,.spinner-grow{animation-duration:1.5s;-webkit-animation-duration:1.5s;-moz-animation-duration:1.5s;-ms-animation-duration:1.5s;-o-animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1045;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media(prefers-reduced-motion: reduce){.offcanvas{transition:none}}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin-top:-0.5rem;margin-right:-0.5rem;margin-bottom:-0.5rem}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;-webkit-flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid rgba(0,0,0,.2);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid rgba(0,0,0,.2);transform:translateX(100%)}.offcanvas-top{top:0;right:0;left:0;height:30vh;max-height:100%;border-bottom:1px solid rgba(0,0,0,.2);transform:translateY(-100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid rgba(0,0,0,.2);transform:translateY(100%)}.offcanvas.show{transform:none}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentColor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{animation:placeholder-glow 2s ease-in-out infinite}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{mask-image:linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);-webkit-mask-image:linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);mask-size:200% 100%;-webkit-mask-size:200% 100%;animation:placeholder-wave 2s linear infinite}@keyframes placeholder-wave{100%{mask-position:-200% 0%;-webkit-mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.link-default{color:#373a3c}.link-default:hover,.link-default:focus{color:#2c2e30}.link-primary{color:#2780e3}.link-primary:hover,.link-primary:focus{color:#1f66b6}.link-secondary{color:#373a3c}.link-secondary:hover,.link-secondary:focus{color:#2c2e30}.link-success{color:#3fb618}.link-success:hover,.link-success:focus{color:#329213}.link-info{color:#9954bb}.link-info:hover,.link-info:focus{color:#7a4396}.link-warning{color:#ff7518}.link-warning:hover,.link-warning:focus{color:#cc5e13}.link-danger{color:#ff0039}.link-danger:hover,.link-danger:focus{color:#cc002e}.link-light{color:#f8f9fa}.link-light:hover,.link-light:focus{color:#f9fafb}.link-dark{color:#373a3c}.link-dark:hover,.link-dark:focus{color:#2c2e30}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio: 100%}.ratio-4x3{--bs-aspect-ratio: calc(3 / 4 * 100%)}.ratio-16x9{--bs-aspect-ratio: calc(9 / 16 * 100%)}.ratio-21x9{--bs-aspect-ratio: calc(9 / 21 * 100%)}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:sticky;top:0;z-index:1020}@media(min-width: 576px){.sticky-sm-top{position:sticky;top:0;z-index:1020}}@media(min-width: 768px){.sticky-md-top{position:sticky;top:0;z-index:1020}}@media(min-width: 992px){.sticky-lg-top{position:sticky;top:0;z-index:1020}}@media(min-width: 1200px){.sticky-xl-top{position:sticky;top:0;z-index:1020}}@media(min-width: 1400px){.sticky-xxl-top{position:sticky;top:0;z-index:1020}}.hstack{display:flex;display:-webkit-flex;flex-direction:row;-webkit-flex-direction:row;align-items:center;-webkit-align-items:center;align-self:stretch;-webkit-align-self:stretch}.vstack{display:flex;display:-webkit-flex;flex:1 1 auto;-webkit-flex:1 1 auto;flex-direction:column;-webkit-flex-direction:column;align-self:stretch;-webkit-align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute !important;width:1px !important;height:1px !important;padding:0 !important;margin:-1px !important;overflow:hidden !important;clip:rect(0, 0, 0, 0) !important;white-space:nowrap !important;border:0 !important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;-webkit-align-self:stretch;width:1px;min-height:1em;background-color:currentColor;opacity:.25}.align-baseline{vertical-align:baseline !important}.align-top{vertical-align:top !important}.align-middle{vertical-align:middle !important}.align-bottom{vertical-align:bottom !important}.align-text-bottom{vertical-align:text-bottom !important}.align-text-top{vertical-align:text-top !important}.float-start{float:left !important}.float-end{float:right !important}.float-none{float:none !important}.opacity-0{opacity:0 !important}.opacity-25{opacity:.25 !important}.opacity-50{opacity:.5 !important}.opacity-75{opacity:.75 !important}.opacity-100{opacity:1 !important}.overflow-auto{overflow:auto !important}.overflow-hidden{overflow:hidden !important}.overflow-visible{overflow:visible !important}.overflow-scroll{overflow:scroll !important}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.d-block{display:block !important}.d-grid{display:grid !important}.d-table{display:table !important}.d-table-row{display:table-row !important}.d-table-cell{display:table-cell !important}.d-flex{display:flex !important}.d-inline-flex{display:inline-flex !important}.d-none{display:none !important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15) !important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075) !important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175) !important}.shadow-none{box-shadow:none !important}.position-static{position:static !important}.position-relative{position:relative !important}.position-absolute{position:absolute !important}.position-fixed{position:fixed !important}.position-sticky{position:sticky !important}.top-0{top:0 !important}.top-50{top:50% !important}.top-100{top:100% !important}.bottom-0{bottom:0 !important}.bottom-50{bottom:50% !important}.bottom-100{bottom:100% !important}.start-0{left:0 !important}.start-50{left:50% !important}.start-100{left:100% !important}.end-0{right:0 !important}.end-50{right:50% !important}.end-100{right:100% !important}.translate-middle{transform:translate(-50%, -50%) !important}.translate-middle-x{transform:translateX(-50%) !important}.translate-middle-y{transform:translateY(-50%) !important}.border{border:1px solid #dee2e6 !important}.border-0{border:0 !important}.border-top{border-top:1px solid #dee2e6 !important}.border-top-0{border-top:0 !important}.border-end{border-right:1px solid #dee2e6 !important}.border-end-0{border-right:0 !important}.border-bottom{border-bottom:1px solid #dee2e6 !important}.border-bottom-0{border-bottom:0 !important}.border-start{border-left:1px solid #dee2e6 !important}.border-start-0{border-left:0 !important}.border-default{border-color:#373a3c !important}.border-primary{border-color:#2780e3 !important}.border-secondary{border-color:#373a3c !important}.border-success{border-color:#3fb618 !important}.border-info{border-color:#9954bb !important}.border-warning{border-color:#ff7518 !important}.border-danger{border-color:#ff0039 !important}.border-light{border-color:#f8f9fa !important}.border-dark{border-color:#373a3c !important}.border-white{border-color:#fff !important}.border-1{border-width:1px !important}.border-2{border-width:2px !important}.border-3{border-width:3px !important}.border-4{border-width:4px !important}.border-5{border-width:5px !important}.w-25{width:25% !important}.w-50{width:50% !important}.w-75{width:75% !important}.w-100{width:100% !important}.w-auto{width:auto !important}.mw-100{max-width:100% !important}.vw-100{width:100vw !important}.min-vw-100{min-width:100vw !important}.h-25{height:25% !important}.h-50{height:50% !important}.h-75{height:75% !important}.h-100{height:100% !important}.h-auto{height:auto !important}.mh-100{max-height:100% !important}.vh-100{height:100vh !important}.min-vh-100{min-height:100vh !important}.flex-fill{flex:1 1 auto !important}.flex-row{flex-direction:row !important}.flex-column{flex-direction:column !important}.flex-row-reverse{flex-direction:row-reverse !important}.flex-column-reverse{flex-direction:column-reverse !important}.flex-grow-0{flex-grow:0 !important}.flex-grow-1{flex-grow:1 !important}.flex-shrink-0{flex-shrink:0 !important}.flex-shrink-1{flex-shrink:1 !important}.flex-wrap{flex-wrap:wrap !important}.flex-nowrap{flex-wrap:nowrap !important}.flex-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-0{gap:0 !important}.gap-1{gap:.25rem !important}.gap-2{gap:.5rem !important}.gap-3{gap:1rem !important}.gap-4{gap:1.5rem !important}.gap-5{gap:3rem !important}.justify-content-start{justify-content:flex-start !important}.justify-content-end{justify-content:flex-end !important}.justify-content-center{justify-content:center !important}.justify-content-between{justify-content:space-between !important}.justify-content-around{justify-content:space-around !important}.justify-content-evenly{justify-content:space-evenly !important}.align-items-start{align-items:flex-start !important}.align-items-end{align-items:flex-end !important}.align-items-center{align-items:center !important}.align-items-baseline{align-items:baseline !important}.align-items-stretch{align-items:stretch !important}.align-content-start{align-content:flex-start !important}.align-content-end{align-content:flex-end !important}.align-content-center{align-content:center !important}.align-content-between{align-content:space-between !important}.align-content-around{align-content:space-around !important}.align-content-stretch{align-content:stretch !important}.align-self-auto{align-self:auto !important}.align-self-start{align-self:flex-start !important}.align-self-end{align-self:flex-end !important}.align-self-center{align-self:center !important}.align-self-baseline{align-self:baseline !important}.align-self-stretch{align-self:stretch !important}.order-first{order:-1 !important}.order-0{order:0 !important}.order-1{order:1 !important}.order-2{order:2 !important}.order-3{order:3 !important}.order-4{order:4 !important}.order-5{order:5 !important}.order-last{order:6 !important}.m-0{margin:0 !important}.m-1{margin:.25rem !important}.m-2{margin:.5rem !important}.m-3{margin:1rem !important}.m-4{margin:1.5rem !important}.m-5{margin:3rem !important}.m-auto{margin:auto !important}.mx-0{margin-right:0 !important;margin-left:0 !important}.mx-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-3{margin-right:1rem !important;margin-left:1rem !important}.mx-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-5{margin-right:3rem !important;margin-left:3rem !important}.mx-auto{margin-right:auto !important;margin-left:auto !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-0{margin-top:0 !important}.mt-1{margin-top:.25rem !important}.mt-2{margin-top:.5rem !important}.mt-3{margin-top:1rem !important}.mt-4{margin-top:1.5rem !important}.mt-5{margin-top:3rem !important}.mt-auto{margin-top:auto !important}.me-0{margin-right:0 !important}.me-1{margin-right:.25rem !important}.me-2{margin-right:.5rem !important}.me-3{margin-right:1rem !important}.me-4{margin-right:1.5rem !important}.me-5{margin-right:3rem !important}.me-auto{margin-right:auto !important}.mb-0{margin-bottom:0 !important}.mb-1{margin-bottom:.25rem !important}.mb-2{margin-bottom:.5rem !important}.mb-3{margin-bottom:1rem !important}.mb-4{margin-bottom:1.5rem !important}.mb-5{margin-bottom:3rem !important}.mb-auto{margin-bottom:auto !important}.ms-0{margin-left:0 !important}.ms-1{margin-left:.25rem !important}.ms-2{margin-left:.5rem !important}.ms-3{margin-left:1rem !important}.ms-4{margin-left:1.5rem !important}.ms-5{margin-left:3rem !important}.ms-auto{margin-left:auto !important}.p-0{padding:0 !important}.p-1{padding:.25rem !important}.p-2{padding:.5rem !important}.p-3{padding:1rem !important}.p-4{padding:1.5rem !important}.p-5{padding:3rem !important}.px-0{padding-right:0 !important;padding-left:0 !important}.px-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-3{padding-right:1rem !important;padding-left:1rem !important}.px-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-5{padding-right:3rem !important;padding-left:3rem !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-0{padding-top:0 !important}.pt-1{padding-top:.25rem !important}.pt-2{padding-top:.5rem !important}.pt-3{padding-top:1rem !important}.pt-4{padding-top:1.5rem !important}.pt-5{padding-top:3rem !important}.pe-0{padding-right:0 !important}.pe-1{padding-right:.25rem !important}.pe-2{padding-right:.5rem !important}.pe-3{padding-right:1rem !important}.pe-4{padding-right:1.5rem !important}.pe-5{padding-right:3rem !important}.pb-0{padding-bottom:0 !important}.pb-1{padding-bottom:.25rem !important}.pb-2{padding-bottom:.5rem !important}.pb-3{padding-bottom:1rem !important}.pb-4{padding-bottom:1.5rem !important}.pb-5{padding-bottom:3rem !important}.ps-0{padding-left:0 !important}.ps-1{padding-left:.25rem !important}.ps-2{padding-left:.5rem !important}.ps-3{padding-left:1rem !important}.ps-4{padding-left:1.5rem !important}.ps-5{padding-left:3rem !important}.font-monospace{font-family:var(--bs-font-monospace) !important}.fs-1{font-size:calc(1.345rem + 1.14vw) !important}.fs-2{font-size:calc(1.3rem + 0.6vw) !important}.fs-3{font-size:calc(1.275rem + 0.3vw) !important}.fs-4{font-size:1.25rem !important}.fs-5{font-size:1.1rem !important}.fs-6{font-size:1rem !important}.fst-italic{font-style:italic !important}.fst-normal{font-style:normal !important}.fw-light{font-weight:300 !important}.fw-lighter{font-weight:lighter !important}.fw-normal{font-weight:400 !important}.fw-bold{font-weight:700 !important}.fw-bolder{font-weight:bolder !important}.lh-1{line-height:1 !important}.lh-sm{line-height:1.25 !important}.lh-base{line-height:1.5 !important}.lh-lg{line-height:2 !important}.text-start{text-align:left !important}.text-end{text-align:right !important}.text-center{text-align:center !important}.text-decoration-none{text-decoration:none !important}.text-decoration-underline{text-decoration:underline !important}.text-decoration-line-through{text-decoration:line-through !important}.text-lowercase{text-transform:lowercase !important}.text-uppercase{text-transform:uppercase !important}.text-capitalize{text-transform:capitalize !important}.text-wrap{white-space:normal !important}.text-nowrap{white-space:nowrap !important}.text-break{word-wrap:break-word !important;word-break:break-word !important}.text-default{--bs-text-opacity: 1;color:rgba(var(--bs-default-rgb), var(--bs-text-opacity)) !important}.text-primary{--bs-text-opacity: 1;color:rgba(var(--bs-primary-rgb), var(--bs-text-opacity)) !important}.text-secondary{--bs-text-opacity: 1;color:rgba(var(--bs-secondary-rgb), var(--bs-text-opacity)) !important}.text-success{--bs-text-opacity: 1;color:rgba(var(--bs-success-rgb), var(--bs-text-opacity)) !important}.text-info{--bs-text-opacity: 1;color:rgba(var(--bs-info-rgb), var(--bs-text-opacity)) !important}.text-warning{--bs-text-opacity: 1;color:rgba(var(--bs-warning-rgb), var(--bs-text-opacity)) !important}.text-danger{--bs-text-opacity: 1;color:rgba(var(--bs-danger-rgb), var(--bs-text-opacity)) !important}.text-light{--bs-text-opacity: 1;color:rgba(var(--bs-light-rgb), var(--bs-text-opacity)) !important}.text-dark{--bs-text-opacity: 1;color:rgba(var(--bs-dark-rgb), var(--bs-text-opacity)) !important}.text-black{--bs-text-opacity: 1;color:rgba(var(--bs-black-rgb), var(--bs-text-opacity)) !important}.text-white{--bs-text-opacity: 1;color:rgba(var(--bs-white-rgb), var(--bs-text-opacity)) !important}.text-body{--bs-text-opacity: 1;color:rgba(var(--bs-body-color-rgb), var(--bs-text-opacity)) !important}.text-muted{--bs-text-opacity: 1;color:#6c757d !important}.text-black-50{--bs-text-opacity: 1;color:rgba(0,0,0,.5) !important}.text-white-50{--bs-text-opacity: 1;color:rgba(255,255,255,.5) !important}.text-reset{--bs-text-opacity: 1;color:inherit !important}.text-opacity-25{--bs-text-opacity: 0.25}.text-opacity-50{--bs-text-opacity: 0.5}.text-opacity-75{--bs-text-opacity: 0.75}.text-opacity-100{--bs-text-opacity: 1}.bg-default{--bs-bg-opacity: 1;background-color:rgba(var(--bs-default-rgb), var(--bs-bg-opacity)) !important}.bg-primary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-primary-rgb), var(--bs-bg-opacity)) !important}.bg-secondary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-secondary-rgb), var(--bs-bg-opacity)) !important}.bg-success{--bs-bg-opacity: 1;background-color:rgba(var(--bs-success-rgb), var(--bs-bg-opacity)) !important}.bg-info{--bs-bg-opacity: 1;background-color:rgba(var(--bs-info-rgb), var(--bs-bg-opacity)) !important}.bg-warning{--bs-bg-opacity: 1;background-color:rgba(var(--bs-warning-rgb), var(--bs-bg-opacity)) !important}.bg-danger{--bs-bg-opacity: 1;background-color:rgba(var(--bs-danger-rgb), var(--bs-bg-opacity)) !important}.bg-light{--bs-bg-opacity: 1;background-color:rgba(var(--bs-light-rgb), var(--bs-bg-opacity)) !important}.bg-dark{--bs-bg-opacity: 1;background-color:rgba(var(--bs-dark-rgb), var(--bs-bg-opacity)) !important}.bg-black{--bs-bg-opacity: 1;background-color:rgba(var(--bs-black-rgb), var(--bs-bg-opacity)) !important}.bg-white{--bs-bg-opacity: 1;background-color:rgba(var(--bs-white-rgb), var(--bs-bg-opacity)) !important}.bg-body{--bs-bg-opacity: 1;background-color:rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important}.bg-transparent{--bs-bg-opacity: 1;background-color:transparent !important}.bg-opacity-10{--bs-bg-opacity: 0.1}.bg-opacity-25{--bs-bg-opacity: 0.25}.bg-opacity-50{--bs-bg-opacity: 0.5}.bg-opacity-75{--bs-bg-opacity: 0.75}.bg-opacity-100{--bs-bg-opacity: 1}.bg-gradient{background-image:var(--bs-gradient) !important}.user-select-all{user-select:all !important}.user-select-auto{user-select:auto !important}.user-select-none{user-select:none !important}.pe-none{pointer-events:none !important}.pe-auto{pointer-events:auto !important}.rounded{border-radius:.25rem !important}.rounded-0{border-radius:0 !important}.rounded-1{border-radius:.2em !important}.rounded-2{border-radius:.25rem !important}.rounded-3{border-radius:.3rem !important}.rounded-circle{border-radius:50% !important}.rounded-pill{border-radius:50rem !important}.rounded-top{border-top-left-radius:.25rem !important;border-top-right-radius:.25rem !important}.rounded-end{border-top-right-radius:.25rem !important;border-bottom-right-radius:.25rem !important}.rounded-bottom{border-bottom-right-radius:.25rem !important;border-bottom-left-radius:.25rem !important}.rounded-start{border-bottom-left-radius:.25rem !important;border-top-left-radius:.25rem !important}.visible{visibility:visible !important}.invisible{visibility:hidden !important}@media(min-width: 576px){.float-sm-start{float:left !important}.float-sm-end{float:right !important}.float-sm-none{float:none !important}.d-sm-inline{display:inline !important}.d-sm-inline-block{display:inline-block !important}.d-sm-block{display:block !important}.d-sm-grid{display:grid !important}.d-sm-table{display:table !important}.d-sm-table-row{display:table-row !important}.d-sm-table-cell{display:table-cell !important}.d-sm-flex{display:flex !important}.d-sm-inline-flex{display:inline-flex !important}.d-sm-none{display:none !important}.flex-sm-fill{flex:1 1 auto !important}.flex-sm-row{flex-direction:row !important}.flex-sm-column{flex-direction:column !important}.flex-sm-row-reverse{flex-direction:row-reverse !important}.flex-sm-column-reverse{flex-direction:column-reverse !important}.flex-sm-grow-0{flex-grow:0 !important}.flex-sm-grow-1{flex-grow:1 !important}.flex-sm-shrink-0{flex-shrink:0 !important}.flex-sm-shrink-1{flex-shrink:1 !important}.flex-sm-wrap{flex-wrap:wrap !important}.flex-sm-nowrap{flex-wrap:nowrap !important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-sm-0{gap:0 !important}.gap-sm-1{gap:.25rem !important}.gap-sm-2{gap:.5rem !important}.gap-sm-3{gap:1rem !important}.gap-sm-4{gap:1.5rem !important}.gap-sm-5{gap:3rem !important}.justify-content-sm-start{justify-content:flex-start !important}.justify-content-sm-end{justify-content:flex-end !important}.justify-content-sm-center{justify-content:center !important}.justify-content-sm-between{justify-content:space-between !important}.justify-content-sm-around{justify-content:space-around !important}.justify-content-sm-evenly{justify-content:space-evenly !important}.align-items-sm-start{align-items:flex-start !important}.align-items-sm-end{align-items:flex-end !important}.align-items-sm-center{align-items:center !important}.align-items-sm-baseline{align-items:baseline !important}.align-items-sm-stretch{align-items:stretch !important}.align-content-sm-start{align-content:flex-start !important}.align-content-sm-end{align-content:flex-end !important}.align-content-sm-center{align-content:center !important}.align-content-sm-between{align-content:space-between !important}.align-content-sm-around{align-content:space-around !important}.align-content-sm-stretch{align-content:stretch !important}.align-self-sm-auto{align-self:auto !important}.align-self-sm-start{align-self:flex-start !important}.align-self-sm-end{align-self:flex-end !important}.align-self-sm-center{align-self:center !important}.align-self-sm-baseline{align-self:baseline !important}.align-self-sm-stretch{align-self:stretch !important}.order-sm-first{order:-1 !important}.order-sm-0{order:0 !important}.order-sm-1{order:1 !important}.order-sm-2{order:2 !important}.order-sm-3{order:3 !important}.order-sm-4{order:4 !important}.order-sm-5{order:5 !important}.order-sm-last{order:6 !important}.m-sm-0{margin:0 !important}.m-sm-1{margin:.25rem !important}.m-sm-2{margin:.5rem !important}.m-sm-3{margin:1rem !important}.m-sm-4{margin:1.5rem !important}.m-sm-5{margin:3rem !important}.m-sm-auto{margin:auto !important}.mx-sm-0{margin-right:0 !important;margin-left:0 !important}.mx-sm-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-sm-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-sm-3{margin-right:1rem !important;margin-left:1rem !important}.mx-sm-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-sm-5{margin-right:3rem !important;margin-left:3rem !important}.mx-sm-auto{margin-right:auto !important;margin-left:auto !important}.my-sm-0{margin-top:0 !important;margin-bottom:0 !important}.my-sm-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-sm-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-sm-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-sm-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-sm-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-sm-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-sm-0{margin-top:0 !important}.mt-sm-1{margin-top:.25rem !important}.mt-sm-2{margin-top:.5rem !important}.mt-sm-3{margin-top:1rem !important}.mt-sm-4{margin-top:1.5rem !important}.mt-sm-5{margin-top:3rem !important}.mt-sm-auto{margin-top:auto !important}.me-sm-0{margin-right:0 !important}.me-sm-1{margin-right:.25rem !important}.me-sm-2{margin-right:.5rem !important}.me-sm-3{margin-right:1rem !important}.me-sm-4{margin-right:1.5rem !important}.me-sm-5{margin-right:3rem !important}.me-sm-auto{margin-right:auto !important}.mb-sm-0{margin-bottom:0 !important}.mb-sm-1{margin-bottom:.25rem !important}.mb-sm-2{margin-bottom:.5rem !important}.mb-sm-3{margin-bottom:1rem !important}.mb-sm-4{margin-bottom:1.5rem !important}.mb-sm-5{margin-bottom:3rem !important}.mb-sm-auto{margin-bottom:auto !important}.ms-sm-0{margin-left:0 !important}.ms-sm-1{margin-left:.25rem !important}.ms-sm-2{margin-left:.5rem !important}.ms-sm-3{margin-left:1rem !important}.ms-sm-4{margin-left:1.5rem !important}.ms-sm-5{margin-left:3rem !important}.ms-sm-auto{margin-left:auto !important}.p-sm-0{padding:0 !important}.p-sm-1{padding:.25rem !important}.p-sm-2{padding:.5rem !important}.p-sm-3{padding:1rem !important}.p-sm-4{padding:1.5rem !important}.p-sm-5{padding:3rem !important}.px-sm-0{padding-right:0 !important;padding-left:0 !important}.px-sm-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-sm-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-sm-3{padding-right:1rem !important;padding-left:1rem !important}.px-sm-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-sm-5{padding-right:3rem !important;padding-left:3rem !important}.py-sm-0{padding-top:0 !important;padding-bottom:0 !important}.py-sm-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-sm-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-sm-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-sm-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-sm-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-sm-0{padding-top:0 !important}.pt-sm-1{padding-top:.25rem !important}.pt-sm-2{padding-top:.5rem !important}.pt-sm-3{padding-top:1rem !important}.pt-sm-4{padding-top:1.5rem !important}.pt-sm-5{padding-top:3rem !important}.pe-sm-0{padding-right:0 !important}.pe-sm-1{padding-right:.25rem !important}.pe-sm-2{padding-right:.5rem !important}.pe-sm-3{padding-right:1rem !important}.pe-sm-4{padding-right:1.5rem !important}.pe-sm-5{padding-right:3rem !important}.pb-sm-0{padding-bottom:0 !important}.pb-sm-1{padding-bottom:.25rem !important}.pb-sm-2{padding-bottom:.5rem !important}.pb-sm-3{padding-bottom:1rem !important}.pb-sm-4{padding-bottom:1.5rem !important}.pb-sm-5{padding-bottom:3rem !important}.ps-sm-0{padding-left:0 !important}.ps-sm-1{padding-left:.25rem !important}.ps-sm-2{padding-left:.5rem !important}.ps-sm-3{padding-left:1rem !important}.ps-sm-4{padding-left:1.5rem !important}.ps-sm-5{padding-left:3rem !important}.text-sm-start{text-align:left !important}.text-sm-end{text-align:right !important}.text-sm-center{text-align:center !important}}@media(min-width: 768px){.float-md-start{float:left !important}.float-md-end{float:right !important}.float-md-none{float:none !important}.d-md-inline{display:inline !important}.d-md-inline-block{display:inline-block !important}.d-md-block{display:block !important}.d-md-grid{display:grid !important}.d-md-table{display:table !important}.d-md-table-row{display:table-row !important}.d-md-table-cell{display:table-cell !important}.d-md-flex{display:flex !important}.d-md-inline-flex{display:inline-flex !important}.d-md-none{display:none !important}.flex-md-fill{flex:1 1 auto !important}.flex-md-row{flex-direction:row !important}.flex-md-column{flex-direction:column !important}.flex-md-row-reverse{flex-direction:row-reverse !important}.flex-md-column-reverse{flex-direction:column-reverse !important}.flex-md-grow-0{flex-grow:0 !important}.flex-md-grow-1{flex-grow:1 !important}.flex-md-shrink-0{flex-shrink:0 !important}.flex-md-shrink-1{flex-shrink:1 !important}.flex-md-wrap{flex-wrap:wrap !important}.flex-md-nowrap{flex-wrap:nowrap !important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-md-0{gap:0 !important}.gap-md-1{gap:.25rem !important}.gap-md-2{gap:.5rem !important}.gap-md-3{gap:1rem !important}.gap-md-4{gap:1.5rem !important}.gap-md-5{gap:3rem !important}.justify-content-md-start{justify-content:flex-start !important}.justify-content-md-end{justify-content:flex-end !important}.justify-content-md-center{justify-content:center !important}.justify-content-md-between{justify-content:space-between !important}.justify-content-md-around{justify-content:space-around !important}.justify-content-md-evenly{justify-content:space-evenly !important}.align-items-md-start{align-items:flex-start !important}.align-items-md-end{align-items:flex-end !important}.align-items-md-center{align-items:center !important}.align-items-md-baseline{align-items:baseline !important}.align-items-md-stretch{align-items:stretch !important}.align-content-md-start{align-content:flex-start !important}.align-content-md-end{align-content:flex-end !important}.align-content-md-center{align-content:center !important}.align-content-md-between{align-content:space-between !important}.align-content-md-around{align-content:space-around !important}.align-content-md-stretch{align-content:stretch !important}.align-self-md-auto{align-self:auto !important}.align-self-md-start{align-self:flex-start !important}.align-self-md-end{align-self:flex-end !important}.align-self-md-center{align-self:center !important}.align-self-md-baseline{align-self:baseline !important}.align-self-md-stretch{align-self:stretch !important}.order-md-first{order:-1 !important}.order-md-0{order:0 !important}.order-md-1{order:1 !important}.order-md-2{order:2 !important}.order-md-3{order:3 !important}.order-md-4{order:4 !important}.order-md-5{order:5 !important}.order-md-last{order:6 !important}.m-md-0{margin:0 !important}.m-md-1{margin:.25rem !important}.m-md-2{margin:.5rem !important}.m-md-3{margin:1rem !important}.m-md-4{margin:1.5rem !important}.m-md-5{margin:3rem !important}.m-md-auto{margin:auto !important}.mx-md-0{margin-right:0 !important;margin-left:0 !important}.mx-md-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-md-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-md-3{margin-right:1rem !important;margin-left:1rem !important}.mx-md-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-md-5{margin-right:3rem !important;margin-left:3rem !important}.mx-md-auto{margin-right:auto !important;margin-left:auto !important}.my-md-0{margin-top:0 !important;margin-bottom:0 !important}.my-md-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-md-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-md-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-md-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-md-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-md-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-md-0{margin-top:0 !important}.mt-md-1{margin-top:.25rem !important}.mt-md-2{margin-top:.5rem !important}.mt-md-3{margin-top:1rem !important}.mt-md-4{margin-top:1.5rem !important}.mt-md-5{margin-top:3rem !important}.mt-md-auto{margin-top:auto !important}.me-md-0{margin-right:0 !important}.me-md-1{margin-right:.25rem !important}.me-md-2{margin-right:.5rem !important}.me-md-3{margin-right:1rem !important}.me-md-4{margin-right:1.5rem !important}.me-md-5{margin-right:3rem !important}.me-md-auto{margin-right:auto !important}.mb-md-0{margin-bottom:0 !important}.mb-md-1{margin-bottom:.25rem !important}.mb-md-2{margin-bottom:.5rem !important}.mb-md-3{margin-bottom:1rem !important}.mb-md-4{margin-bottom:1.5rem !important}.mb-md-5{margin-bottom:3rem !important}.mb-md-auto{margin-bottom:auto !important}.ms-md-0{margin-left:0 !important}.ms-md-1{margin-left:.25rem !important}.ms-md-2{margin-left:.5rem !important}.ms-md-3{margin-left:1rem !important}.ms-md-4{margin-left:1.5rem !important}.ms-md-5{margin-left:3rem !important}.ms-md-auto{margin-left:auto !important}.p-md-0{padding:0 !important}.p-md-1{padding:.25rem !important}.p-md-2{padding:.5rem !important}.p-md-3{padding:1rem !important}.p-md-4{padding:1.5rem !important}.p-md-5{padding:3rem !important}.px-md-0{padding-right:0 !important;padding-left:0 !important}.px-md-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-md-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-md-3{padding-right:1rem !important;padding-left:1rem !important}.px-md-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-md-5{padding-right:3rem !important;padding-left:3rem !important}.py-md-0{padding-top:0 !important;padding-bottom:0 !important}.py-md-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-md-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-md-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-md-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-md-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-md-0{padding-top:0 !important}.pt-md-1{padding-top:.25rem !important}.pt-md-2{padding-top:.5rem !important}.pt-md-3{padding-top:1rem !important}.pt-md-4{padding-top:1.5rem !important}.pt-md-5{padding-top:3rem !important}.pe-md-0{padding-right:0 !important}.pe-md-1{padding-right:.25rem !important}.pe-md-2{padding-right:.5rem !important}.pe-md-3{padding-right:1rem !important}.pe-md-4{padding-right:1.5rem !important}.pe-md-5{padding-right:3rem !important}.pb-md-0{padding-bottom:0 !important}.pb-md-1{padding-bottom:.25rem !important}.pb-md-2{padding-bottom:.5rem !important}.pb-md-3{padding-bottom:1rem !important}.pb-md-4{padding-bottom:1.5rem !important}.pb-md-5{padding-bottom:3rem !important}.ps-md-0{padding-left:0 !important}.ps-md-1{padding-left:.25rem !important}.ps-md-2{padding-left:.5rem !important}.ps-md-3{padding-left:1rem !important}.ps-md-4{padding-left:1.5rem !important}.ps-md-5{padding-left:3rem !important}.text-md-start{text-align:left !important}.text-md-end{text-align:right !important}.text-md-center{text-align:center !important}}@media(min-width: 992px){.float-lg-start{float:left !important}.float-lg-end{float:right !important}.float-lg-none{float:none !important}.d-lg-inline{display:inline !important}.d-lg-inline-block{display:inline-block !important}.d-lg-block{display:block !important}.d-lg-grid{display:grid !important}.d-lg-table{display:table !important}.d-lg-table-row{display:table-row !important}.d-lg-table-cell{display:table-cell !important}.d-lg-flex{display:flex !important}.d-lg-inline-flex{display:inline-flex !important}.d-lg-none{display:none !important}.flex-lg-fill{flex:1 1 auto !important}.flex-lg-row{flex-direction:row !important}.flex-lg-column{flex-direction:column !important}.flex-lg-row-reverse{flex-direction:row-reverse !important}.flex-lg-column-reverse{flex-direction:column-reverse !important}.flex-lg-grow-0{flex-grow:0 !important}.flex-lg-grow-1{flex-grow:1 !important}.flex-lg-shrink-0{flex-shrink:0 !important}.flex-lg-shrink-1{flex-shrink:1 !important}.flex-lg-wrap{flex-wrap:wrap !important}.flex-lg-nowrap{flex-wrap:nowrap !important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-lg-0{gap:0 !important}.gap-lg-1{gap:.25rem !important}.gap-lg-2{gap:.5rem !important}.gap-lg-3{gap:1rem !important}.gap-lg-4{gap:1.5rem !important}.gap-lg-5{gap:3rem !important}.justify-content-lg-start{justify-content:flex-start !important}.justify-content-lg-end{justify-content:flex-end !important}.justify-content-lg-center{justify-content:center !important}.justify-content-lg-between{justify-content:space-between !important}.justify-content-lg-around{justify-content:space-around !important}.justify-content-lg-evenly{justify-content:space-evenly !important}.align-items-lg-start{align-items:flex-start !important}.align-items-lg-end{align-items:flex-end !important}.align-items-lg-center{align-items:center !important}.align-items-lg-baseline{align-items:baseline !important}.align-items-lg-stretch{align-items:stretch !important}.align-content-lg-start{align-content:flex-start !important}.align-content-lg-end{align-content:flex-end !important}.align-content-lg-center{align-content:center !important}.align-content-lg-between{align-content:space-between !important}.align-content-lg-around{align-content:space-around !important}.align-content-lg-stretch{align-content:stretch !important}.align-self-lg-auto{align-self:auto !important}.align-self-lg-start{align-self:flex-start !important}.align-self-lg-end{align-self:flex-end !important}.align-self-lg-center{align-self:center !important}.align-self-lg-baseline{align-self:baseline !important}.align-self-lg-stretch{align-self:stretch !important}.order-lg-first{order:-1 !important}.order-lg-0{order:0 !important}.order-lg-1{order:1 !important}.order-lg-2{order:2 !important}.order-lg-3{order:3 !important}.order-lg-4{order:4 !important}.order-lg-5{order:5 !important}.order-lg-last{order:6 !important}.m-lg-0{margin:0 !important}.m-lg-1{margin:.25rem !important}.m-lg-2{margin:.5rem !important}.m-lg-3{margin:1rem !important}.m-lg-4{margin:1.5rem !important}.m-lg-5{margin:3rem !important}.m-lg-auto{margin:auto !important}.mx-lg-0{margin-right:0 !important;margin-left:0 !important}.mx-lg-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-lg-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-lg-3{margin-right:1rem !important;margin-left:1rem !important}.mx-lg-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-lg-5{margin-right:3rem !important;margin-left:3rem !important}.mx-lg-auto{margin-right:auto !important;margin-left:auto !important}.my-lg-0{margin-top:0 !important;margin-bottom:0 !important}.my-lg-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-lg-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-lg-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-lg-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-lg-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-lg-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-lg-0{margin-top:0 !important}.mt-lg-1{margin-top:.25rem !important}.mt-lg-2{margin-top:.5rem !important}.mt-lg-3{margin-top:1rem !important}.mt-lg-4{margin-top:1.5rem !important}.mt-lg-5{margin-top:3rem !important}.mt-lg-auto{margin-top:auto !important}.me-lg-0{margin-right:0 !important}.me-lg-1{margin-right:.25rem !important}.me-lg-2{margin-right:.5rem !important}.me-lg-3{margin-right:1rem !important}.me-lg-4{margin-right:1.5rem !important}.me-lg-5{margin-right:3rem !important}.me-lg-auto{margin-right:auto !important}.mb-lg-0{margin-bottom:0 !important}.mb-lg-1{margin-bottom:.25rem !important}.mb-lg-2{margin-bottom:.5rem !important}.mb-lg-3{margin-bottom:1rem !important}.mb-lg-4{margin-bottom:1.5rem !important}.mb-lg-5{margin-bottom:3rem !important}.mb-lg-auto{margin-bottom:auto !important}.ms-lg-0{margin-left:0 !important}.ms-lg-1{margin-left:.25rem !important}.ms-lg-2{margin-left:.5rem !important}.ms-lg-3{margin-left:1rem !important}.ms-lg-4{margin-left:1.5rem !important}.ms-lg-5{margin-left:3rem !important}.ms-lg-auto{margin-left:auto !important}.p-lg-0{padding:0 !important}.p-lg-1{padding:.25rem !important}.p-lg-2{padding:.5rem !important}.p-lg-3{padding:1rem !important}.p-lg-4{padding:1.5rem !important}.p-lg-5{padding:3rem !important}.px-lg-0{padding-right:0 !important;padding-left:0 !important}.px-lg-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-lg-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-lg-3{padding-right:1rem !important;padding-left:1rem !important}.px-lg-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-lg-5{padding-right:3rem !important;padding-left:3rem !important}.py-lg-0{padding-top:0 !important;padding-bottom:0 !important}.py-lg-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-lg-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-lg-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-lg-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-lg-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-lg-0{padding-top:0 !important}.pt-lg-1{padding-top:.25rem !important}.pt-lg-2{padding-top:.5rem !important}.pt-lg-3{padding-top:1rem !important}.pt-lg-4{padding-top:1.5rem !important}.pt-lg-5{padding-top:3rem !important}.pe-lg-0{padding-right:0 !important}.pe-lg-1{padding-right:.25rem !important}.pe-lg-2{padding-right:.5rem !important}.pe-lg-3{padding-right:1rem !important}.pe-lg-4{padding-right:1.5rem !important}.pe-lg-5{padding-right:3rem !important}.pb-lg-0{padding-bottom:0 !important}.pb-lg-1{padding-bottom:.25rem !important}.pb-lg-2{padding-bottom:.5rem !important}.pb-lg-3{padding-bottom:1rem !important}.pb-lg-4{padding-bottom:1.5rem !important}.pb-lg-5{padding-bottom:3rem !important}.ps-lg-0{padding-left:0 !important}.ps-lg-1{padding-left:.25rem !important}.ps-lg-2{padding-left:.5rem !important}.ps-lg-3{padding-left:1rem !important}.ps-lg-4{padding-left:1.5rem !important}.ps-lg-5{padding-left:3rem !important}.text-lg-start{text-align:left !important}.text-lg-end{text-align:right !important}.text-lg-center{text-align:center !important}}@media(min-width: 1200px){.float-xl-start{float:left !important}.float-xl-end{float:right !important}.float-xl-none{float:none !important}.d-xl-inline{display:inline !important}.d-xl-inline-block{display:inline-block !important}.d-xl-block{display:block !important}.d-xl-grid{display:grid !important}.d-xl-table{display:table !important}.d-xl-table-row{display:table-row !important}.d-xl-table-cell{display:table-cell !important}.d-xl-flex{display:flex !important}.d-xl-inline-flex{display:inline-flex !important}.d-xl-none{display:none !important}.flex-xl-fill{flex:1 1 auto !important}.flex-xl-row{flex-direction:row !important}.flex-xl-column{flex-direction:column !important}.flex-xl-row-reverse{flex-direction:row-reverse !important}.flex-xl-column-reverse{flex-direction:column-reverse !important}.flex-xl-grow-0{flex-grow:0 !important}.flex-xl-grow-1{flex-grow:1 !important}.flex-xl-shrink-0{flex-shrink:0 !important}.flex-xl-shrink-1{flex-shrink:1 !important}.flex-xl-wrap{flex-wrap:wrap !important}.flex-xl-nowrap{flex-wrap:nowrap !important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-xl-0{gap:0 !important}.gap-xl-1{gap:.25rem !important}.gap-xl-2{gap:.5rem !important}.gap-xl-3{gap:1rem !important}.gap-xl-4{gap:1.5rem !important}.gap-xl-5{gap:3rem !important}.justify-content-xl-start{justify-content:flex-start !important}.justify-content-xl-end{justify-content:flex-end !important}.justify-content-xl-center{justify-content:center !important}.justify-content-xl-between{justify-content:space-between !important}.justify-content-xl-around{justify-content:space-around !important}.justify-content-xl-evenly{justify-content:space-evenly !important}.align-items-xl-start{align-items:flex-start !important}.align-items-xl-end{align-items:flex-end !important}.align-items-xl-center{align-items:center !important}.align-items-xl-baseline{align-items:baseline !important}.align-items-xl-stretch{align-items:stretch !important}.align-content-xl-start{align-content:flex-start !important}.align-content-xl-end{align-content:flex-end !important}.align-content-xl-center{align-content:center !important}.align-content-xl-between{align-content:space-between !important}.align-content-xl-around{align-content:space-around !important}.align-content-xl-stretch{align-content:stretch !important}.align-self-xl-auto{align-self:auto !important}.align-self-xl-start{align-self:flex-start !important}.align-self-xl-end{align-self:flex-end !important}.align-self-xl-center{align-self:center !important}.align-self-xl-baseline{align-self:baseline !important}.align-self-xl-stretch{align-self:stretch !important}.order-xl-first{order:-1 !important}.order-xl-0{order:0 !important}.order-xl-1{order:1 !important}.order-xl-2{order:2 !important}.order-xl-3{order:3 !important}.order-xl-4{order:4 !important}.order-xl-5{order:5 !important}.order-xl-last{order:6 !important}.m-xl-0{margin:0 !important}.m-xl-1{margin:.25rem !important}.m-xl-2{margin:.5rem !important}.m-xl-3{margin:1rem !important}.m-xl-4{margin:1.5rem !important}.m-xl-5{margin:3rem !important}.m-xl-auto{margin:auto !important}.mx-xl-0{margin-right:0 !important;margin-left:0 !important}.mx-xl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xl-auto{margin-right:auto !important;margin-left:auto !important}.my-xl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xl-0{margin-top:0 !important}.mt-xl-1{margin-top:.25rem !important}.mt-xl-2{margin-top:.5rem !important}.mt-xl-3{margin-top:1rem !important}.mt-xl-4{margin-top:1.5rem !important}.mt-xl-5{margin-top:3rem !important}.mt-xl-auto{margin-top:auto !important}.me-xl-0{margin-right:0 !important}.me-xl-1{margin-right:.25rem !important}.me-xl-2{margin-right:.5rem !important}.me-xl-3{margin-right:1rem !important}.me-xl-4{margin-right:1.5rem !important}.me-xl-5{margin-right:3rem !important}.me-xl-auto{margin-right:auto !important}.mb-xl-0{margin-bottom:0 !important}.mb-xl-1{margin-bottom:.25rem !important}.mb-xl-2{margin-bottom:.5rem !important}.mb-xl-3{margin-bottom:1rem !important}.mb-xl-4{margin-bottom:1.5rem !important}.mb-xl-5{margin-bottom:3rem !important}.mb-xl-auto{margin-bottom:auto !important}.ms-xl-0{margin-left:0 !important}.ms-xl-1{margin-left:.25rem !important}.ms-xl-2{margin-left:.5rem !important}.ms-xl-3{margin-left:1rem !important}.ms-xl-4{margin-left:1.5rem !important}.ms-xl-5{margin-left:3rem !important}.ms-xl-auto{margin-left:auto !important}.p-xl-0{padding:0 !important}.p-xl-1{padding:.25rem !important}.p-xl-2{padding:.5rem !important}.p-xl-3{padding:1rem !important}.p-xl-4{padding:1.5rem !important}.p-xl-5{padding:3rem !important}.px-xl-0{padding-right:0 !important;padding-left:0 !important}.px-xl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xl-0{padding-top:0 !important}.pt-xl-1{padding-top:.25rem !important}.pt-xl-2{padding-top:.5rem !important}.pt-xl-3{padding-top:1rem !important}.pt-xl-4{padding-top:1.5rem !important}.pt-xl-5{padding-top:3rem !important}.pe-xl-0{padding-right:0 !important}.pe-xl-1{padding-right:.25rem !important}.pe-xl-2{padding-right:.5rem !important}.pe-xl-3{padding-right:1rem !important}.pe-xl-4{padding-right:1.5rem !important}.pe-xl-5{padding-right:3rem !important}.pb-xl-0{padding-bottom:0 !important}.pb-xl-1{padding-bottom:.25rem !important}.pb-xl-2{padding-bottom:.5rem !important}.pb-xl-3{padding-bottom:1rem !important}.pb-xl-4{padding-bottom:1.5rem !important}.pb-xl-5{padding-bottom:3rem !important}.ps-xl-0{padding-left:0 !important}.ps-xl-1{padding-left:.25rem !important}.ps-xl-2{padding-left:.5rem !important}.ps-xl-3{padding-left:1rem !important}.ps-xl-4{padding-left:1.5rem !important}.ps-xl-5{padding-left:3rem !important}.text-xl-start{text-align:left !important}.text-xl-end{text-align:right !important}.text-xl-center{text-align:center !important}}@media(min-width: 1400px){.float-xxl-start{float:left !important}.float-xxl-end{float:right !important}.float-xxl-none{float:none !important}.d-xxl-inline{display:inline !important}.d-xxl-inline-block{display:inline-block !important}.d-xxl-block{display:block !important}.d-xxl-grid{display:grid !important}.d-xxl-table{display:table !important}.d-xxl-table-row{display:table-row !important}.d-xxl-table-cell{display:table-cell !important}.d-xxl-flex{display:flex !important}.d-xxl-inline-flex{display:inline-flex !important}.d-xxl-none{display:none !important}.flex-xxl-fill{flex:1 1 auto !important}.flex-xxl-row{flex-direction:row !important}.flex-xxl-column{flex-direction:column !important}.flex-xxl-row-reverse{flex-direction:row-reverse !important}.flex-xxl-column-reverse{flex-direction:column-reverse !important}.flex-xxl-grow-0{flex-grow:0 !important}.flex-xxl-grow-1{flex-grow:1 !important}.flex-xxl-shrink-0{flex-shrink:0 !important}.flex-xxl-shrink-1{flex-shrink:1 !important}.flex-xxl-wrap{flex-wrap:wrap !important}.flex-xxl-nowrap{flex-wrap:nowrap !important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-xxl-0{gap:0 !important}.gap-xxl-1{gap:.25rem !important}.gap-xxl-2{gap:.5rem !important}.gap-xxl-3{gap:1rem !important}.gap-xxl-4{gap:1.5rem !important}.gap-xxl-5{gap:3rem !important}.justify-content-xxl-start{justify-content:flex-start !important}.justify-content-xxl-end{justify-content:flex-end !important}.justify-content-xxl-center{justify-content:center !important}.justify-content-xxl-between{justify-content:space-between !important}.justify-content-xxl-around{justify-content:space-around !important}.justify-content-xxl-evenly{justify-content:space-evenly !important}.align-items-xxl-start{align-items:flex-start !important}.align-items-xxl-end{align-items:flex-end !important}.align-items-xxl-center{align-items:center !important}.align-items-xxl-baseline{align-items:baseline !important}.align-items-xxl-stretch{align-items:stretch !important}.align-content-xxl-start{align-content:flex-start !important}.align-content-xxl-end{align-content:flex-end !important}.align-content-xxl-center{align-content:center !important}.align-content-xxl-between{align-content:space-between !important}.align-content-xxl-around{align-content:space-around !important}.align-content-xxl-stretch{align-content:stretch !important}.align-self-xxl-auto{align-self:auto !important}.align-self-xxl-start{align-self:flex-start !important}.align-self-xxl-end{align-self:flex-end !important}.align-self-xxl-center{align-self:center !important}.align-self-xxl-baseline{align-self:baseline !important}.align-self-xxl-stretch{align-self:stretch !important}.order-xxl-first{order:-1 !important}.order-xxl-0{order:0 !important}.order-xxl-1{order:1 !important}.order-xxl-2{order:2 !important}.order-xxl-3{order:3 !important}.order-xxl-4{order:4 !important}.order-xxl-5{order:5 !important}.order-xxl-last{order:6 !important}.m-xxl-0{margin:0 !important}.m-xxl-1{margin:.25rem !important}.m-xxl-2{margin:.5rem !important}.m-xxl-3{margin:1rem !important}.m-xxl-4{margin:1.5rem !important}.m-xxl-5{margin:3rem !important}.m-xxl-auto{margin:auto !important}.mx-xxl-0{margin-right:0 !important;margin-left:0 !important}.mx-xxl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xxl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xxl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xxl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xxl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xxl-auto{margin-right:auto !important;margin-left:auto !important}.my-xxl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xxl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xxl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xxl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xxl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xxl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xxl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xxl-0{margin-top:0 !important}.mt-xxl-1{margin-top:.25rem !important}.mt-xxl-2{margin-top:.5rem !important}.mt-xxl-3{margin-top:1rem !important}.mt-xxl-4{margin-top:1.5rem !important}.mt-xxl-5{margin-top:3rem !important}.mt-xxl-auto{margin-top:auto !important}.me-xxl-0{margin-right:0 !important}.me-xxl-1{margin-right:.25rem !important}.me-xxl-2{margin-right:.5rem !important}.me-xxl-3{margin-right:1rem !important}.me-xxl-4{margin-right:1.5rem !important}.me-xxl-5{margin-right:3rem !important}.me-xxl-auto{margin-right:auto !important}.mb-xxl-0{margin-bottom:0 !important}.mb-xxl-1{margin-bottom:.25rem !important}.mb-xxl-2{margin-bottom:.5rem !important}.mb-xxl-3{margin-bottom:1rem !important}.mb-xxl-4{margin-bottom:1.5rem !important}.mb-xxl-5{margin-bottom:3rem !important}.mb-xxl-auto{margin-bottom:auto !important}.ms-xxl-0{margin-left:0 !important}.ms-xxl-1{margin-left:.25rem !important}.ms-xxl-2{margin-left:.5rem !important}.ms-xxl-3{margin-left:1rem !important}.ms-xxl-4{margin-left:1.5rem !important}.ms-xxl-5{margin-left:3rem !important}.ms-xxl-auto{margin-left:auto !important}.p-xxl-0{padding:0 !important}.p-xxl-1{padding:.25rem !important}.p-xxl-2{padding:.5rem !important}.p-xxl-3{padding:1rem !important}.p-xxl-4{padding:1.5rem !important}.p-xxl-5{padding:3rem !important}.px-xxl-0{padding-right:0 !important;padding-left:0 !important}.px-xxl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xxl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xxl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xxl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xxl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xxl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xxl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xxl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xxl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xxl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xxl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xxl-0{padding-top:0 !important}.pt-xxl-1{padding-top:.25rem !important}.pt-xxl-2{padding-top:.5rem !important}.pt-xxl-3{padding-top:1rem !important}.pt-xxl-4{padding-top:1.5rem !important}.pt-xxl-5{padding-top:3rem !important}.pe-xxl-0{padding-right:0 !important}.pe-xxl-1{padding-right:.25rem !important}.pe-xxl-2{padding-right:.5rem !important}.pe-xxl-3{padding-right:1rem !important}.pe-xxl-4{padding-right:1.5rem !important}.pe-xxl-5{padding-right:3rem !important}.pb-xxl-0{padding-bottom:0 !important}.pb-xxl-1{padding-bottom:.25rem !important}.pb-xxl-2{padding-bottom:.5rem !important}.pb-xxl-3{padding-bottom:1rem !important}.pb-xxl-4{padding-bottom:1.5rem !important}.pb-xxl-5{padding-bottom:3rem !important}.ps-xxl-0{padding-left:0 !important}.ps-xxl-1{padding-left:.25rem !important}.ps-xxl-2{padding-left:.5rem !important}.ps-xxl-3{padding-left:1rem !important}.ps-xxl-4{padding-left:1.5rem !important}.ps-xxl-5{padding-left:3rem !important}.text-xxl-start{text-align:left !important}.text-xxl-end{text-align:right !important}.text-xxl-center{text-align:center !important}}.bg-default{color:#fff}.bg-primary{color:#fff}.bg-secondary{color:#fff}.bg-success{color:#fff}.bg-info{color:#fff}.bg-warning{color:#fff}.bg-danger{color:#fff}.bg-light{color:#000}.bg-dark{color:#fff}@media(min-width: 1200px){.fs-1{font-size:2.2rem !important}.fs-2{font-size:1.75rem !important}.fs-3{font-size:1.5rem !important}}@media print{.d-print-inline{display:inline !important}.d-print-inline-block{display:inline-block !important}.d-print-block{display:block !important}.d-print-grid{display:grid !important}.d-print-table{display:table !important}.d-print-table-row{display:table-row !important}.d-print-table-cell{display:table-cell !important}.d-print-flex{display:flex !important}.d-print-inline-flex{display:inline-flex !important}.d-print-none{display:none !important}}.quarto-container{min-height:calc(100vh - 132px)}footer.footer .nav-footer,#quarto-header nav{padding-left:1em;padding-right:1em}nav[role=doc-toc]{padding-left:.5em}#quarto-content>*{padding-top:14px}@media(max-width: 991.98px){#quarto-content>*{padding-top:0}#quarto-content .subtitle{padding-top:14px}#quarto-content section:first-of-type h2:first-of-type,#quarto-content section:first-of-type .h2:first-of-type{margin-top:1rem}}.headroom-target,header.headroom{will-change:transform;transition:transform 200ms linear;transition:position 200ms linear}header.headroom--pinned{transform:translateY(0%)}header.headroom--unpinned{transform:translateY(-100%)}.navbar-container{width:100%}.navbar-brand{overflow:hidden;text-overflow:ellipsis}.navbar-brand-container{max-width:calc(100% - 85px);min-width:0;display:flex;align-items:center;margin-right:1em}.navbar-brand.navbar-brand-logo{margin-right:4px;display:inline-flex}.navbar-toggler{flex-basis:content;flex-shrink:0}.navbar-logo{max-height:24px;width:auto;padding-right:4px}nav .nav-item:not(.compact){padding-top:1px}nav .nav-link i,nav .dropdown-item i{padding-right:1px}.navbar-expand-lg .navbar-nav .nav-link{padding-left:.6rem;padding-right:.6rem}nav .nav-item.compact .nav-link{padding-left:.5rem;padding-right:.5rem;font-size:1.1rem}.navbar-nav .dropdown-menu{min-width:220px;font-size:.9rem}.navbar .navbar-nav .nav-link.dropdown-toggle::after{opacity:.75;vertical-align:.175em}.navbar ul.dropdown-menu{padding-top:0;padding-bottom:0}.navbar .dropdown-header{text-transform:uppercase;font-size:.8rem;padding:0 .5rem}.navbar .dropdown-item{padding:.4rem .5rem}.navbar .dropdown-item>i.bi{margin-left:.1rem;margin-right:.25em}.sidebar #quarto-search{margin-top:-1px}.sidebar #quarto-search svg.aa-SubmitIcon{width:16px;height:16px}.sidebar-navigation a{color:inherit}.sidebar-title{margin-top:.25rem;padding-bottom:.5rem;font-size:1.3rem;line-height:1.6rem;visibility:visible}.sidebar-title>a{font-size:inherit;text-decoration:none}.sidebar-title .sidebar-tools-main{margin-top:-6px}.sidebar-header-stacked .sidebar-title{margin-top:.6rem}.sidebar-logo{max-width:90%;padding-bottom:.5rem}.sidebar-logo-link{text-decoration:none}.sidebar-navigation li a{text-decoration:none}.sidebar-navigation .sidebar-tool{opacity:.7;font-size:.875rem}#quarto-sidebar>nav>.sidebar-tools-main{margin-left:14px}.sidebar-tools-main{margin-left:0px}.sidebar-tools-main:not(.tools-wide){display:inline-block;vertical-align:middle}.sidebar-tools-main.tools-wide{padding-top:.3em}.sidebar-navigation .sidebar-tool.dropdown-toggle::after{display:none}.sidebar.sidebar-navigation>*{padding-top:1em}.sidebar-item{margin-bottom:.2em}.sidebar-section{margin-top:.2em;padding-left:.5em;padding-bottom:.2em}.sidebar-item .sidebar-item-container{display:flex;justify-content:space-between}.sidebar-item .sidebar-item-toggle .bi{font-size:.7rem;text-align:center}.sidebar-navigation .sidebar-divider{margin-left:0;margin-right:0;margin-top:.5rem;margin-bottom:.5rem}@media(max-width: 767.98px){.quarto-secondary-nav{display:block}}@media(min-width: 992px){.quarto-secondary-nav{display:none}}.quarto-secondary-nav .quarto-btn-toggle{color:#595959;padding-right:0}.quarto-secondary-nav[aria-expanded=false] .quarto-btn-toggle .bi-chevron-right::before{transform:none}.quarto-secondary-nav[aria-expanded=true] .quarto-btn-toggle .bi-chevron-right::before{transform:rotate(90deg)}.quarto-secondary-nav .quarto-btn-toggle .bi-chevron-right::before{transition:transform 200ms ease}.quarto-secondary-nav{cursor:pointer}.quarto-secondary-nav-title{margin-top:.3em;color:#595959;padding-top:4px}div.sidebar-item-container{color:#595959}div.sidebar-item-container:hover,div.sidebar-item-container:focus{color:rgba(27,88,157,.8)}div.sidebar-item-container.disabled{color:rgba(89,89,89,.75)}div.sidebar-item-container .active,div.sidebar-item-container .show>.nav-link,div.sidebar-item-container .sidebar-link>code{color:#1b589d}div.sidebar.sidebar-navigation.rollup.quarto-sidebar-toggle-contents,nav.sidebar.sidebar-navigation:not(.rollup){background-color:#fff}@media(max-width: 991.98px){.sidebar-navigation .sidebar-item a,.nav-page .nav-page-text,.sidebar-navigation{font-size:1rem}.sidebar-navigation ul.sidebar-section.depth1 .sidebar-section-item{font-size:1.1rem}.sidebar-logo{display:none}.sidebar.sidebar-navigation{position:static;border-bottom:1px solid #dee2e6}.sidebar.sidebar-navigation.collapsing{position:fixed;z-index:1000}.sidebar.sidebar-navigation.show{position:fixed;z-index:1000}.sidebar.sidebar-navigation{transition:height .15s linear;width:100%}nav.quarto-secondary-nav{background-color:#fff;border-bottom:1px solid #dee2e6}.sidebar .sidebar-footer{visibility:visible;padding-top:1rem;position:inherit}.sidebar-tools-collapse{display:block}}@media(min-width: 992px){#quarto-sidebar{display:flex;flex-direction:column}.nav-page .nav-page-text,.sidebar-navigation .sidebar-section .sidebar-item{font-size:.875rem}.sidebar-navigation .sidebar-item{font-size:.925rem}.sidebar.sidebar-navigation{display:block;position:sticky}.sidebar-search{width:100%}.sidebar .sidebar-footer{visibility:visible}}.sidebar .sidebar-footer{padding:.5rem 1rem;align-self:flex-end;color:#6c757d;width:100%}#quarto-sidebar{width:100%;padding-right:1em;color:#595959}.quarto-sidebar-footer{font-size:.875em}.sidebar-section .bi-chevron-right{vertical-align:middle}.sidebar-section a .bi-chevron-right::before{transform:rotate(90deg)}.sidebar-section a.collapsed .bi-chevron-right::before{transform:none}.sidebar-section .bi-chevron-right::before{font-size:.9em;transition:transform 200ms ease}.notransition{-webkit-transition:none !important;-moz-transition:none !important;-o-transition:none !important;transition:none !important}.btn:focus:not(:focus-visible){box-shadow:none}.page-navigation{display:flex;justify-content:space-between}.nav-page{padding-bottom:.75em}.nav-page .bi{font-size:1.8rem;vertical-align:middle}.nav-page .nav-page-text{padding-left:.25em;padding-right:.25em}.nav-page a{color:#6c757d;text-decoration:none;display:flex;align-items:center}.nav-page a:hover{color:#1f66b6}.toc-actions{display:flex}.toc-actions p{margin-block-start:0;margin-block-end:0}.toc-actions a{text-decoration:none;color:inherit;font-weight:400}.toc-actions a:hover{color:#1f66b6}.toc-actions .action-links{margin-left:4px}.sidebar nav[role=doc-toc] .toc-actions .bi{margin-left:-4px;font-size:.7rem;color:#6c757d}.sidebar nav[role=doc-toc] .toc-actions .bi:before{padding-top:3px}#quarto-margin-sidebar .toc-actions .bi:before{margin-top:.3rem;font-size:.7rem;color:#6c757d;vertical-align:top}.sidebar nav[role=doc-toc] .toc-actions>div:first-of-type{margin-top:-3px}#quarto-margin-sidebar .toc-actions p,.sidebar nav[role=doc-toc] .toc-actions p{font-size:.875rem}.nav-footer{display:flex;justify-content:center;align-items:center;text-align:center;padding-top:.5rem;padding-bottom:.5rem;background-color:#fff}body.nav-fixed{padding-top:64px}.nav-footer-contents{color:#6c757d;margin-top:.25rem}.nav-footer{min-height:3.5em;color:#757575}.nav-footer a{color:#757575}.nav-footer .nav-footer-left{font-size:.825em}.nav-footer .nav-footer-center{font-size:.825em}.nav-footer .nav-footer-right{font-size:.825em}.nav-footer-left .footer-items,.nav-footer-center .footer-items,.nav-footer-right .footer-items{display:flex;padding-top:.3em;padding-bottom:.3em;margin-bottom:0em}.nav-footer-left .footer-items .nav-link,.nav-footer-center .footer-items .nav-link,.nav-footer-right .footer-items .nav-link{padding-left:.6em;padding-right:.6em}.nav-footer-left{margin-right:auto}.nav-footer-center{min-height:3em;position:absolute;text-align:center}.nav-footer-center .footer-items{justify-content:center}@media(max-width: 767.98px){.nav-footer-center{margin-top:3em}}.nav-footer-right{margin-left:auto}.navbar .quarto-reader-toggle{padding-left:.4em;padding-right:0}.navbar .quarto-reader-toggle.reader .quarto-reader-toggle-btn{background-color:#fdfeff;border-radius:3px}.quarto-reader-toggle.reader.sidebar-tool .quarto-reader-toggle-btn{background-color:#595959;border-radius:3px}.quarto-reader-toggle.sidebar-tool{padding-left:.3em}.quarto-reader-toggle .quarto-reader-toggle-btn{display:inline-flex;padding-left:.1em;padding-right:.3em;text-align:center}.navbar .quarto-reader-toggle:not(.reader) .bi::before{background-image:url('data:image/svg+xml,')}.navbar .quarto-reader-toggle.reader .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-reader-toggle:not(.reader) .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-reader-toggle.reader .bi::before{background-image:url('data:image/svg+xml,')}.aa-DetachedOverlay ul.aa-List,#quarto-search-results ul.aa-List{list-style:none;padding-left:0}.aa-DetachedOverlay .aa-Panel,#quarto-search-results .aa-Panel{background-color:#fff;position:absolute;z-index:2000}#quarto-search-results .aa-Panel{max-width:400px}#quarto-search input{font-size:.925rem}@media(min-width: 992px){.navbar #quarto-search{margin-left:1rem}}#quarto-sidebar .sidebar-search .aa-Autocomplete{width:100%}.navbar .aa-Autocomplete .aa-Form{width:180px}.navbar #quarto-search.type-overlay .aa-Autocomplete{width:40px}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form{background-color:inherit;border:none}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form:focus-within{box-shadow:none;outline:none}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-InputWrapper{display:none}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-InputWrapper:focus-within{display:inherit}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-Label svg,.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-LoadingIndicator svg{width:26px;height:26px;color:#fdfeff;opacity:1}.navbar #quarto-search.type-overlay .aa-Autocomplete svg.aa-SubmitIcon{width:26px;height:26px;color:#fdfeff;opacity:1}.aa-Autocomplete .aa-Form,.aa-DetachedFormContainer .aa-Form{align-items:center;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem;color:#373a3c;display:flex;line-height:1em;margin:0;position:relative;width:100%}.aa-Autocomplete .aa-Form:focus-within,.aa-DetachedFormContainer .aa-Form:focus-within{box-shadow:rgba(39,128,227,.6) 0 0 0 1px;outline:currentColor none medium}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix{align-items:center;display:flex;flex-shrink:0;order:1}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-Label,.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-Label,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator{cursor:initial;flex-shrink:0;padding:0;text-align:left}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-Label svg,.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator svg,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-Label svg,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator svg{color:#373a3c;opacity:.5}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-SubmitButton,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-SubmitButton{appearance:none;background:none;border:0;margin:0}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator{align-items:center;display:flex;justify-content:center}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator[hidden],.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator[hidden]{display:none}.aa-Autocomplete .aa-Form .aa-InputWrapper,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper{order:3;position:relative;width:100%}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input{appearance:none;background:none;border:0;color:#373a3c;font:inherit;height:calc(1.5em + (0.1rem + 2px));padding:0;width:100%}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::placeholder,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::placeholder{color:#373a3c;opacity:.8}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input:focus,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input:focus{border-color:none;box-shadow:none;outline:none}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-decoration,.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-cancel-button,.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-button,.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-decoration,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-decoration,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-cancel-button,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-button,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-decoration{display:none}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix{align-items:center;display:flex;order:4}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton{align-items:center;background:none;border:0;color:#373a3c;opacity:.8;cursor:pointer;display:flex;margin:0;width:calc(1.5em + (0.1rem + 2px))}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:hover,.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:focus,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:hover,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:focus{color:#373a3c;opacity:.8}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton[hidden],.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton[hidden]{display:none}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton svg,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton svg{width:calc(1.5em + 0.75rem + 2px)}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton{border:none;align-items:center;background:none;color:#373a3c;opacity:.4;font-size:.7rem;cursor:pointer;display:none;margin:0;width:calc(1em + (0.1rem + 2px))}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:hover,.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:focus,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:hover,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:focus{color:#373a3c;opacity:.8}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton[hidden],.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton[hidden]{display:none}#quarto-search-results .aa-Panel{border:solid #ced4da 1px}#quarto-search-results .aa-SourceNoResults{width:398px}.aa-DetachedOverlay .aa-Panel,#quarto-search-results .aa-Panel{max-height:65vh;overflow-y:auto;font-size:.925rem}.aa-DetachedOverlay .aa-SourceNoResults,#quarto-search-results .aa-SourceNoResults{height:60px;display:flex;justify-content:center;align-items:center}.aa-DetachedOverlay .search-error,#quarto-search-results .search-error{padding-top:10px;padding-left:20px;padding-right:20px;cursor:default}.aa-DetachedOverlay .search-error .search-error-title,#quarto-search-results .search-error .search-error-title{font-size:1.1rem;margin-bottom:.5rem}.aa-DetachedOverlay .search-error .search-error-title .search-error-icon,#quarto-search-results .search-error .search-error-title .search-error-icon{margin-right:8px}.aa-DetachedOverlay .search-error .search-error-text,#quarto-search-results .search-error .search-error-text{font-weight:300}.aa-DetachedOverlay .search-result-text,#quarto-search-results .search-result-text{font-weight:300;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;line-height:1.2rem;max-height:2.4rem}.aa-DetachedOverlay .aa-SourceHeader .search-result-header,#quarto-search-results .aa-SourceHeader .search-result-header{font-size:.875rem;background-color:#f2f2f2;padding-left:14px;padding-bottom:4px;padding-top:4px}.aa-DetachedOverlay .aa-SourceHeader .search-result-header-no-results,#quarto-search-results .aa-SourceHeader .search-result-header-no-results{display:none}.aa-DetachedOverlay .aa-SourceFooter .algolia-search-logo,#quarto-search-results .aa-SourceFooter .algolia-search-logo{width:110px;opacity:.85;margin:8px;float:right}.aa-DetachedOverlay .search-result-section,#quarto-search-results .search-result-section{font-size:.925em}.aa-DetachedOverlay a.search-result-link,#quarto-search-results a.search-result-link{color:inherit;text-decoration:none}.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item,#quarto-search-results li.aa-Item[aria-selected=true] .search-item{background-color:#2780e3}.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item.search-result-more,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-section,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-text,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-title-container,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-text-container,#quarto-search-results li.aa-Item[aria-selected=true] .search-item.search-result-more,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-section,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-text,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-title-container,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-text-container{color:#fff;background-color:#2780e3}.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item mark.search-match,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-match.mark,#quarto-search-results li.aa-Item[aria-selected=true] .search-item mark.search-match,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-match.mark{color:#fff;background-color:#4b95e8}.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item,#quarto-search-results li.aa-Item[aria-selected=false] .search-item{background-color:#fff}.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item.search-result-more,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-section,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-text,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-title-container,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-text-container,#quarto-search-results li.aa-Item[aria-selected=false] .search-item.search-result-more,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-section,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-text,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-title-container,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-text-container{color:#373a3c}.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item mark.search-match,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-match.mark,#quarto-search-results li.aa-Item[aria-selected=false] .search-item mark.search-match,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-match.mark{color:inherit;background-color:#e5effc}.aa-DetachedOverlay .aa-Item .search-result-doc:not(.document-selectable) .search-result-title-container,#quarto-search-results .aa-Item .search-result-doc:not(.document-selectable) .search-result-title-container{background-color:#fff;color:#373a3c}.aa-DetachedOverlay .aa-Item .search-result-doc:not(.document-selectable) .search-result-text-container,#quarto-search-results .aa-Item .search-result-doc:not(.document-selectable) .search-result-text-container{padding-top:0px}.aa-DetachedOverlay li.aa-Item .search-result-doc.document-selectable .search-result-text-container,#quarto-search-results li.aa-Item .search-result-doc.document-selectable .search-result-text-container{margin-top:-4px}.aa-DetachedOverlay .aa-Item,#quarto-search-results .aa-Item{cursor:pointer}.aa-DetachedOverlay .aa-Item .search-item,#quarto-search-results .aa-Item .search-item{border-left:none;border-right:none;border-top:none;background-color:#fff;border-color:#ced4da;color:#373a3c}.aa-DetachedOverlay .aa-Item .search-item p,#quarto-search-results .aa-Item .search-item p{margin-top:0;margin-bottom:0}.aa-DetachedOverlay .aa-Item .search-item i.bi,#quarto-search-results .aa-Item .search-item i.bi{padding-left:8px;padding-right:8px;font-size:1.3em}.aa-DetachedOverlay .aa-Item .search-item .search-result-title,#quarto-search-results .aa-Item .search-item .search-result-title{margin-top:.3em;margin-bottom:.1rem}.aa-DetachedOverlay .aa-Item .search-result-title-container,#quarto-search-results .aa-Item .search-result-title-container{font-size:1em;display:flex;padding:6px 4px 6px 4px}.aa-DetachedOverlay .aa-Item .search-result-text-container,#quarto-search-results .aa-Item .search-result-text-container{padding-bottom:8px;padding-right:8px;margin-left:44px}.aa-DetachedOverlay .aa-Item .search-result-doc-section,.aa-DetachedOverlay .aa-Item .search-result-more,#quarto-search-results .aa-Item .search-result-doc-section,#quarto-search-results .aa-Item .search-result-more{padding-top:8px;padding-bottom:8px;padding-left:44px}.aa-DetachedOverlay .aa-Item .search-result-more,#quarto-search-results .aa-Item .search-result-more{font-size:.8em;font-weight:400}.aa-DetachedOverlay .aa-Item .search-result-doc,#quarto-search-results .aa-Item .search-result-doc{border-top:1px solid #ced4da}.aa-DetachedSearchButton{background:none;border:none}.aa-DetachedSearchButton .aa-DetachedSearchButtonPlaceholder{display:none}.navbar .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon{color:#fdfeff}.sidebar-tools-collapse #quarto-search,.sidebar-tools-main #quarto-search{display:inline}.sidebar-tools-collapse #quarto-search .aa-Autocomplete,.sidebar-tools-main #quarto-search .aa-Autocomplete{display:inline}.sidebar-tools-collapse #quarto-search .aa-DetachedSearchButton,.sidebar-tools-main #quarto-search .aa-DetachedSearchButton{padding-left:4px;padding-right:4px}.sidebar-tools-collapse #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon,.sidebar-tools-main #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon{color:#595959}.sidebar-tools-collapse #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon .aa-SubmitIcon,.sidebar-tools-main #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon .aa-SubmitIcon{margin-top:-3px}.aa-DetachedContainer{background:rgba(255,255,255,.65);width:90%;bottom:0;box-shadow:rgba(206,212,218,.6) 0 0 0 1px;outline:currentColor none medium;display:flex;flex-direction:column;left:0;margin:0;overflow:hidden;padding:0;position:fixed;right:0;top:0;z-index:1101}.aa-DetachedContainer::after{height:32px}.aa-DetachedContainer .aa-SourceHeader{margin:var(--aa-spacing-half) 0 var(--aa-spacing-half) 2px}.aa-DetachedContainer .aa-Panel{background-color:#fff;border-radius:0;box-shadow:none;flex-grow:1;margin:0;padding:0;position:relative}.aa-DetachedContainer .aa-PanelLayout{bottom:0;box-shadow:none;left:0;margin:0;max-height:none;overflow-y:auto;position:absolute;right:0;top:0;width:100%}.aa-DetachedFormContainer{background-color:#fff;border-bottom:1px solid #ced4da;display:flex;flex-direction:row;justify-content:space-between;margin:0;padding:.5em}.aa-DetachedCancelButton{background:none;font-size:.8em;border:0;border-radius:3px;color:#373a3c;cursor:pointer;margin:0 0 0 .5em;padding:0 .5em}.aa-DetachedCancelButton:hover,.aa-DetachedCancelButton:focus{box-shadow:rgba(39,128,227,.6) 0 0 0 1px;outline:currentColor none medium}.aa-DetachedContainer--modal{border-radius:6px;bottom:inherit;height:auto;margin:0 auto;max-width:850px;position:absolute;top:100px}.aa-DetachedContainer--modal .aa-PanelLayout{max-height:var(--aa-detached-modal-max-height);padding-bottom:var(--aa-spacing-half);position:static}.aa-Detached{height:100vh;overflow:hidden}.aa-DetachedOverlay{background-color:rgba(55,58,60,.4);position:fixed;left:0;right:0;top:0;margin:0;padding:0;height:100vh;z-index:1100}.quarto-listing{padding-bottom:1em}.listing-pagination{padding-top:.5em}ul.pagination{float:right;padding-left:8px;padding-top:.5em}ul.pagination li{padding-right:.75em}ul.pagination li.disabled a,ul.pagination li.active a{color:#373a3c;text-decoration:none}ul.pagination li:last-of-type{padding-right:0}.listing-actions-group{display:flex}.quarto-listing-filter{margin-bottom:1em;width:200px;margin-left:auto}.quarto-listing-sort{margin-bottom:1em;margin-right:auto;width:auto}.quarto-listing-sort .input-group-text{font-size:.8em}.input-group-text{border-right:none}.quarto-listing-sort select.form-select{font-size:.8em}.listing-no-matching{text-align:center;padding-top:2em;padding-bottom:3em;font-size:1em}#quarto-margin-sidebar .quarto-listing-category{padding-top:0;font-size:1rem}#quarto-margin-sidebar .quarto-listing-category-title{cursor:pointer;font-weight:600;font-size:1rem}.quarto-listing-category .category{cursor:pointer}.quarto-listing-category .category.active{font-weight:600}.quarto-listing-category.category-cloud{display:flex;flex-wrap:wrap;align-items:baseline}.quarto-listing-category.category-cloud .category{padding-right:5px}.quarto-listing-category.category-cloud .category-cloud-1{font-size:.75em}.quarto-listing-category.category-cloud .category-cloud-2{font-size:.95em}.quarto-listing-category.category-cloud .category-cloud-3{font-size:1.15em}.quarto-listing-category.category-cloud .category-cloud-4{font-size:1.35em}.quarto-listing-category.category-cloud .category-cloud-5{font-size:1.55em}.quarto-listing-category.category-cloud .category-cloud-6{font-size:1.75em}.quarto-listing-category.category-cloud .category-cloud-7{font-size:1.95em}.quarto-listing-category.category-cloud .category-cloud-8{font-size:2.15em}.quarto-listing-category.category-cloud .category-cloud-9{font-size:2.35em}.quarto-listing-category.category-cloud .category-cloud-10{font-size:2.55em}.quarto-listing-cols-1{grid-template-columns:repeat(1, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-1{grid-template-columns:repeat(1, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-1{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-2{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-2{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-2{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-3{grid-template-columns:repeat(3, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-3{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-3{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-4{grid-template-columns:repeat(4, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-4{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-4{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-5{grid-template-columns:repeat(5, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-5{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-5{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-6{grid-template-columns:repeat(6, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-6{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-6{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-7{grid-template-columns:repeat(7, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-7{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-7{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-8{grid-template-columns:repeat(8, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-8{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-8{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-9{grid-template-columns:repeat(9, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-9{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-9{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-10{grid-template-columns:repeat(10, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-10{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-10{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-11{grid-template-columns:repeat(11, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-11{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-11{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-12{grid-template-columns:repeat(12, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-12{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-12{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-grid{gap:1.5em}.quarto-grid-item.borderless{border:none}.quarto-grid-item.borderless .listing-categories .listing-category:last-of-type,.quarto-grid-item.borderless .listing-categories .listing-category:first-of-type{padding-left:0}.quarto-grid-item.borderless .listing-categories .listing-category{border:0}.quarto-grid-link{text-decoration:none;color:inherit}.quarto-grid-link:hover{text-decoration:none;color:inherit}.quarto-grid-item h5.title,.quarto-grid-item .title.h5{margin-top:0;margin-bottom:0}.quarto-grid-item .card-footer{display:flex;justify-content:space-between;font-size:.8em}.quarto-grid-item .card-footer p{margin-bottom:0}.quarto-grid-item p.card-img-top{margin-bottom:0}.quarto-grid-item img.thumbnail-image{object-fit:cover}.quarto-grid-item .card-other-values{margin-top:.5em;font-size:.8em}.quarto-grid-item .card-other-values tr{margin-bottom:.5em}.quarto-grid-item .card-other-values tr>td:first-of-type{font-weight:600;padding-right:1em;padding-left:1em;vertical-align:top}.quarto-grid-item div.post-contents{display:flex;flex-direction:column;text-decoration:none;height:100%}.quarto-grid-item div.card-img-bg{background-color:#adb5bd;flex-shrink:0}.quarto-grid-item .card-attribution{padding-top:1em;display:flex;gap:1em;text-transform:uppercase;color:#6c757d;font-weight:500;flex-grow:10;align-items:flex-end}.quarto-grid-item .description{padding-bottom:1em}.quarto-grid-item .card-attribution .date{align-self:flex-end}.quarto-grid-item .card-attribution.justify{justify-content:space-between}.quarto-grid-item .card-attribution.start{justify-content:flex-start}.quarto-grid-item .card-attribution.end{justify-content:flex-end}.quarto-grid-item .card-title{margin-bottom:.1em}.quarto-grid-item .card-subtitle{padding-top:.25em}.quarto-grid-item .card-text{font-size:.9em}.quarto-grid-item .listing-reading-time{padding-bottom:.25em}.quarto-grid-item .card-text-small{font-size:.8em}.quarto-grid-item .card-subtitle.subtitle{font-size:.9em;font-weight:600;padding-bottom:.5em}.quarto-grid-item .listing-categories{display:flex;flex-wrap:wrap;padding-bottom:5px}.quarto-grid-item .listing-categories .listing-category{color:#6c757d;border:solid 1px #dee2e6;border-radius:.25rem;text-transform:uppercase;font-size:.65em;padding-left:.5em;padding-right:.5em;padding-top:.15em;padding-bottom:.15em;cursor:pointer;margin-right:4px;margin-bottom:4px}.quarto-grid-item.card-right{text-align:right}.quarto-grid-item.card-right .listing-categories{justify-content:flex-end}.quarto-grid-item.card-left{text-align:left}.quarto-grid-item.card-center{text-align:center}.quarto-grid-item.card-center .listing-description{text-align:justify}.quarto-grid-item.card-center .listing-categories{justify-content:center}table.quarto-listing-table td.image{padding:0px}table.quarto-listing-table td.image img{width:100%;max-width:50px;object-fit:contain}table.quarto-listing-table a{text-decoration:none}table.quarto-listing-table th a{color:inherit}table.quarto-listing-table th a.asc:after{margin-bottom:-2px;margin-left:5px;display:inline-block;height:1rem;width:1rem;background-repeat:no-repeat;background-size:1rem 1rem;background-image:url('data:image/svg+xml,');content:""}table.quarto-listing-table th a.desc:after{margin-bottom:-2px;margin-left:5px;display:inline-block;height:1rem;width:1rem;background-repeat:no-repeat;background-size:1rem 1rem;background-image:url('data:image/svg+xml,');content:""}table.quarto-listing-table.table-hover td{cursor:pointer}.quarto-post.image-left{flex-direction:row}.quarto-post.image-right{flex-direction:row-reverse}@media(max-width: 767.98px){.quarto-post.image-right,.quarto-post.image-left{gap:0em;flex-direction:column}.quarto-post .metadata{padding-bottom:1em;order:2}.quarto-post .body{order:1}.quarto-post .thumbnail{order:3}}.list.quarto-listing-default div:last-of-type{border-bottom:none}@media(min-width: 992px){.quarto-listing-container-default{margin-right:2em}}div.quarto-post{display:flex;gap:2em;margin-bottom:1.5em;border-bottom:1px solid #dee2e6}@media(max-width: 767.98px){div.quarto-post{padding-bottom:1em}}div.quarto-post .metadata{flex-basis:20%;flex-grow:0;margin-top:.2em;flex-shrink:10}div.quarto-post .thumbnail{flex-basis:30%;flex-grow:0;flex-shrink:0}div.quarto-post .thumbnail img{margin-top:.4em;width:100%;object-fit:cover}div.quarto-post .body{flex-basis:45%;flex-grow:1;flex-shrink:0}div.quarto-post .body h3.listing-title,div.quarto-post .body .listing-title.h3{margin-top:0px;margin-bottom:0px;border-bottom:none}div.quarto-post .body .listing-subtitle{font-size:.875em;margin-bottom:.5em;margin-top:.2em}div.quarto-post .body .description{font-size:.9em}div.quarto-post a{color:#373a3c;display:flex;flex-direction:column;text-decoration:none}div.quarto-post a div.description{flex-shrink:0}div.quarto-post .metadata{display:flex;flex-direction:column;font-size:.8em;font-family:var(--bs-font-sans-serif);flex-basis:33%}div.quarto-post .listing-categories{display:flex;flex-wrap:wrap;padding-bottom:5px}div.quarto-post .listing-categories .listing-category{color:#6c757d;border:solid 1px #dee2e6;border-radius:.25rem;text-transform:uppercase;font-size:.65em;padding-left:.5em;padding-right:.5em;padding-top:.15em;padding-bottom:.15em;cursor:pointer;margin-right:4px;margin-bottom:4px}div.quarto-post .listing-description{margin-bottom:.5em}div.quarto-about-jolla{display:flex !important;flex-direction:column;align-items:center;margin-top:10%;padding-bottom:1em}div.quarto-about-jolla .about-image{object-fit:cover;margin-left:auto;margin-right:auto;margin-bottom:1.5em}div.quarto-about-jolla img.round{border-radius:50%}div.quarto-about-jolla img.rounded{border-radius:10px}div.quarto-about-jolla .quarto-title h1.title,div.quarto-about-jolla .quarto-title .title.h1{text-align:center}div.quarto-about-jolla .quarto-title .description{text-align:center}div.quarto-about-jolla h2,div.quarto-about-jolla .h2{border-bottom:none}div.quarto-about-jolla .about-sep{width:60%}div.quarto-about-jolla main{text-align:center}div.quarto-about-jolla .about-links{display:flex}@media(min-width: 992px){div.quarto-about-jolla .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-jolla .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-jolla .about-link{color:#686d71;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-jolla .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-jolla .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-jolla .about-link:hover{color:#2780e3}div.quarto-about-jolla .about-link i.bi{margin-right:.15em}div.quarto-about-solana{display:flex !important;flex-direction:column;padding-top:3em !important;padding-bottom:1em}div.quarto-about-solana .about-entity{display:flex !important;align-items:start;justify-content:space-between}@media(min-width: 992px){div.quarto-about-solana .about-entity{flex-direction:row}}@media(max-width: 991.98px){div.quarto-about-solana .about-entity{flex-direction:column-reverse;align-items:center;text-align:center}}div.quarto-about-solana .about-entity .entity-contents{display:flex;flex-direction:column}@media(max-width: 767.98px){div.quarto-about-solana .about-entity .entity-contents{width:100%}}div.quarto-about-solana .about-entity .about-image{object-fit:cover}@media(max-width: 991.98px){div.quarto-about-solana .about-entity .about-image{margin-bottom:1.5em}}div.quarto-about-solana .about-entity img.round{border-radius:50%}div.quarto-about-solana .about-entity img.rounded{border-radius:10px}div.quarto-about-solana .about-entity .about-links{display:flex;justify-content:left;padding-bottom:1.2em}@media(min-width: 992px){div.quarto-about-solana .about-entity .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-solana .about-entity .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-solana .about-entity .about-link{color:#686d71;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-solana .about-entity .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-solana .about-entity .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-solana .about-entity .about-link:hover{color:#2780e3}div.quarto-about-solana .about-entity .about-link i.bi{margin-right:.15em}div.quarto-about-solana .about-contents{padding-right:1.5em;flex-basis:0;flex-grow:1}div.quarto-about-solana .about-contents main.content{margin-top:0}div.quarto-about-solana .about-contents h2,div.quarto-about-solana .about-contents .h2{border-bottom:none}div.quarto-about-trestles{display:flex !important;flex-direction:row;padding-top:3em !important;padding-bottom:1em}@media(max-width: 991.98px){div.quarto-about-trestles{flex-direction:column;padding-top:0em !important}}div.quarto-about-trestles .about-entity{display:flex !important;flex-direction:column;align-items:center;text-align:center;padding-right:1em}@media(min-width: 992px){div.quarto-about-trestles .about-entity{flex:0 0 42%}}div.quarto-about-trestles .about-entity .about-image{object-fit:cover;margin-bottom:1.5em}div.quarto-about-trestles .about-entity img.round{border-radius:50%}div.quarto-about-trestles .about-entity img.rounded{border-radius:10px}div.quarto-about-trestles .about-entity .about-links{display:flex;justify-content:center}@media(min-width: 992px){div.quarto-about-trestles .about-entity .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-trestles .about-entity .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-trestles .about-entity .about-link{color:#686d71;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-trestles .about-entity .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-trestles .about-entity .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-trestles .about-entity .about-link:hover{color:#2780e3}div.quarto-about-trestles .about-entity .about-link i.bi{margin-right:.15em}div.quarto-about-trestles .about-contents{flex-basis:0;flex-grow:1}div.quarto-about-trestles .about-contents h2,div.quarto-about-trestles .about-contents .h2{border-bottom:none}@media(min-width: 992px){div.quarto-about-trestles .about-contents{border-left:solid 1px #dee2e6;padding-left:1.5em}}div.quarto-about-trestles .about-contents main.content{margin-top:0}div.quarto-about-marquee{padding-bottom:1em}div.quarto-about-marquee .about-contents{display:flex;flex-direction:column}div.quarto-about-marquee .about-image{max-height:550px;margin-bottom:1.5em;object-fit:cover}div.quarto-about-marquee img.round{border-radius:50%}div.quarto-about-marquee img.rounded{border-radius:10px}div.quarto-about-marquee h2,div.quarto-about-marquee .h2{border-bottom:none}div.quarto-about-marquee .about-links{display:flex;justify-content:center;padding-top:1.5em}@media(min-width: 992px){div.quarto-about-marquee .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-marquee .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-marquee .about-link{color:#686d71;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-marquee .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-marquee .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-marquee .about-link:hover{color:#2780e3}div.quarto-about-marquee .about-link i.bi{margin-right:.15em}@media(min-width: 992px){div.quarto-about-marquee .about-link{border:none}}div.quarto-about-broadside{display:flex;flex-direction:column;padding-bottom:1em}div.quarto-about-broadside .about-main{display:flex !important;padding-top:0 !important}@media(min-width: 992px){div.quarto-about-broadside .about-main{flex-direction:row;align-items:flex-start}}@media(max-width: 991.98px){div.quarto-about-broadside .about-main{flex-direction:column}}@media(max-width: 991.98px){div.quarto-about-broadside .about-main .about-entity{flex-shrink:0;width:100%;height:450px;margin-bottom:1.5em;background-size:cover;background-repeat:no-repeat}}@media(min-width: 992px){div.quarto-about-broadside .about-main .about-entity{flex:0 10 50%;margin-right:1.5em;width:100%;height:100%;background-size:100%;background-repeat:no-repeat}}div.quarto-about-broadside .about-main .about-contents{padding-top:14px;flex:0 0 50%}div.quarto-about-broadside h2,div.quarto-about-broadside .h2{border-bottom:none}div.quarto-about-broadside .about-sep{margin-top:1.5em;width:60%;align-self:center}div.quarto-about-broadside .about-links{display:flex;justify-content:center;column-gap:20px;padding-top:1.5em}@media(min-width: 992px){div.quarto-about-broadside .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-broadside .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-broadside .about-link{color:#686d71;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-broadside .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-broadside .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-broadside .about-link:hover{color:#2780e3}div.quarto-about-broadside .about-link i.bi{margin-right:.15em}@media(min-width: 992px){div.quarto-about-broadside .about-link{border:none}}.tippy-box[data-theme~=quarto]{background-color:#fff;color:#373a3c;border-radius:.25rem;border:solid 1px #dee2e6;font-size:.875rem}.tippy-box[data-theme~=quarto] .tippy-arrow{color:#dee2e6}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:-1px}.tippy-box[data-placement^=bottom]>.tippy-content{padding:.75em 1em;z-index:1}.top-right{position:absolute;top:1em;right:1em}.hidden{display:none !important}.zindex-bottom{z-index:-1 !important}.quarto-layout-panel{margin-bottom:1em}.quarto-layout-panel>figure{width:100%}.quarto-layout-panel>figure>figcaption,.quarto-layout-panel>.panel-caption{margin-top:10pt}.quarto-layout-panel>.table-caption{margin-top:0px}.table-caption p{margin-bottom:.5em}.quarto-layout-row{display:flex;flex-direction:row;align-items:flex-start}.quarto-layout-valign-top{align-items:flex-start}.quarto-layout-valign-bottom{align-items:flex-end}.quarto-layout-valign-center{align-items:center}.quarto-layout-cell{position:relative;margin-right:20px}.quarto-layout-cell:last-child{margin-right:0}.quarto-layout-cell figure,.quarto-layout-cell>p{margin:.2em}.quarto-layout-cell img{max-width:100%}.quarto-layout-cell .html-widget{width:100% !important}.quarto-layout-cell div figure p{margin:0}.quarto-layout-cell figure{display:inline-block;margin-inline-start:0;margin-inline-end:0}.quarto-layout-cell table{display:inline-table}.quarto-layout-cell-subref figcaption,figure .quarto-layout-row figure figcaption{text-align:center;font-style:italic}.quarto-figure{position:relative;margin-bottom:1em}.quarto-figure>figure{width:100%;margin-bottom:0}.quarto-figure-left>figure>p{text-align:left}.quarto-figure-center>figure>p{text-align:center}.quarto-figure-right>figure>p{text-align:right}figure>p:empty{display:none}figure>p:first-child{margin-top:0;margin-bottom:0}figure>figcaption{margin-top:.5em}div[id^=tbl-]{position:relative}.quarto-figure>.anchorjs-link,div[id^=tbl-]>.anchorjs-link{position:absolute;top:0;right:0}.quarto-figure:hover>.anchorjs-link,div[id^=tbl-]:hover>.anchorjs-link,h2:hover>.anchorjs-link,.h2:hover>.anchorjs-link,h3:hover>.anchorjs-link,.h3:hover>.anchorjs-link,h4:hover>.anchorjs-link,.h4:hover>.anchorjs-link,h5:hover>.anchorjs-link,.h5:hover>.anchorjs-link,h6:hover>.anchorjs-link,.h6:hover>.anchorjs-link,.reveal-anchorjs-link>.anchorjs-link{opacity:1}#title-block-header{margin-block-end:1rem;position:relative;margin-top:-1px}#title-block-header .abstract{margin-block-start:1rem}#title-block-header .abstract .abstract-title{font-weight:600}#title-block-header a{text-decoration:none}#title-block-header .author,#title-block-header .date,#title-block-header .doi{margin-block-end:.2rem}#title-block-header .quarto-title-block>div{display:flex}#title-block-header .quarto-title-block>div>h1,#title-block-header .quarto-title-block>div>.h1{flex-grow:1}#title-block-header .quarto-title-block>div>button{flex-shrink:0;height:2.25rem;margin-top:0}@media(min-width: 992px){#title-block-header .quarto-title-block>div>button{margin-top:5px}}tr.header>th>p:last-of-type{margin-bottom:0px}table,.table{caption-side:top;margin-bottom:1.5rem}caption,.table-caption{padding-top:.5rem;padding-bottom:.5rem;text-align:center}.utterances{max-width:none;margin-left:-8px}iframe{margin-bottom:1em}details{margin-bottom:1em}details[show]{margin-bottom:0}details>summary{color:#6c757d}details>summary>p:only-child{display:inline}pre.sourceCode,code.sourceCode{position:relative}p code:not(.sourceCode){white-space:pre-wrap}code{white-space:pre}@media print{code{white-space:pre-wrap}}pre>code{display:block}pre>code.sourceCode{white-space:pre}pre>code.sourceCode>span>a:first-child::before{text-decoration:none}pre.code-overflow-wrap>code.sourceCode{white-space:pre-wrap}pre.code-overflow-scroll>code.sourceCode{white-space:pre}code a:any-link{color:inherit;text-decoration:none}code a:hover{color:inherit;text-decoration:underline}ul.task-list{padding-left:1em}[data-tippy-root]{display:inline-block}.tippy-content .footnote-back{display:none}.quarto-embedded-source-code{display:none}.quarto-unresolved-ref{font-weight:600}.quarto-cover-image{max-width:35%;float:right;margin-left:30px}.cell-output-display .widget-subarea{margin-bottom:1em}.cell-output-display:not(.no-overflow-x){overflow-x:auto}.panel-input{margin-bottom:1em}.panel-input>div,.panel-input>div>div{display:inline-block;vertical-align:top;padding-right:12px}.panel-input>p:last-child{margin-bottom:0}.layout-sidebar{margin-bottom:1em}.layout-sidebar .tab-content{border:none}.tab-content>.page-columns.active{display:grid}div.sourceCode>iframe{width:100%;height:300px;margin-bottom:-0.5em}div.ansi-escaped-output{font-family:monospace;display:block}/*! -* -* ansi colors from IPython notebook's -* -*/.ansi-black-fg{color:#3e424d}.ansi-black-bg{background-color:#3e424d}.ansi-black-intense-fg{color:#282c36}.ansi-black-intense-bg{background-color:#282c36}.ansi-red-fg{color:#e75c58}.ansi-red-bg{background-color:#e75c58}.ansi-red-intense-fg{color:#b22b31}.ansi-red-intense-bg{background-color:#b22b31}.ansi-green-fg{color:#00a250}.ansi-green-bg{background-color:#00a250}.ansi-green-intense-fg{color:#007427}.ansi-green-intense-bg{background-color:#007427}.ansi-yellow-fg{color:#ddb62b}.ansi-yellow-bg{background-color:#ddb62b}.ansi-yellow-intense-fg{color:#b27d12}.ansi-yellow-intense-bg{background-color:#b27d12}.ansi-blue-fg{color:#208ffb}.ansi-blue-bg{background-color:#208ffb}.ansi-blue-intense-fg{color:#0065ca}.ansi-blue-intense-bg{background-color:#0065ca}.ansi-magenta-fg{color:#d160c4}.ansi-magenta-bg{background-color:#d160c4}.ansi-magenta-intense-fg{color:#a03196}.ansi-magenta-intense-bg{background-color:#a03196}.ansi-cyan-fg{color:#60c6c8}.ansi-cyan-bg{background-color:#60c6c8}.ansi-cyan-intense-fg{color:#258f8f}.ansi-cyan-intense-bg{background-color:#258f8f}.ansi-white-fg{color:#c5c1b4}.ansi-white-bg{background-color:#c5c1b4}.ansi-white-intense-fg{color:#a1a6b2}.ansi-white-intense-bg{background-color:#a1a6b2}.ansi-default-inverse-fg{color:#fff}.ansi-default-inverse-bg{background-color:#000}.ansi-bold{font-weight:bold}.ansi-underline{text-decoration:underline}:root{--quarto-body-bg: #fff;--quarto-body-color: #373a3c;--quarto-text-muted: #6c757d;--quarto-border-color: #dee2e6;--quarto-border-width: 1px;--quarto-border-radius: 0.25rem}table.gt_table{color:var(--quarto-body-color);font-size:1em;width:100%;background-color:transparent;border-top-width:inherit;border-bottom-width:inherit;border-color:var(--quarto-border-color)}table.gt_table th.gt_column_spanner_outer{color:var(--quarto-body-color);background-color:transparent;border-top-width:inherit;border-bottom-width:inherit;border-color:var(--quarto-border-color)}table.gt_table th.gt_col_heading{color:var(--quarto-body-color);font-weight:bold;background-color:transparent}table.gt_table thead.gt_col_headings{border-bottom:1px solid currentColor;border-top-width:inherit;border-top-color:var(--quarto-border-color)}table.gt_table thead.gt_col_headings:not(:first-child){border-top-width:1px;border-top-color:var(--quarto-border-color)}table.gt_table td.gt_row{border-bottom-width:1px;border-bottom-color:var(--quarto-border-color);border-top-width:0px}table.gt_table tbody.gt_table_body{border-top-width:1px;border-bottom-width:1px;border-bottom-color:var(--quarto-border-color);border-top-color:currentColor}div.columns{display:initial;gap:initial}div.column{display:inline-block;overflow-x:initial;vertical-align:top;width:50%}@media print{:root{font-size:11pt}#quarto-sidebar,#TOC,.nav-page{display:none}.page-columns .content{grid-column-start:page-start}.fixed-top{position:relative}.panel-caption,.figure-caption,figcaption{color:#666}}.code-copy-button{position:absolute;top:0;right:0;border:0;margin-top:5px;margin-right:5px;background-color:transparent}.code-copy-button:focus{outline:none}.code-copy-button-tooltip{font-size:.75em}pre.sourceCode:hover>.code-copy-button>.bi::before{display:inline-block;height:1rem;width:1rem;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:1rem 1rem}pre.sourceCode:hover>.code-copy-button-checked>.bi::before{background-image:url('data:image/svg+xml,')}pre.sourceCode:hover>.code-copy-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}pre.sourceCode:hover>.code-copy-button-checked:hover>.bi::before{background-image:url('data:image/svg+xml,')}main ol ol,main ul ul,main ol ul,main ul ol{margin-bottom:1em}body{margin:0}main.page-columns>header>h1.title,main.page-columns>header>.title.h1{margin-bottom:0}@media(min-width: 992px){body .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.fullcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] 35px [page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.slimcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.listing:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc(1200px - 3em)) [body-content-end] 3em [body-end] 50px [body-end-outset] minmax(0px, 250px) [page-end-inset] 50px [page-end] 1fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 175px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 175px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] minmax(25px, 50px) [page-start-inset] minmax(50px, 150px) [body-start-outset] minmax(25px, 50px) [body-start] 1.5em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end] minmax(25px, 50px) [body-end-outset] minmax(50px, 150px) [page-end-inset] minmax(25px, 50px) [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1000px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 100px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1000px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 50px [page-start-inset] minmax(50px, 150px) [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(450px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1000px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 100px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 50px [page-start-inset] minmax(50px, 150px) [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(450px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(50px, 150px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] minmax(25px, 50px) [page-start-inset] minmax(50px, 150px) [body-start-outset] minmax(25px, 50px) [body-start] 1.5em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end] minmax(25px, 50px) [body-end-outset] minmax(50px, 150px) [page-end-inset] minmax(25px, 50px) [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}}@media(max-width: 991.98px){body .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.fullcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.slimcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.listing:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(1200px - 3em)) [body-content-end body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 145px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc(750px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 145px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc(750px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(75px, 150px) [page-end-inset] 25px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(25px, 50px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc( 1000px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(25px, 50px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 4fr [screen-end-inset] 1.5em [screen-end]}body.floating.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(75px, 150px) [page-end-inset] 25px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}}@media(max-width: 767.98px){body .page-columns,body.fullcontent:not(.floating):not(.docked) .page-columns,body.slimcontent:not(.floating):not(.docked) .page-columns,body.docked .page-columns,body.docked.slimcontent .page-columns,body.docked.fullcontent .page-columns,body.floating .page-columns,body.floating.slimcontent .page-columns,body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}nav[role=doc-toc]{display:none}}body,.page-row-navigation{grid-template-rows:[page-top] max-content [contents-top] max-content [contents-bottom] max-content [page-bottom]}.page-rows-contents{grid-template-rows:[content-top] minmax(max-content, 1fr) [content-bottom] minmax(60px, max-content) [page-bottom]}.page-full{grid-column:screen-start/screen-end !important}.page-columns>*{grid-column:body-content-start/body-content-end}.page-columns.column-page>*{grid-column:page-start/page-end}.page-columns.column-page-left>*{grid-column:page-start/body-content-end}.page-columns.column-page-right>*{grid-column:body-content-start/page-end}.page-rows{grid-auto-rows:auto}.header{grid-column:screen-start/screen-end;grid-row:page-top/contents-top}#quarto-content{padding:0;grid-column:screen-start/screen-end;grid-row:contents-top/contents-bottom}body.floating .sidebar.sidebar-navigation{grid-column:page-start/body-start;grid-row:content-top/page-bottom}body.docked .sidebar.sidebar-navigation{grid-column:screen-start/body-start;grid-row:content-top/page-bottom}.sidebar.toc-left{grid-column:page-start/body-start;grid-row:content-top/page-bottom}.sidebar.margin-sidebar{grid-column:body-end/page-end;grid-row:content-top/page-bottom}.page-columns .content{grid-column:body-content-start/body-content-end;grid-row:content-top/content-bottom;align-content:flex-start}.page-columns .page-navigation{grid-column:body-content-start/body-content-end;grid-row:content-bottom/page-bottom}.page-columns .footer{grid-column:screen-start/screen-end;grid-row:contents-bottom/page-bottom}.page-columns .column-body{grid-column:body-content-start/body-content-end}.page-columns .column-body-fullbleed{grid-column:body-start/body-end}.page-columns .column-body-outset{grid-column:body-start-outset/body-end-outset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-body-outset table{background:#fff}.page-columns .column-body-outset-left{grid-column:body-start-outset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-body-outset-left table{background:#fff}.page-columns .column-body-outset-right{grid-column:body-content-start/body-end-outset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-body-outset-right table{background:#fff}.page-columns .column-page{grid-column:page-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page table{background:#fff}.page-columns .column-page-inset{grid-column:page-start-inset/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-inset table{background:#fff}.page-columns .column-page-inset-left{grid-column:page-start-inset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-inset-left table{background:#fff}.page-columns .column-page-inset-right{grid-column:body-content-start/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-inset-right figcaption table{background:#fff}.page-columns .column-page-left{grid-column:page-start/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-left table{background:#fff}.page-columns .column-page-right{grid-column:body-content-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-right figcaption table{background:#fff}#quarto-content.page-columns #quarto-margin-sidebar,#quarto-content.page-columns #quarto-sidebar{z-index:1}@media(max-width: 991.98px){#quarto-content.page-columns #quarto-margin-sidebar.collapse,#quarto-content.page-columns #quarto-sidebar.collapse{z-index:1055}}#quarto-content.page-columns main.column-page,#quarto-content.page-columns main.column-page-right,#quarto-content.page-columns main.column-page-left{z-index:0}.page-columns .column-screen-inset{grid-column:screen-start-inset/screen-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset table{background:#fff}.page-columns .column-screen-inset-left{grid-column:screen-start-inset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-left table{background:#fff}.page-columns .column-screen-inset-right{grid-column:body-content-start/screen-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-right table{background:#fff}.page-columns .column-screen{grid-column:screen-start/screen-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen table{background:#fff}.page-columns .column-screen-left{grid-column:screen-start/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-left table{background:#fff}.page-columns .column-screen-right{grid-column:body-content-start/screen-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-right table{background:#fff}.page-columns .column-screen-inset-shaded{grid-column:screen-start/screen-end;padding:1em;background:#f8f9fa;z-index:998;transform:translate3d(0, 0, 0);margin-bottom:1em}.zindex-content{z-index:998;transform:translate3d(0, 0, 0)}.zindex-modal{z-index:1055;transform:translate3d(0, 0, 0)}.zindex-over-content{z-index:999;transform:translate3d(0, 0, 0)}img.img-fluid.column-screen,img.img-fluid.column-screen-inset-shaded,img.img-fluid.column-screen-inset,img.img-fluid.column-screen-inset-left,img.img-fluid.column-screen-inset-right,img.img-fluid.column-screen-left,img.img-fluid.column-screen-right{width:100%}@media(min-width: 992px){.margin-caption,div.aside,aside,.column-margin{grid-column:body-end/page-end !important;z-index:998}.column-sidebar{grid-column:page-start/body-start !important;z-index:998}.column-leftmargin{grid-column:screen-start-inset/body-start !important;z-index:998}.no-row-height{height:1em;overflow:visible}}@media(max-width: 991.98px){.margin-caption,div.aside,aside,.column-margin{grid-column:body-end/page-end !important;z-index:998}.no-row-height{height:1em;overflow:visible}.page-columns.page-full{overflow:visible}.page-columns.toc-left .margin-caption,.page-columns.toc-left div.aside,.page-columns.toc-left aside,.page-columns.toc-left .column-margin{grid-column:body-content-start/body-content-end !important;z-index:998;transform:translate3d(0, 0, 0)}.page-columns.toc-left .no-row-height{height:initial;overflow:initial}}@media(max-width: 767.98px){.margin-caption,div.aside,aside,.column-margin{grid-column:body-content-start/body-content-end !important;z-index:998;transform:translate3d(0, 0, 0)}.no-row-height{height:initial;overflow:initial}#quarto-margin-sidebar{display:none}.hidden-sm{display:none}}.panel-grid{display:grid;grid-template-rows:repeat(1, 1fr);grid-template-columns:repeat(24, 1fr);gap:1em}.panel-grid .g-col-1{grid-column:auto/span 1}.panel-grid .g-col-2{grid-column:auto/span 2}.panel-grid .g-col-3{grid-column:auto/span 3}.panel-grid .g-col-4{grid-column:auto/span 4}.panel-grid .g-col-5{grid-column:auto/span 5}.panel-grid .g-col-6{grid-column:auto/span 6}.panel-grid .g-col-7{grid-column:auto/span 7}.panel-grid .g-col-8{grid-column:auto/span 8}.panel-grid .g-col-9{grid-column:auto/span 9}.panel-grid .g-col-10{grid-column:auto/span 10}.panel-grid .g-col-11{grid-column:auto/span 11}.panel-grid .g-col-12{grid-column:auto/span 12}.panel-grid .g-col-13{grid-column:auto/span 13}.panel-grid .g-col-14{grid-column:auto/span 14}.panel-grid .g-col-15{grid-column:auto/span 15}.panel-grid .g-col-16{grid-column:auto/span 16}.panel-grid .g-col-17{grid-column:auto/span 17}.panel-grid .g-col-18{grid-column:auto/span 18}.panel-grid .g-col-19{grid-column:auto/span 19}.panel-grid .g-col-20{grid-column:auto/span 20}.panel-grid .g-col-21{grid-column:auto/span 21}.panel-grid .g-col-22{grid-column:auto/span 22}.panel-grid .g-col-23{grid-column:auto/span 23}.panel-grid .g-col-24{grid-column:auto/span 24}.panel-grid .g-start-1{grid-column-start:1}.panel-grid .g-start-2{grid-column-start:2}.panel-grid .g-start-3{grid-column-start:3}.panel-grid .g-start-4{grid-column-start:4}.panel-grid .g-start-5{grid-column-start:5}.panel-grid .g-start-6{grid-column-start:6}.panel-grid .g-start-7{grid-column-start:7}.panel-grid .g-start-8{grid-column-start:8}.panel-grid .g-start-9{grid-column-start:9}.panel-grid .g-start-10{grid-column-start:10}.panel-grid .g-start-11{grid-column-start:11}.panel-grid .g-start-12{grid-column-start:12}.panel-grid .g-start-13{grid-column-start:13}.panel-grid .g-start-14{grid-column-start:14}.panel-grid .g-start-15{grid-column-start:15}.panel-grid .g-start-16{grid-column-start:16}.panel-grid .g-start-17{grid-column-start:17}.panel-grid .g-start-18{grid-column-start:18}.panel-grid .g-start-19{grid-column-start:19}.panel-grid .g-start-20{grid-column-start:20}.panel-grid .g-start-21{grid-column-start:21}.panel-grid .g-start-22{grid-column-start:22}.panel-grid .g-start-23{grid-column-start:23}@media(min-width: 576px){.panel-grid .g-col-sm-1{grid-column:auto/span 1}.panel-grid .g-col-sm-2{grid-column:auto/span 2}.panel-grid .g-col-sm-3{grid-column:auto/span 3}.panel-grid .g-col-sm-4{grid-column:auto/span 4}.panel-grid .g-col-sm-5{grid-column:auto/span 5}.panel-grid .g-col-sm-6{grid-column:auto/span 6}.panel-grid .g-col-sm-7{grid-column:auto/span 7}.panel-grid .g-col-sm-8{grid-column:auto/span 8}.panel-grid .g-col-sm-9{grid-column:auto/span 9}.panel-grid .g-col-sm-10{grid-column:auto/span 10}.panel-grid .g-col-sm-11{grid-column:auto/span 11}.panel-grid .g-col-sm-12{grid-column:auto/span 12}.panel-grid .g-col-sm-13{grid-column:auto/span 13}.panel-grid .g-col-sm-14{grid-column:auto/span 14}.panel-grid .g-col-sm-15{grid-column:auto/span 15}.panel-grid .g-col-sm-16{grid-column:auto/span 16}.panel-grid .g-col-sm-17{grid-column:auto/span 17}.panel-grid .g-col-sm-18{grid-column:auto/span 18}.panel-grid .g-col-sm-19{grid-column:auto/span 19}.panel-grid .g-col-sm-20{grid-column:auto/span 20}.panel-grid .g-col-sm-21{grid-column:auto/span 21}.panel-grid .g-col-sm-22{grid-column:auto/span 22}.panel-grid .g-col-sm-23{grid-column:auto/span 23}.panel-grid .g-col-sm-24{grid-column:auto/span 24}.panel-grid .g-start-sm-1{grid-column-start:1}.panel-grid .g-start-sm-2{grid-column-start:2}.panel-grid .g-start-sm-3{grid-column-start:3}.panel-grid .g-start-sm-4{grid-column-start:4}.panel-grid .g-start-sm-5{grid-column-start:5}.panel-grid .g-start-sm-6{grid-column-start:6}.panel-grid .g-start-sm-7{grid-column-start:7}.panel-grid .g-start-sm-8{grid-column-start:8}.panel-grid .g-start-sm-9{grid-column-start:9}.panel-grid .g-start-sm-10{grid-column-start:10}.panel-grid .g-start-sm-11{grid-column-start:11}.panel-grid .g-start-sm-12{grid-column-start:12}.panel-grid .g-start-sm-13{grid-column-start:13}.panel-grid .g-start-sm-14{grid-column-start:14}.panel-grid .g-start-sm-15{grid-column-start:15}.panel-grid .g-start-sm-16{grid-column-start:16}.panel-grid .g-start-sm-17{grid-column-start:17}.panel-grid .g-start-sm-18{grid-column-start:18}.panel-grid .g-start-sm-19{grid-column-start:19}.panel-grid .g-start-sm-20{grid-column-start:20}.panel-grid .g-start-sm-21{grid-column-start:21}.panel-grid .g-start-sm-22{grid-column-start:22}.panel-grid .g-start-sm-23{grid-column-start:23}}@media(min-width: 768px){.panel-grid .g-col-md-1{grid-column:auto/span 1}.panel-grid .g-col-md-2{grid-column:auto/span 2}.panel-grid .g-col-md-3{grid-column:auto/span 3}.panel-grid .g-col-md-4{grid-column:auto/span 4}.panel-grid .g-col-md-5{grid-column:auto/span 5}.panel-grid .g-col-md-6{grid-column:auto/span 6}.panel-grid .g-col-md-7{grid-column:auto/span 7}.panel-grid .g-col-md-8{grid-column:auto/span 8}.panel-grid .g-col-md-9{grid-column:auto/span 9}.panel-grid .g-col-md-10{grid-column:auto/span 10}.panel-grid .g-col-md-11{grid-column:auto/span 11}.panel-grid .g-col-md-12{grid-column:auto/span 12}.panel-grid .g-col-md-13{grid-column:auto/span 13}.panel-grid .g-col-md-14{grid-column:auto/span 14}.panel-grid .g-col-md-15{grid-column:auto/span 15}.panel-grid .g-col-md-16{grid-column:auto/span 16}.panel-grid .g-col-md-17{grid-column:auto/span 17}.panel-grid .g-col-md-18{grid-column:auto/span 18}.panel-grid .g-col-md-19{grid-column:auto/span 19}.panel-grid .g-col-md-20{grid-column:auto/span 20}.panel-grid .g-col-md-21{grid-column:auto/span 21}.panel-grid .g-col-md-22{grid-column:auto/span 22}.panel-grid .g-col-md-23{grid-column:auto/span 23}.panel-grid .g-col-md-24{grid-column:auto/span 24}.panel-grid .g-start-md-1{grid-column-start:1}.panel-grid .g-start-md-2{grid-column-start:2}.panel-grid .g-start-md-3{grid-column-start:3}.panel-grid .g-start-md-4{grid-column-start:4}.panel-grid .g-start-md-5{grid-column-start:5}.panel-grid .g-start-md-6{grid-column-start:6}.panel-grid .g-start-md-7{grid-column-start:7}.panel-grid .g-start-md-8{grid-column-start:8}.panel-grid .g-start-md-9{grid-column-start:9}.panel-grid .g-start-md-10{grid-column-start:10}.panel-grid .g-start-md-11{grid-column-start:11}.panel-grid .g-start-md-12{grid-column-start:12}.panel-grid .g-start-md-13{grid-column-start:13}.panel-grid .g-start-md-14{grid-column-start:14}.panel-grid .g-start-md-15{grid-column-start:15}.panel-grid .g-start-md-16{grid-column-start:16}.panel-grid .g-start-md-17{grid-column-start:17}.panel-grid .g-start-md-18{grid-column-start:18}.panel-grid .g-start-md-19{grid-column-start:19}.panel-grid .g-start-md-20{grid-column-start:20}.panel-grid .g-start-md-21{grid-column-start:21}.panel-grid .g-start-md-22{grid-column-start:22}.panel-grid .g-start-md-23{grid-column-start:23}}@media(min-width: 992px){.panel-grid .g-col-lg-1{grid-column:auto/span 1}.panel-grid .g-col-lg-2{grid-column:auto/span 2}.panel-grid .g-col-lg-3{grid-column:auto/span 3}.panel-grid .g-col-lg-4{grid-column:auto/span 4}.panel-grid .g-col-lg-5{grid-column:auto/span 5}.panel-grid .g-col-lg-6{grid-column:auto/span 6}.panel-grid .g-col-lg-7{grid-column:auto/span 7}.panel-grid .g-col-lg-8{grid-column:auto/span 8}.panel-grid .g-col-lg-9{grid-column:auto/span 9}.panel-grid .g-col-lg-10{grid-column:auto/span 10}.panel-grid .g-col-lg-11{grid-column:auto/span 11}.panel-grid .g-col-lg-12{grid-column:auto/span 12}.panel-grid .g-col-lg-13{grid-column:auto/span 13}.panel-grid .g-col-lg-14{grid-column:auto/span 14}.panel-grid .g-col-lg-15{grid-column:auto/span 15}.panel-grid .g-col-lg-16{grid-column:auto/span 16}.panel-grid .g-col-lg-17{grid-column:auto/span 17}.panel-grid .g-col-lg-18{grid-column:auto/span 18}.panel-grid .g-col-lg-19{grid-column:auto/span 19}.panel-grid .g-col-lg-20{grid-column:auto/span 20}.panel-grid .g-col-lg-21{grid-column:auto/span 21}.panel-grid .g-col-lg-22{grid-column:auto/span 22}.panel-grid .g-col-lg-23{grid-column:auto/span 23}.panel-grid .g-col-lg-24{grid-column:auto/span 24}.panel-grid .g-start-lg-1{grid-column-start:1}.panel-grid .g-start-lg-2{grid-column-start:2}.panel-grid .g-start-lg-3{grid-column-start:3}.panel-grid .g-start-lg-4{grid-column-start:4}.panel-grid .g-start-lg-5{grid-column-start:5}.panel-grid .g-start-lg-6{grid-column-start:6}.panel-grid .g-start-lg-7{grid-column-start:7}.panel-grid .g-start-lg-8{grid-column-start:8}.panel-grid .g-start-lg-9{grid-column-start:9}.panel-grid .g-start-lg-10{grid-column-start:10}.panel-grid .g-start-lg-11{grid-column-start:11}.panel-grid .g-start-lg-12{grid-column-start:12}.panel-grid .g-start-lg-13{grid-column-start:13}.panel-grid .g-start-lg-14{grid-column-start:14}.panel-grid .g-start-lg-15{grid-column-start:15}.panel-grid .g-start-lg-16{grid-column-start:16}.panel-grid .g-start-lg-17{grid-column-start:17}.panel-grid .g-start-lg-18{grid-column-start:18}.panel-grid .g-start-lg-19{grid-column-start:19}.panel-grid .g-start-lg-20{grid-column-start:20}.panel-grid .g-start-lg-21{grid-column-start:21}.panel-grid .g-start-lg-22{grid-column-start:22}.panel-grid .g-start-lg-23{grid-column-start:23}}@media(min-width: 1200px){.panel-grid .g-col-xl-1{grid-column:auto/span 1}.panel-grid .g-col-xl-2{grid-column:auto/span 2}.panel-grid .g-col-xl-3{grid-column:auto/span 3}.panel-grid .g-col-xl-4{grid-column:auto/span 4}.panel-grid .g-col-xl-5{grid-column:auto/span 5}.panel-grid .g-col-xl-6{grid-column:auto/span 6}.panel-grid .g-col-xl-7{grid-column:auto/span 7}.panel-grid .g-col-xl-8{grid-column:auto/span 8}.panel-grid .g-col-xl-9{grid-column:auto/span 9}.panel-grid .g-col-xl-10{grid-column:auto/span 10}.panel-grid .g-col-xl-11{grid-column:auto/span 11}.panel-grid .g-col-xl-12{grid-column:auto/span 12}.panel-grid .g-col-xl-13{grid-column:auto/span 13}.panel-grid .g-col-xl-14{grid-column:auto/span 14}.panel-grid .g-col-xl-15{grid-column:auto/span 15}.panel-grid .g-col-xl-16{grid-column:auto/span 16}.panel-grid .g-col-xl-17{grid-column:auto/span 17}.panel-grid .g-col-xl-18{grid-column:auto/span 18}.panel-grid .g-col-xl-19{grid-column:auto/span 19}.panel-grid .g-col-xl-20{grid-column:auto/span 20}.panel-grid .g-col-xl-21{grid-column:auto/span 21}.panel-grid .g-col-xl-22{grid-column:auto/span 22}.panel-grid .g-col-xl-23{grid-column:auto/span 23}.panel-grid .g-col-xl-24{grid-column:auto/span 24}.panel-grid .g-start-xl-1{grid-column-start:1}.panel-grid .g-start-xl-2{grid-column-start:2}.panel-grid .g-start-xl-3{grid-column-start:3}.panel-grid .g-start-xl-4{grid-column-start:4}.panel-grid .g-start-xl-5{grid-column-start:5}.panel-grid .g-start-xl-6{grid-column-start:6}.panel-grid .g-start-xl-7{grid-column-start:7}.panel-grid .g-start-xl-8{grid-column-start:8}.panel-grid .g-start-xl-9{grid-column-start:9}.panel-grid .g-start-xl-10{grid-column-start:10}.panel-grid .g-start-xl-11{grid-column-start:11}.panel-grid .g-start-xl-12{grid-column-start:12}.panel-grid .g-start-xl-13{grid-column-start:13}.panel-grid .g-start-xl-14{grid-column-start:14}.panel-grid .g-start-xl-15{grid-column-start:15}.panel-grid .g-start-xl-16{grid-column-start:16}.panel-grid .g-start-xl-17{grid-column-start:17}.panel-grid .g-start-xl-18{grid-column-start:18}.panel-grid .g-start-xl-19{grid-column-start:19}.panel-grid .g-start-xl-20{grid-column-start:20}.panel-grid .g-start-xl-21{grid-column-start:21}.panel-grid .g-start-xl-22{grid-column-start:22}.panel-grid .g-start-xl-23{grid-column-start:23}}@media(min-width: 1400px){.panel-grid .g-col-xxl-1{grid-column:auto/span 1}.panel-grid .g-col-xxl-2{grid-column:auto/span 2}.panel-grid .g-col-xxl-3{grid-column:auto/span 3}.panel-grid .g-col-xxl-4{grid-column:auto/span 4}.panel-grid .g-col-xxl-5{grid-column:auto/span 5}.panel-grid .g-col-xxl-6{grid-column:auto/span 6}.panel-grid .g-col-xxl-7{grid-column:auto/span 7}.panel-grid .g-col-xxl-8{grid-column:auto/span 8}.panel-grid .g-col-xxl-9{grid-column:auto/span 9}.panel-grid .g-col-xxl-10{grid-column:auto/span 10}.panel-grid .g-col-xxl-11{grid-column:auto/span 11}.panel-grid .g-col-xxl-12{grid-column:auto/span 12}.panel-grid .g-col-xxl-13{grid-column:auto/span 13}.panel-grid .g-col-xxl-14{grid-column:auto/span 14}.panel-grid .g-col-xxl-15{grid-column:auto/span 15}.panel-grid .g-col-xxl-16{grid-column:auto/span 16}.panel-grid .g-col-xxl-17{grid-column:auto/span 17}.panel-grid .g-col-xxl-18{grid-column:auto/span 18}.panel-grid .g-col-xxl-19{grid-column:auto/span 19}.panel-grid .g-col-xxl-20{grid-column:auto/span 20}.panel-grid .g-col-xxl-21{grid-column:auto/span 21}.panel-grid .g-col-xxl-22{grid-column:auto/span 22}.panel-grid .g-col-xxl-23{grid-column:auto/span 23}.panel-grid .g-col-xxl-24{grid-column:auto/span 24}.panel-grid .g-start-xxl-1{grid-column-start:1}.panel-grid .g-start-xxl-2{grid-column-start:2}.panel-grid .g-start-xxl-3{grid-column-start:3}.panel-grid .g-start-xxl-4{grid-column-start:4}.panel-grid .g-start-xxl-5{grid-column-start:5}.panel-grid .g-start-xxl-6{grid-column-start:6}.panel-grid .g-start-xxl-7{grid-column-start:7}.panel-grid .g-start-xxl-8{grid-column-start:8}.panel-grid .g-start-xxl-9{grid-column-start:9}.panel-grid .g-start-xxl-10{grid-column-start:10}.panel-grid .g-start-xxl-11{grid-column-start:11}.panel-grid .g-start-xxl-12{grid-column-start:12}.panel-grid .g-start-xxl-13{grid-column-start:13}.panel-grid .g-start-xxl-14{grid-column-start:14}.panel-grid .g-start-xxl-15{grid-column-start:15}.panel-grid .g-start-xxl-16{grid-column-start:16}.panel-grid .g-start-xxl-17{grid-column-start:17}.panel-grid .g-start-xxl-18{grid-column-start:18}.panel-grid .g-start-xxl-19{grid-column-start:19}.panel-grid .g-start-xxl-20{grid-column-start:20}.panel-grid .g-start-xxl-21{grid-column-start:21}.panel-grid .g-start-xxl-22{grid-column-start:22}.panel-grid .g-start-xxl-23{grid-column-start:23}}main{margin-top:1em;margin-bottom:1em}h1,.h1,h2,.h2{margin-top:2rem;margin-bottom:1rem}h1.title,.title.h1{margin-top:0}h2,.h2{border-bottom:1px solid #dee2e6;padding-bottom:.5rem}h3,.h3,h4,.h4{margin-top:1.5rem}.header-section-number{color:#747a7f}.nav-link.active .header-section-number{color:inherit}mark,.mark{padding:0em}.panel-caption,caption,.figure-caption{font-size:1rem}.panel-caption,.figure-caption,figcaption{color:#747a7f}.table-caption,caption{color:#373a3c}.quarto-layout-cell[data-ref-parent] caption{color:#747a7f}.column-margin figcaption,.margin-caption,div.aside,aside,.column-margin{color:#747a7f;font-size:.825rem}.panel-caption.margin-caption{text-align:inherit}.column-margin.column-container p{margin-bottom:0}.column-margin.column-container>*:not(.collapse){padding-top:.5em;padding-bottom:.5em;display:block}.column-margin.column-container>*.collapse:not(.show){display:none}@media(min-width: 768px){.column-margin.column-container .callout-margin-content:first-child{margin-top:4.5em}.column-margin.column-container .callout-margin-content-simple:first-child{margin-top:3.5em}}.margin-caption>*{padding-top:.5em;padding-bottom:.5em}@media(max-width: 767.98px){.quarto-layout-row{flex-direction:column}}.tab-content{margin-top:0px;border-left:#dee2e6 1px solid;border-right:#dee2e6 1px solid;border-bottom:#dee2e6 1px solid;margin-left:0;padding:1em;margin-bottom:1em}@media(max-width: 767.98px){.layout-sidebar{margin-left:0;margin-right:0}}.panel-sidebar,.panel-sidebar .form-control,.panel-input,.panel-input .form-control,.selectize-dropdown{font-size:.9rem}.panel-sidebar .form-control,.panel-input .form-control{padding-top:.1rem}.tab-pane div.sourceCode{margin-top:0px}.tab-pane>p{padding-top:1em}.tab-content>.tab-pane:not(.active){display:none !important}div.sourceCode{background-color:rgba(233,236,239,.65);border:1px solid rgba(233,236,239,.65);border-radius:.25rem}pre.sourceCode{background-color:transparent}pre.sourceCode{border:none;font-size:.875em;overflow:visible !important;padding:.4em}.callout pre.sourceCode{padding-left:0}div.sourceCode{overflow-y:hidden}.callout div.sourceCode{margin-left:initial}.blockquote{font-size:inherit;padding-left:1rem;padding-right:1.5rem;color:#747a7f}.blockquote h1:first-child,.blockquote .h1:first-child,.blockquote h2:first-child,.blockquote .h2:first-child,.blockquote h3:first-child,.blockquote .h3:first-child,.blockquote h4:first-child,.blockquote .h4:first-child,.blockquote h5:first-child,.blockquote .h5:first-child{margin-top:0}pre{background-color:initial;padding:initial;border:initial}p code:not(.sourceCode),li code:not(.sourceCode){background-color:#f7f7f7;padding:.2em}nav p code:not(.sourceCode),nav li code:not(.sourceCode){background-color:transparent;padding:0}#quarto-embedded-source-code-modal>.modal-dialog{max-width:1000px;padding-left:1.75rem;padding-right:1.75rem}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-body{padding:0}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-body div.sourceCode{margin:0;padding:.2rem .2rem;border-radius:0px;border:none}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-header{padding:.7rem}.code-tools-button{font-size:1rem;padding:.15rem .15rem;margin-left:5px;color:#6c757d;background-color:transparent;transition:initial;cursor:pointer}.code-tools-button>.bi::before{display:inline-block;height:1rem;width:1rem;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:1rem 1rem}.code-tools-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}#quarto-embedded-source-code-modal .code-copy-button>.bi::before{background-image:url('data:image/svg+xml,')}#quarto-embedded-source-code-modal .code-copy-button-checked>.bi::before{background-image:url('data:image/svg+xml,')}.sidebar{will-change:top;transition:top 200ms linear;position:sticky;overflow-y:auto;padding-top:1.2em;max-height:100vh}.sidebar.toc-left,.sidebar.margin-sidebar{top:0px;padding-top:1em}.sidebar.toc-left>*,.sidebar.margin-sidebar>*{padding-top:.5em}.sidebar.quarto-banner-title-block-sidebar>*{padding-top:1.65em}.sidebar nav[role=doc-toc]>h2,.sidebar nav[role=doc-toc]>.h2{font-size:.875rem;font-weight:400;margin-bottom:.5rem;margin-top:.3rem;font-family:inherit;border-bottom:0;padding-bottom:0;padding-top:0px}.sidebar nav[role=doc-toc]>ul a{border-left:1px solid #e9ecef;padding-left:.6rem}.sidebar nav[role=doc-toc]>ul a:empty{display:none}.sidebar nav[role=doc-toc] ul{padding-left:0;list-style:none;font-size:.875rem;font-weight:300}.sidebar nav[role=doc-toc]>ul li a{line-height:1.1rem;padding-bottom:.2rem;padding-top:.2rem;color:inherit}.sidebar nav[role=doc-toc] ul>li>ul>li>a{padding-left:1.2em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>a{padding-left:2.4em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>a{padding-left:3.6em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>ul>li>a{padding-left:4.8em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>ul>li>ul>li>a{padding-left:6em}.sidebar nav[role=doc-toc] ul>li>ul>li>a.active{border-left:1px solid #2780e3;color:#2780e3 !important}.sidebar nav[role=doc-toc] ul>li>a.active{border-left:1px solid #2780e3;color:#2780e3 !important}kbd,.kbd{color:#373a3c;background-color:#f8f9fa;border:1px solid;border-radius:5px;border-color:#dee2e6}div.hanging-indent{margin-left:1em;text-indent:-1em}.citation a,.footnote-ref{text-decoration:none}.footnotes ol{padding-left:1em}.tippy-content>*{margin-bottom:.7em}.tippy-content>*:last-child{margin-bottom:0}.table a{word-break:break-word}.table>:not(:first-child){border-top-width:1px;border-top-color:#dee2e6}.table>thead{border-bottom:1px solid currentColor}.table>tbody{border-top:1px solid #dee2e6}.callout{margin-top:1.25rem;margin-bottom:1.25rem;border-radius:.25rem}.callout.callout-style-simple{padding:.4em .7em;border-left:5px solid;border-right:1px solid #dee2e6;border-top:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.callout.callout-style-default{border-left:5px solid;border-right:1px solid #dee2e6;border-top:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.callout .callout-body-container{flex-grow:1}.callout.callout-style-simple .callout-body{font-size:.9rem;font-weight:400}.callout.callout-style-default .callout-body{font-size:.9rem;font-weight:400}.callout.callout-captioned .callout-body{margin-top:.2em}.callout:not(.no-icon).callout-captioned.callout-style-simple .callout-body{padding-left:1.6em}.callout.callout-captioned>.callout-header{padding-top:.2em;margin-bottom:-0.2em}.callout.callout-style-simple>div.callout-header{border-bottom:none;font-size:.9rem;font-weight:600;opacity:75%}.callout.callout-style-default>div.callout-header{border-bottom:none;font-weight:600;opacity:85%;font-size:.9rem;padding-left:.5em;padding-right:.5em}.callout.callout-style-default div.callout-body{padding-left:.5em;padding-right:.5em}.callout.callout-style-default div.callout-body>:first-child{margin-top:.5em}.callout>div.callout-header[data-bs-toggle=collapse]{cursor:pointer}.callout.callout-style-default .callout-header[aria-expanded=false],.callout.callout-style-default .callout-header[aria-expanded=true]{padding-top:0px;margin-bottom:0px;align-items:center}.callout.callout-captioned .callout-body>:last-child:not(.sourceCode),.callout.callout-captioned .callout-body>div>:last-child:not(.sourceCode){margin-bottom:.5rem}.callout:not(.callout-captioned) .callout-body>:first-child,.callout:not(.callout-captioned) .callout-body>div>:first-child{margin-top:.25rem}.callout:not(.callout-captioned) .callout-body>:last-child,.callout:not(.callout-captioned) .callout-body>div>:last-child{margin-bottom:.2rem}.callout.callout-style-simple .callout-icon::before,.callout.callout-style-simple .callout-toggle::before{height:1rem;width:1rem;display:inline-block;content:"";background-repeat:no-repeat;background-size:1rem 1rem}.callout.callout-style-default .callout-icon::before,.callout.callout-style-default .callout-toggle::before{height:.9rem;width:.9rem;display:inline-block;content:"";background-repeat:no-repeat;background-size:.9rem .9rem}.callout.callout-style-default .callout-toggle::before{margin-top:5px}.callout .callout-btn-toggle .callout-toggle::before{transition:transform .2s linear}.callout .callout-header[aria-expanded=false] .callout-toggle::before{transform:rotate(-90deg)}.callout .callout-header[aria-expanded=true] .callout-toggle::before{transform:none}.callout.callout-style-simple:not(.no-icon) div.callout-icon-container{padding-top:.2em;padding-right:.55em}.callout.callout-style-default:not(.no-icon) div.callout-icon-container{padding-top:.1em;padding-right:.35em}.callout.callout-style-default:not(.no-icon) div.callout-caption-container{margin-top:-1px}.callout.callout-style-default.callout-caution:not(.no-icon) div.callout-icon-container{padding-top:.3em;padding-right:.35em}.callout>.callout-body>.callout-icon-container>.no-icon,.callout>.callout-header>.callout-icon-container>.no-icon{display:none}div.callout.callout{border-left-color:#6c757d}div.callout.callout-style-default>.callout-header{background-color:#6c757d}div.callout-note.callout{border-left-color:#2780e3}div.callout-note.callout-style-default>.callout-header{background-color:#e9f2fc}div.callout-note:not(.callout-captioned) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-note.callout-captioned .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-note .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-tip.callout{border-left-color:#3fb618}div.callout-tip.callout-style-default>.callout-header{background-color:#ecf8e8}div.callout-tip:not(.callout-captioned) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-tip.callout-captioned .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-tip .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-warning.callout{border-left-color:#ff7518}div.callout-warning.callout-style-default>.callout-header{background-color:#fff1e8}div.callout-warning:not(.callout-captioned) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-warning.callout-captioned .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-warning .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-caution.callout{border-left-color:#f0ad4e}div.callout-caution.callout-style-default>.callout-header{background-color:#fef7ed}div.callout-caution:not(.callout-captioned) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-caution.callout-captioned .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-caution .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-important.callout{border-left-color:#ff0039}div.callout-important.callout-style-default>.callout-header{background-color:#ffe6eb}div.callout-important:not(.callout-captioned) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-important.callout-captioned .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-important .callout-toggle::before{background-image:url('data:image/svg+xml,')}.quarto-toggle-container{display:flex;align-items:center}@media(min-width: 992px){.navbar .quarto-color-scheme-toggle{padding-left:.5rem;padding-right:.5rem}}@media(max-width: 767.98px){.navbar .quarto-color-scheme-toggle{padding-left:0;padding-right:0;padding-bottom:.5em}}.quarto-reader-toggle .bi::before,.quarto-color-scheme-toggle .bi::before{display:inline-block;height:1rem;width:1rem;content:"";background-repeat:no-repeat;background-size:1rem 1rem}.navbar-collapse .quarto-color-scheme-toggle{padding-left:.6rem;padding-right:0;margin-top:-12px}.sidebar-navigation{padding-left:20px}.sidebar-navigation .quarto-color-scheme-toggle .bi::before{padding-top:.2rem;margin-bottom:-0.2rem}.sidebar-tools-main .quarto-color-scheme-toggle .bi::before{padding-top:.2rem;margin-bottom:-0.2rem}.navbar .quarto-color-scheme-toggle .bi::before{padding-top:7px;margin-bottom:-7px;padding-left:2px;margin-right:2px}.navbar .quarto-color-scheme-toggle:not(.alternate) .bi::before{background-image:url('data:image/svg+xml,')}.navbar .quarto-color-scheme-toggle.alternate .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-color-scheme-toggle:not(.alternate) .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-color-scheme-toggle.alternate .bi::before{background-image:url('data:image/svg+xml,')}.quarto-sidebar-toggle{border-color:#dee2e6;border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem;border-style:solid;border-width:1px;overflow:hidden;border-top-width:0px;padding-top:0px !important}.quarto-sidebar-toggle-title{cursor:pointer;padding-bottom:2px;margin-left:.25em;text-align:center;font-weight:400;font-size:.775em}#quarto-content .quarto-sidebar-toggle{background:#fafafa}#quarto-content .quarto-sidebar-toggle-title{color:#373a3c}.quarto-sidebar-toggle-icon{color:#dee2e6;margin-right:.5em;float:right;transition:transform .2s ease}.quarto-sidebar-toggle-icon::before{padding-top:5px}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-icon{transform:rotate(-180deg)}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-title{border-bottom:solid #dee2e6 1px}.quarto-sidebar-toggle-contents{background-color:#fff;padding-right:10px;padding-left:10px;margin-top:0px !important;transition:max-height .5s ease}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-contents{padding-top:1em;padding-bottom:10px}.quarto-sidebar-toggle:not(.expanded) .quarto-sidebar-toggle-contents{padding-top:0px !important;padding-bottom:0px}nav[role=doc-toc]{z-index:1020}#quarto-sidebar>*,nav[role=doc-toc]>*{transition:opacity .1s ease,border .1s ease}#quarto-sidebar.slow>*,nav[role=doc-toc].slow>*{transition:opacity .4s ease,border .4s ease}.quarto-color-scheme-toggle:not(.alternate).top-right .bi::before{background-image:url('data:image/svg+xml,')}.quarto-color-scheme-toggle.alternate.top-right .bi::before{background-image:url('data:image/svg+xml,')}#quarto-appendix.default{border-top:1px solid #dee2e6}#quarto-appendix.default{background-color:#fff;padding-top:1.5em;margin-top:2em;z-index:998}#quarto-appendix.default .quarto-appendix-heading{margin-top:0;line-height:1.4em;font-weight:600;opacity:.9;border-bottom:none;margin-bottom:0}#quarto-appendix.default .footnotes ol,#quarto-appendix.default .footnotes ol li>p:last-of-type,#quarto-appendix.default .quarto-appendix-contents>p:last-of-type{margin-bottom:0}#quarto-appendix.default .quarto-appendix-secondary-label{margin-bottom:.4em}#quarto-appendix.default .quarto-appendix-bibtex{font-size:.7em;padding:1em;border:solid 1px #dee2e6;margin-bottom:1em}#quarto-appendix.default .quarto-appendix-bibtex code.sourceCode{white-space:pre-wrap}#quarto-appendix.default .quarto-appendix-citeas{font-size:.9em;padding:1em;border:solid 1px #dee2e6;margin-bottom:1em}#quarto-appendix.default .quarto-appendix-heading{font-size:1em !important}#quarto-appendix.default *[role=doc-endnotes]>ol,#quarto-appendix.default .quarto-appendix-contents>*:not(h2):not(.h2){font-size:.9em}#quarto-appendix.default section{padding-bottom:1.5em}#quarto-appendix.default section *[role=doc-endnotes],#quarto-appendix.default section>*:not(a){opacity:.9;word-wrap:break-word}.btn.btn-quarto,div.cell-output-display .btn-quarto{color:#cbcccc;background-color:#373a3c;border-color:#373a3c}.btn.btn-quarto:hover,div.cell-output-display .btn-quarto:hover{color:#cbcccc;background-color:#555859;border-color:#4b4e50}.btn-check:focus+.btn.btn-quarto,.btn.btn-quarto:focus,.btn-check:focus+div.cell-output-display .btn-quarto,div.cell-output-display .btn-quarto:focus{color:#cbcccc;background-color:#555859;border-color:#4b4e50;box-shadow:0 0 0 .25rem rgba(77,80,82,.5)}.btn-check:checked+.btn.btn-quarto,.btn-check:active+.btn.btn-quarto,.btn.btn-quarto:active,.btn.btn-quarto.active,.show>.btn.btn-quarto.dropdown-toggle,.btn-check:checked+div.cell-output-display .btn-quarto,.btn-check:active+div.cell-output-display .btn-quarto,div.cell-output-display .btn-quarto:active,div.cell-output-display .btn-quarto.active,.show>div.cell-output-display .btn-quarto.dropdown-toggle{color:#fff;background-color:#5f6163;border-color:#4b4e50}.btn-check:checked+.btn.btn-quarto:focus,.btn-check:active+.btn.btn-quarto:focus,.btn.btn-quarto:active:focus,.btn.btn-quarto.active:focus,.show>.btn.btn-quarto.dropdown-toggle:focus,.btn-check:checked+div.cell-output-display .btn-quarto:focus,.btn-check:active+div.cell-output-display .btn-quarto:focus,div.cell-output-display .btn-quarto:active:focus,div.cell-output-display .btn-quarto.active:focus,.show>div.cell-output-display .btn-quarto.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(77,80,82,.5)}.btn.btn-quarto:disabled,.btn.btn-quarto.disabled,div.cell-output-display .btn-quarto:disabled,div.cell-output-display .btn-quarto.disabled{color:#fff;background-color:#373a3c;border-color:#373a3c}nav.quarto-secondary-nav.color-navbar{background-color:#2780e3;color:#fdfeff}nav.quarto-secondary-nav.color-navbar h1,nav.quarto-secondary-nav.color-navbar .h1,nav.quarto-secondary-nav.color-navbar .quarto-btn-toggle{color:#fdfeff}@media(max-width: 991.98px){body.nav-sidebar .quarto-title-banner,body.nav-sidebar .quarto-title-banner{display:none}}p.subtitle{margin-top:.25em;margin-bottom:.5em}code a:any-link{color:inherit;text-decoration-color:#6c757d}/*! light */div.observablehq table thead tr th{background-color:var(--bs-body-bg)}input,button,select,optgroup,textarea{background-color:var(--bs-body-bg)}@media print{.page-columns .column-screen-inset{grid-column:page-start-inset/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset table{background:#fff}.page-columns .column-screen-inset-left{grid-column:page-start-inset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-left table{background:#fff}.page-columns .column-screen-inset-right{grid-column:body-content-start/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-right table{background:#fff}.page-columns .column-screen{grid-column:page-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen table{background:#fff}.page-columns .column-screen-left{grid-column:page-start/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-left table{background:#fff}.page-columns .column-screen-right{grid-column:body-content-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-right table{background:#fff}.page-columns .column-screen-inset-shaded{grid-column:page-start-inset/page-end-inset;padding:1em;background:#f8f9fa;z-index:998;transform:translate3d(0, 0, 0);margin-bottom:1em}}.quarto-video{margin-bottom:1em}a.external:after{display:inline-block;height:.75rem;width:.75rem;margin-bottom:.15em;margin-left:.25em;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:.75rem .75rem}a.external:after:hover{cursor:pointer}.quarto-ext-icon{display:inline-block;font-size:.75em;padding-left:.3em}.code-with-filename .code-with-filename-file{margin-bottom:0;padding-bottom:2px;padding-top:2px;padding-left:.7em;border:var(--quarto-border-width) solid var(--quarto-border-color);border-radius:var(--quarto-border-radius);border-bottom:0;border-bottom-left-radius:0%;border-bottom-right-radius:0%}.code-with-filename div.sourceCode,.reveal .code-with-filename div.sourceCode{margin-top:0;border-top-left-radius:0%;border-top-right-radius:0%}.code-with-filename .code-with-filename-file pre{margin-bottom:0}.code-with-filename .code-with-filename-file,.code-with-filename .code-with-filename-file pre{background-color:rgba(219,219,219,.8)}.quarto-dark .code-with-filename .code-with-filename-file,.quarto-dark .code-with-filename .code-with-filename-file pre{background-color:#555}.code-with-filename .code-with-filename-file strong{font-weight:400}.quarto-title-banner{margin-bottom:1em;color:#fdfeff;background:#2780e3}.quarto-title-banner .code-tools-button{color:#97cbff}.quarto-title-banner .code-tools-button:hover{color:#fdfeff}.quarto-title-banner .code-tools-button>.bi::before{background-image:url('data:image/svg+xml,')}.quarto-title-banner .code-tools-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}.quarto-title-banner .quarto-title .title{font-weight:600}.quarto-title-banner .quarto-categories{margin-top:.75em}@media(min-width: 992px){.quarto-title-banner{padding-top:2.5em;padding-bottom:2.5em}}@media(max-width: 991.98px){.quarto-title-banner{padding-top:1em;padding-bottom:1em}}main.quarto-banner-title-block section:first-of-type h2:first-of-type,main.quarto-banner-title-block section:first-of-type .h2:first-of-type,main.quarto-banner-title-block section:first-of-type h3:first-of-type,main.quarto-banner-title-block section:first-of-type .h3:first-of-type,main.quarto-banner-title-block section:first-of-type h4:first-of-type,main.quarto-banner-title-block section:first-of-type .h4:first-of-type{margin-top:0}.quarto-title .quarto-categories{display:flex;column-gap:.4em;padding-bottom:.5em;margin-top:.75em}.quarto-title .quarto-categories .quarto-category{padding:.25em .75em;font-size:.65em;text-transform:uppercase;border:solid 1px;border-radius:.25rem;opacity:.6}.quarto-title .quarto-categories .quarto-category a{color:inherit}#title-block-header.quarto-title-block.default .quarto-title-meta{display:grid;grid-template-columns:repeat(2, 1fr)}#title-block-header.quarto-title-block.default .quarto-title .title{margin-bottom:0}#title-block-header.quarto-title-block.default .quarto-title-author-orcid img{margin-top:-5px}#title-block-header.quarto-title-block.default .quarto-description p:last-of-type{margin-bottom:0}#title-block-header.quarto-title-block.default .quarto-title-meta-contents p,#title-block-header.quarto-title-block.default .quarto-title-authors p,#title-block-header.quarto-title-block.default .quarto-title-affiliations p{margin-bottom:.1em}#title-block-header.quarto-title-block.default .quarto-title-meta-heading{text-transform:uppercase;margin-top:1em;font-size:.8em;opacity:.8;font-weight:400}#title-block-header.quarto-title-block.default .quarto-title-meta-contents{font-size:.9em}#title-block-header.quarto-title-block.default .quarto-title-meta-contents a{color:#373a3c}#title-block-header.quarto-title-block.default .quarto-title-meta-contents p.affiliation:last-of-type{margin-bottom:.7em}#title-block-header.quarto-title-block.default p.affiliation{margin-bottom:.1em}#title-block-header.quarto-title-block.default .description,#title-block-header.quarto-title-block.default .abstract{margin-top:0}#title-block-header.quarto-title-block.default .description>p,#title-block-header.quarto-title-block.default .abstract>p{font-size:.9em}#title-block-header.quarto-title-block.default .description>p:last-of-type,#title-block-header.quarto-title-block.default .abstract>p:last-of-type{margin-bottom:0}#title-block-header.quarto-title-block.default .description .abstract-title,#title-block-header.quarto-title-block.default .abstract .abstract-title{margin-top:1em;text-transform:uppercase;font-size:.8em;opacity:.8;font-weight:400}#title-block-header.quarto-title-block.default .quarto-title-meta-author{display:grid;grid-template-columns:1fr 1fr}body{-webkit-font-smoothing:antialiased}.badge.bg-light{color:#373a3c}.progress .progress-bar{font-size:8px;line-height:8px}/*# sourceMappingURL=d6b77e37a12f878a50f9f8a85e535bdc.css.map */ diff --git a/_proc/_docs/site_libs/bootstrap/bootstrap.min.js b/_proc/_docs/site_libs/bootstrap/bootstrap.min.js deleted file mode 100644 index cc0a255..0000000 --- a/_proc/_docs/site_libs/bootstrap/bootstrap.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Bootstrap v5.1.3 (https://getbootstrap.com/) - * Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t="transitionend",e=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return e},i=t=>{const i=e(t);return i&&document.querySelector(i)?i:null},n=t=>{const i=e(t);return i?document.querySelector(i):null},s=e=>{e.dispatchEvent(new Event(t))},o=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),r=t=>o(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(t):null,a=(t,e,i)=>{Object.keys(i).forEach((n=>{const s=i[n],r=e[n],a=r&&o(r)?"element":null==(l=r)?`${l}`:{}.toString.call(l).match(/\s([a-z]+)/i)[1].toLowerCase();var l;if(!new RegExp(s).test(a))throw new TypeError(`${t.toUpperCase()}: Option "${n}" provided type "${a}" but expected type "${s}".`)}))},l=t=>!(!o(t)||0===t.getClientRects().length)&&"visible"===getComputedStyle(t).getPropertyValue("visibility"),c=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),h=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?h(t.parentNode):null},d=()=>{},u=t=>{t.offsetHeight},f=()=>{const{jQuery:t}=window;return t&&!document.body.hasAttribute("data-bs-no-jquery")?t:null},p=[],m=()=>"rtl"===document.documentElement.dir,g=t=>{var e;e=()=>{const e=f();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(p.length||document.addEventListener("DOMContentLoaded",(()=>{p.forEach((t=>t()))})),p.push(e)):e()},_=t=>{"function"==typeof t&&t()},b=(e,i,n=!0)=>{if(!n)return void _(e);const o=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(i)+5;let r=!1;const a=({target:n})=>{n===i&&(r=!0,i.removeEventListener(t,a),_(e))};i.addEventListener(t,a),setTimeout((()=>{r||s(i)}),o)},v=(t,e,i,n)=>{let s=t.indexOf(e);if(-1===s)return t[!i&&n?t.length-1:0];const o=t.length;return s+=i?1:-1,n&&(s=(s+o)%o),t[Math.max(0,Math.min(s,o-1))]},y=/[^.]*(?=\..*)\.|.*/,w=/\..*/,E=/::\d+$/,A={};let T=1;const O={mouseenter:"mouseover",mouseleave:"mouseout"},C=/^(mouseenter|mouseleave)/i,k=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function L(t,e){return e&&`${e}::${T++}`||t.uidEvent||T++}function x(t){const e=L(t);return t.uidEvent=e,A[e]=A[e]||{},A[e]}function D(t,e,i=null){const n=Object.keys(t);for(let s=0,o=n.length;sfunction(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};n?n=t(n):i=t(i)}const[o,r,a]=S(e,i,n),l=x(t),c=l[a]||(l[a]={}),h=D(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=L(r,e.replace(y,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(let a=o.length;a--;)if(o[a]===r)return s.delegateTarget=r,n.oneOff&&j.off(t,s.type,e,i),i.apply(r,[s]);return null}}(t,i,n):function(t,e){return function i(n){return n.delegateTarget=t,i.oneOff&&j.off(t,n.type,e),e.apply(t,[n])}}(t,i);u.delegationSelector=o?i:null,u.originalHandler=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function I(t,e,i,n,s){const o=D(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function P(t){return t=t.replace(w,""),O[t]||t}const j={on(t,e,i,n){N(t,e,i,n,!1)},one(t,e,i,n){N(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=S(e,i,n),a=r!==e,l=x(t),c=e.startsWith(".");if(void 0!==o){if(!l||!l[r])return;return void I(t,l,r,o,s?i:null)}c&&Object.keys(l).forEach((i=>{!function(t,e,i,n){const s=e[i]||{};Object.keys(s).forEach((o=>{if(o.includes(n)){const n=s[o];I(t,e,i,n.originalHandler,n.delegationSelector)}}))}(t,l,i,e.slice(1))}));const h=l[r]||{};Object.keys(h).forEach((i=>{const n=i.replace(E,"");if(!a||e.includes(n)){const e=h[i];I(t,l,r,e.originalHandler,e.delegationSelector)}}))},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=f(),s=P(e),o=e!==s,r=k.has(s);let a,l=!0,c=!0,h=!1,d=null;return o&&n&&(a=n.Event(e,i),n(t).trigger(a),l=!a.isPropagationStopped(),c=!a.isImmediatePropagationStopped(),h=a.isDefaultPrevented()),r?(d=document.createEvent("HTMLEvents"),d.initEvent(s,l,!0)):d=new CustomEvent(e,{bubbles:l,cancelable:!0}),void 0!==i&&Object.keys(i).forEach((t=>{Object.defineProperty(d,t,{get:()=>i[t]})})),h&&d.preventDefault(),c&&t.dispatchEvent(d),d.defaultPrevented&&void 0!==a&&a.preventDefault(),d}},M=new Map,H={set(t,e,i){M.has(t)||M.set(t,new Map);const n=M.get(t);n.has(e)||0===n.size?n.set(e,i):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(n.keys())[0]}.`)},get:(t,e)=>M.has(t)&&M.get(t).get(e)||null,remove(t,e){if(!M.has(t))return;const i=M.get(t);i.delete(e),0===i.size&&M.delete(t)}};class B{constructor(t){(t=r(t))&&(this._element=t,H.set(this._element,this.constructor.DATA_KEY,this))}dispose(){H.remove(this._element,this.constructor.DATA_KEY),j.off(this._element,this.constructor.EVENT_KEY),Object.getOwnPropertyNames(this).forEach((t=>{this[t]=null}))}_queueCallback(t,e,i=!0){b(t,e,i)}static getInstance(t){return H.get(r(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.1.3"}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}}const R=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,s=t.NAME;j.on(document,i,`[data-bs-dismiss="${s}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),c(this))return;const o=n(this)||this.closest(`.${s}`);t.getOrCreateInstance(o)[e]()}))};class W extends B{static get NAME(){return"alert"}close(){if(j.trigger(this._element,"close.bs.alert").defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),j.trigger(this._element,"closed.bs.alert"),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=W.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}R(W,"close"),g(W);const $='[data-bs-toggle="button"]';class z extends B{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=z.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}function q(t){return"true"===t||"false"!==t&&(t===Number(t).toString()?Number(t):""===t||"null"===t?null:t)}function F(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}j.on(document,"click.bs.button.data-api",$,(t=>{t.preventDefault();const e=t.target.closest($);z.getOrCreateInstance(e).toggle()})),g(z);const U={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${F(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${F(e)}`)},getDataAttributes(t){if(!t)return{};const e={};return Object.keys(t.dataset).filter((t=>t.startsWith("bs"))).forEach((i=>{let n=i.replace(/^bs/,"");n=n.charAt(0).toLowerCase()+n.slice(1,n.length),e[n]=q(t.dataset[i])})),e},getDataAttribute:(t,e)=>q(t.getAttribute(`data-bs-${F(e)}`)),offset(t){const e=t.getBoundingClientRect();return{top:e.top+window.pageYOffset,left:e.left+window.pageXOffset}},position:t=>({top:t.offsetTop,left:t.offsetLeft})},V={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let n=t.parentNode;for(;n&&n.nodeType===Node.ELEMENT_NODE&&3!==n.nodeType;)n.matches(e)&&i.push(n),n=n.parentNode;return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(", ");return this.find(e,t).filter((t=>!c(t)&&l(t)))}},K="carousel",X={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0,touch:!0},Y={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean",touch:"boolean"},Q="next",G="prev",Z="left",J="right",tt={ArrowLeft:J,ArrowRight:Z},et="slid.bs.carousel",it="active",nt=".active.carousel-item";class st extends B{constructor(t,e){super(t),this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this.touchStartX=0,this.touchDeltaX=0,this._config=this._getConfig(e),this._indicatorsElement=V.findOne(".carousel-indicators",this._element),this._touchSupported="ontouchstart"in document.documentElement||navigator.maxTouchPoints>0,this._pointerEvent=Boolean(window.PointerEvent),this._addEventListeners()}static get Default(){return X}static get NAME(){return K}next(){this._slide(Q)}nextWhenVisible(){!document.hidden&&l(this._element)&&this.next()}prev(){this._slide(G)}pause(t){t||(this._isPaused=!0),V.findOne(".carousel-item-next, .carousel-item-prev",this._element)&&(s(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null}cycle(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config&&this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))}to(t){this._activeElement=V.findOne(nt,this._element);const e=this._getItemIndex(this._activeElement);if(t>this._items.length-1||t<0)return;if(this._isSliding)return void j.one(this._element,et,(()=>this.to(t)));if(e===t)return this.pause(),void this.cycle();const i=t>e?Q:G;this._slide(i,this._items[t])}_getConfig(t){return t={...X,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(K,t,Y),t}_handleSwipe(){const t=Math.abs(this.touchDeltaX);if(t<=40)return;const e=t/this.touchDeltaX;this.touchDeltaX=0,e&&this._slide(e>0?J:Z)}_addEventListeners(){this._config.keyboard&&j.on(this._element,"keydown.bs.carousel",(t=>this._keydown(t))),"hover"===this._config.pause&&(j.on(this._element,"mouseenter.bs.carousel",(t=>this.pause(t))),j.on(this._element,"mouseleave.bs.carousel",(t=>this.cycle(t)))),this._config.touch&&this._touchSupported&&this._addTouchEventListeners()}_addTouchEventListeners(){const t=t=>this._pointerEvent&&("pen"===t.pointerType||"touch"===t.pointerType),e=e=>{t(e)?this.touchStartX=e.clientX:this._pointerEvent||(this.touchStartX=e.touches[0].clientX)},i=t=>{this.touchDeltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this.touchStartX},n=e=>{t(e)&&(this.touchDeltaX=e.clientX-this.touchStartX),this._handleSwipe(),"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((t=>this.cycle(t)),500+this._config.interval))};V.find(".carousel-item img",this._element).forEach((t=>{j.on(t,"dragstart.bs.carousel",(t=>t.preventDefault()))})),this._pointerEvent?(j.on(this._element,"pointerdown.bs.carousel",(t=>e(t))),j.on(this._element,"pointerup.bs.carousel",(t=>n(t))),this._element.classList.add("pointer-event")):(j.on(this._element,"touchstart.bs.carousel",(t=>e(t))),j.on(this._element,"touchmove.bs.carousel",(t=>i(t))),j.on(this._element,"touchend.bs.carousel",(t=>n(t))))}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=tt[t.key];e&&(t.preventDefault(),this._slide(e))}_getItemIndex(t){return this._items=t&&t.parentNode?V.find(".carousel-item",t.parentNode):[],this._items.indexOf(t)}_getItemByOrder(t,e){const i=t===Q;return v(this._items,e,i,this._config.wrap)}_triggerSlideEvent(t,e){const i=this._getItemIndex(t),n=this._getItemIndex(V.findOne(nt,this._element));return j.trigger(this._element,"slide.bs.carousel",{relatedTarget:t,direction:e,from:n,to:i})}_setActiveIndicatorElement(t){if(this._indicatorsElement){const e=V.findOne(".active",this._indicatorsElement);e.classList.remove(it),e.removeAttribute("aria-current");const i=V.find("[data-bs-target]",this._indicatorsElement);for(let e=0;e{j.trigger(this._element,et,{relatedTarget:o,direction:d,from:s,to:r})};if(this._element.classList.contains("slide")){o.classList.add(h),u(o),n.classList.add(c),o.classList.add(c);const t=()=>{o.classList.remove(c,h),o.classList.add(it),n.classList.remove(it,h,c),this._isSliding=!1,setTimeout(f,0)};this._queueCallback(t,n,!0)}else n.classList.remove(it),o.classList.add(it),this._isSliding=!1,f();a&&this.cycle()}_directionToOrder(t){return[J,Z].includes(t)?m()?t===Z?G:Q:t===Z?Q:G:t}_orderToDirection(t){return[Q,G].includes(t)?m()?t===G?Z:J:t===G?J:Z:t}static carouselInterface(t,e){const i=st.getOrCreateInstance(t,e);let{_config:n}=i;"object"==typeof e&&(n={...n,...e});const s="string"==typeof e?e:n.slide;if("number"==typeof e)i.to(e);else if("string"==typeof s){if(void 0===i[s])throw new TypeError(`No method named "${s}"`);i[s]()}else n.interval&&n.ride&&(i.pause(),i.cycle())}static jQueryInterface(t){return this.each((function(){st.carouselInterface(this,t)}))}static dataApiClickHandler(t){const e=n(this);if(!e||!e.classList.contains("carousel"))return;const i={...U.getDataAttributes(e),...U.getDataAttributes(this)},s=this.getAttribute("data-bs-slide-to");s&&(i.interval=!1),st.carouselInterface(e,i),s&&st.getInstance(e).to(s),t.preventDefault()}}j.on(document,"click.bs.carousel.data-api","[data-bs-slide], [data-bs-slide-to]",st.dataApiClickHandler),j.on(window,"load.bs.carousel.data-api",(()=>{const t=V.find('[data-bs-ride="carousel"]');for(let e=0,i=t.length;et===this._element));null!==s&&o.length&&(this._selector=s,this._triggerArray.push(e))}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return rt}static get NAME(){return ot}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t,e=[];if(this._config.parent){const t=V.find(ut,this._config.parent);e=V.find(".collapse.show, .collapse.collapsing",this._config.parent).filter((e=>!t.includes(e)))}const i=V.findOne(this._selector);if(e.length){const n=e.find((t=>i!==t));if(t=n?pt.getInstance(n):null,t&&t._isTransitioning)return}if(j.trigger(this._element,"show.bs.collapse").defaultPrevented)return;e.forEach((e=>{i!==e&&pt.getOrCreateInstance(e,{toggle:!1}).hide(),t||H.set(e,"bs.collapse",null)}));const n=this._getDimension();this._element.classList.remove(ct),this._element.classList.add(ht),this._element.style[n]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const s=`scroll${n[0].toUpperCase()+n.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(ht),this._element.classList.add(ct,lt),this._element.style[n]="",j.trigger(this._element,"shown.bs.collapse")}),this._element,!0),this._element.style[n]=`${this._element[s]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(j.trigger(this._element,"hide.bs.collapse").defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,u(this._element),this._element.classList.add(ht),this._element.classList.remove(ct,lt);const e=this._triggerArray.length;for(let t=0;t{this._isTransitioning=!1,this._element.classList.remove(ht),this._element.classList.add(ct),j.trigger(this._element,"hidden.bs.collapse")}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(lt)}_getConfig(t){return(t={...rt,...U.getDataAttributes(this._element),...t}).toggle=Boolean(t.toggle),t.parent=r(t.parent),a(ot,t,at),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=V.find(ut,this._config.parent);V.find(ft,this._config.parent).filter((e=>!t.includes(e))).forEach((t=>{const e=n(t);e&&this._addAriaAndCollapsedClass([t],this._isShown(e))}))}_addAriaAndCollapsedClass(t,e){t.length&&t.forEach((t=>{e?t.classList.remove(dt):t.classList.add(dt),t.setAttribute("aria-expanded",e)}))}static jQueryInterface(t){return this.each((function(){const e={};"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1);const i=pt.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}j.on(document,"click.bs.collapse.data-api",ft,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();const e=i(this);V.find(e).forEach((t=>{pt.getOrCreateInstance(t,{toggle:!1}).toggle()}))})),g(pt);var mt="top",gt="bottom",_t="right",bt="left",vt="auto",yt=[mt,gt,_t,bt],wt="start",Et="end",At="clippingParents",Tt="viewport",Ot="popper",Ct="reference",kt=yt.reduce((function(t,e){return t.concat([e+"-"+wt,e+"-"+Et])}),[]),Lt=[].concat(yt,[vt]).reduce((function(t,e){return t.concat([e,e+"-"+wt,e+"-"+Et])}),[]),xt="beforeRead",Dt="read",St="afterRead",Nt="beforeMain",It="main",Pt="afterMain",jt="beforeWrite",Mt="write",Ht="afterWrite",Bt=[xt,Dt,St,Nt,It,Pt,jt,Mt,Ht];function Rt(t){return t?(t.nodeName||"").toLowerCase():null}function Wt(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function $t(t){return t instanceof Wt(t).Element||t instanceof Element}function zt(t){return t instanceof Wt(t).HTMLElement||t instanceof HTMLElement}function qt(t){return"undefined"!=typeof ShadowRoot&&(t instanceof Wt(t).ShadowRoot||t instanceof ShadowRoot)}const Ft={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];zt(s)&&Rt(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});zt(n)&&Rt(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function Ut(t){return t.split("-")[0]}function Vt(t,e){var i=t.getBoundingClientRect();return{width:i.width/1,height:i.height/1,top:i.top/1,right:i.right/1,bottom:i.bottom/1,left:i.left/1,x:i.left/1,y:i.top/1}}function Kt(t){var e=Vt(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function Xt(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&qt(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function Yt(t){return Wt(t).getComputedStyle(t)}function Qt(t){return["table","td","th"].indexOf(Rt(t))>=0}function Gt(t){return(($t(t)?t.ownerDocument:t.document)||window.document).documentElement}function Zt(t){return"html"===Rt(t)?t:t.assignedSlot||t.parentNode||(qt(t)?t.host:null)||Gt(t)}function Jt(t){return zt(t)&&"fixed"!==Yt(t).position?t.offsetParent:null}function te(t){for(var e=Wt(t),i=Jt(t);i&&Qt(i)&&"static"===Yt(i).position;)i=Jt(i);return i&&("html"===Rt(i)||"body"===Rt(i)&&"static"===Yt(i).position)?e:i||function(t){var e=-1!==navigator.userAgent.toLowerCase().indexOf("firefox");if(-1!==navigator.userAgent.indexOf("Trident")&&zt(t)&&"fixed"===Yt(t).position)return null;for(var i=Zt(t);zt(i)&&["html","body"].indexOf(Rt(i))<0;){var n=Yt(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function ee(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}var ie=Math.max,ne=Math.min,se=Math.round;function oe(t,e,i){return ie(t,ne(e,i))}function re(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function ae(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const le={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,n=t.name,s=t.options,o=i.elements.arrow,r=i.modifiersData.popperOffsets,a=Ut(i.placement),l=ee(a),c=[bt,_t].indexOf(a)>=0?"height":"width";if(o&&r){var h=function(t,e){return re("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:ae(t,yt))}(s.padding,i),d=Kt(o),u="y"===l?mt:bt,f="y"===l?gt:_t,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],m=r[l]-i.rects.reference[l],g=te(o),_=g?"y"===l?g.clientHeight||0:g.clientWidth||0:0,b=p/2-m/2,v=h[u],y=_-d[c]-h[f],w=_/2-d[c]/2+b,E=oe(v,w,y),A=l;i.modifiersData[n]=((e={})[A]=E,e.centerOffset=E-w,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&Xt(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function ce(t){return t.split("-")[1]}var he={top:"auto",right:"auto",bottom:"auto",left:"auto"};function de(t){var e,i=t.popper,n=t.popperRect,s=t.placement,o=t.variation,r=t.offsets,a=t.position,l=t.gpuAcceleration,c=t.adaptive,h=t.roundOffsets,d=!0===h?function(t){var e=t.x,i=t.y,n=window.devicePixelRatio||1;return{x:se(se(e*n)/n)||0,y:se(se(i*n)/n)||0}}(r):"function"==typeof h?h(r):r,u=d.x,f=void 0===u?0:u,p=d.y,m=void 0===p?0:p,g=r.hasOwnProperty("x"),_=r.hasOwnProperty("y"),b=bt,v=mt,y=window;if(c){var w=te(i),E="clientHeight",A="clientWidth";w===Wt(i)&&"static"!==Yt(w=Gt(i)).position&&"absolute"===a&&(E="scrollHeight",A="scrollWidth"),w=w,s!==mt&&(s!==bt&&s!==_t||o!==Et)||(v=gt,m-=w[E]-n.height,m*=l?1:-1),s!==bt&&(s!==mt&&s!==gt||o!==Et)||(b=_t,f-=w[A]-n.width,f*=l?1:-1)}var T,O=Object.assign({position:a},c&&he);return l?Object.assign({},O,((T={})[v]=_?"0":"",T[b]=g?"0":"",T.transform=(y.devicePixelRatio||1)<=1?"translate("+f+"px, "+m+"px)":"translate3d("+f+"px, "+m+"px, 0)",T)):Object.assign({},O,((e={})[v]=_?m+"px":"",e[b]=g?f+"px":"",e.transform="",e))}const ue={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:Ut(e.placement),variation:ce(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,de(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,de(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var fe={passive:!0};const pe={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=Wt(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,fe)})),a&&l.addEventListener("resize",i.update,fe),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,fe)})),a&&l.removeEventListener("resize",i.update,fe)}},data:{}};var me={left:"right",right:"left",bottom:"top",top:"bottom"};function ge(t){return t.replace(/left|right|bottom|top/g,(function(t){return me[t]}))}var _e={start:"end",end:"start"};function be(t){return t.replace(/start|end/g,(function(t){return _e[t]}))}function ve(t){var e=Wt(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function ye(t){return Vt(Gt(t)).left+ve(t).scrollLeft}function we(t){var e=Yt(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function Ee(t){return["html","body","#document"].indexOf(Rt(t))>=0?t.ownerDocument.body:zt(t)&&we(t)?t:Ee(Zt(t))}function Ae(t,e){var i;void 0===e&&(e=[]);var n=Ee(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=Wt(n),r=s?[o].concat(o.visualViewport||[],we(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(Ae(Zt(r)))}function Te(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function Oe(t,e){return e===Tt?Te(function(t){var e=Wt(t),i=Gt(t),n=e.visualViewport,s=i.clientWidth,o=i.clientHeight,r=0,a=0;return n&&(s=n.width,o=n.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(r=n.offsetLeft,a=n.offsetTop)),{width:s,height:o,x:r+ye(t),y:a}}(t)):zt(e)?function(t){var e=Vt(t);return e.top=e.top+t.clientTop,e.left=e.left+t.clientLeft,e.bottom=e.top+t.clientHeight,e.right=e.left+t.clientWidth,e.width=t.clientWidth,e.height=t.clientHeight,e.x=e.left,e.y=e.top,e}(e):Te(function(t){var e,i=Gt(t),n=ve(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=ie(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=ie(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+ye(t),l=-n.scrollTop;return"rtl"===Yt(s||i).direction&&(a+=ie(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(Gt(t)))}function Ce(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?Ut(s):null,r=s?ce(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case mt:e={x:a,y:i.y-n.height};break;case gt:e={x:a,y:i.y+i.height};break;case _t:e={x:i.x+i.width,y:l};break;case bt:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?ee(o):null;if(null!=c){var h="y"===c?"height":"width";switch(r){case wt:e[c]=e[c]-(i[h]/2-n[h]/2);break;case Et:e[c]=e[c]+(i[h]/2-n[h]/2)}}return e}function ke(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=void 0===n?t.placement:n,o=i.boundary,r=void 0===o?At:o,a=i.rootBoundary,l=void 0===a?Tt:a,c=i.elementContext,h=void 0===c?Ot:c,d=i.altBoundary,u=void 0!==d&&d,f=i.padding,p=void 0===f?0:f,m=re("number"!=typeof p?p:ae(p,yt)),g=h===Ot?Ct:Ot,_=t.rects.popper,b=t.elements[u?g:h],v=function(t,e,i){var n="clippingParents"===e?function(t){var e=Ae(Zt(t)),i=["absolute","fixed"].indexOf(Yt(t).position)>=0&&zt(t)?te(t):t;return $t(i)?e.filter((function(t){return $t(t)&&Xt(t,i)&&"body"!==Rt(t)})):[]}(t):[].concat(e),s=[].concat(n,[i]),o=s[0],r=s.reduce((function(e,i){var n=Oe(t,i);return e.top=ie(n.top,e.top),e.right=ne(n.right,e.right),e.bottom=ne(n.bottom,e.bottom),e.left=ie(n.left,e.left),e}),Oe(t,o));return r.width=r.right-r.left,r.height=r.bottom-r.top,r.x=r.left,r.y=r.top,r}($t(b)?b:b.contextElement||Gt(t.elements.popper),r,l),y=Vt(t.elements.reference),w=Ce({reference:y,element:_,strategy:"absolute",placement:s}),E=Te(Object.assign({},_,w)),A=h===Ot?E:y,T={top:v.top-A.top+m.top,bottom:A.bottom-v.bottom+m.bottom,left:v.left-A.left+m.left,right:A.right-v.right+m.right},O=t.modifiersData.offset;if(h===Ot&&O){var C=O[s];Object.keys(T).forEach((function(t){var e=[_t,gt].indexOf(t)>=0?1:-1,i=[mt,gt].indexOf(t)>=0?"y":"x";T[t]+=C[i]*e}))}return T}function Le(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,l=i.allowedAutoPlacements,c=void 0===l?Lt:l,h=ce(n),d=h?a?kt:kt.filter((function(t){return ce(t)===h})):yt,u=d.filter((function(t){return c.indexOf(t)>=0}));0===u.length&&(u=d);var f=u.reduce((function(e,i){return e[i]=ke(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[Ut(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}const xe={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name;if(!e.modifiersData[n]._skip){for(var s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0===r||r,l=i.fallbackPlacements,c=i.padding,h=i.boundary,d=i.rootBoundary,u=i.altBoundary,f=i.flipVariations,p=void 0===f||f,m=i.allowedAutoPlacements,g=e.options.placement,_=Ut(g),b=l||(_!==g&&p?function(t){if(Ut(t)===vt)return[];var e=ge(t);return[be(t),e,be(e)]}(g):[ge(g)]),v=[g].concat(b).reduce((function(t,i){return t.concat(Ut(i)===vt?Le(e,{placement:i,boundary:h,rootBoundary:d,padding:c,flipVariations:p,allowedAutoPlacements:m}):i)}),[]),y=e.rects.reference,w=e.rects.popper,E=new Map,A=!0,T=v[0],O=0;O=0,D=x?"width":"height",S=ke(e,{placement:C,boundary:h,rootBoundary:d,altBoundary:u,padding:c}),N=x?L?_t:bt:L?gt:mt;y[D]>w[D]&&(N=ge(N));var I=ge(N),P=[];if(o&&P.push(S[k]<=0),a&&P.push(S[N]<=0,S[I]<=0),P.every((function(t){return t}))){T=C,A=!1;break}E.set(C,P)}if(A)for(var j=function(t){var e=v.find((function(e){var i=E.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return T=e,"break"},M=p?3:1;M>0&&"break"!==j(M);M--);e.placement!==T&&(e.modifiersData[n]._skip=!0,e.placement=T,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function De(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function Se(t){return[mt,_t,gt,bt].some((function(e){return t[e]>=0}))}const Ne={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=ke(e,{elementContext:"reference"}),a=ke(e,{altBoundary:!0}),l=De(r,n),c=De(a,s,o),h=Se(l),d=Se(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},Ie={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.offset,o=void 0===s?[0,0]:s,r=Lt.reduce((function(t,i){return t[i]=function(t,e,i){var n=Ut(t),s=[bt,mt].indexOf(n)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*s,[bt,_t].indexOf(n)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t}),{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[n]=r}},Pe={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=Ce({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},je={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0!==r&&r,l=i.boundary,c=i.rootBoundary,h=i.altBoundary,d=i.padding,u=i.tether,f=void 0===u||u,p=i.tetherOffset,m=void 0===p?0:p,g=ke(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:h}),_=Ut(e.placement),b=ce(e.placement),v=!b,y=ee(_),w="x"===y?"y":"x",E=e.modifiersData.popperOffsets,A=e.rects.reference,T=e.rects.popper,O="function"==typeof m?m(Object.assign({},e.rects,{placement:e.placement})):m,C={x:0,y:0};if(E){if(o||a){var k="y"===y?mt:bt,L="y"===y?gt:_t,x="y"===y?"height":"width",D=E[y],S=E[y]+g[k],N=E[y]-g[L],I=f?-T[x]/2:0,P=b===wt?A[x]:T[x],j=b===wt?-T[x]:-A[x],M=e.elements.arrow,H=f&&M?Kt(M):{width:0,height:0},B=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},R=B[k],W=B[L],$=oe(0,A[x],H[x]),z=v?A[x]/2-I-$-R-O:P-$-R-O,q=v?-A[x]/2+I+$+W+O:j+$+W+O,F=e.elements.arrow&&te(e.elements.arrow),U=F?"y"===y?F.clientTop||0:F.clientLeft||0:0,V=e.modifiersData.offset?e.modifiersData.offset[e.placement][y]:0,K=E[y]+z-V-U,X=E[y]+q-V;if(o){var Y=oe(f?ne(S,K):S,D,f?ie(N,X):N);E[y]=Y,C[y]=Y-D}if(a){var Q="x"===y?mt:bt,G="x"===y?gt:_t,Z=E[w],J=Z+g[Q],tt=Z-g[G],et=oe(f?ne(J,K):J,Z,f?ie(tt,X):tt);E[w]=et,C[w]=et-Z}}e.modifiersData[n]=C}},requiresIfExists:["offset"]};function Me(t,e,i){void 0===i&&(i=!1);var n=zt(e);zt(e)&&function(t){var e=t.getBoundingClientRect();e.width,t.offsetWidth,e.height,t.offsetHeight}(e);var s,o,r=Gt(e),a=Vt(t),l={scrollLeft:0,scrollTop:0},c={x:0,y:0};return(n||!n&&!i)&&(("body"!==Rt(e)||we(r))&&(l=(s=e)!==Wt(s)&&zt(s)?{scrollLeft:(o=s).scrollLeft,scrollTop:o.scrollTop}:ve(s)),zt(e)?((c=Vt(e)).x+=e.clientLeft,c.y+=e.clientTop):r&&(c.x=ye(r))),{x:a.left+l.scrollLeft-c.x,y:a.top+l.scrollTop-c.y,width:a.width,height:a.height}}function He(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var Be={placement:"bottom",modifiers:[],strategy:"absolute"};function Re(){for(var t=arguments.length,e=new Array(t),i=0;ij.on(t,"mouseover",d))),this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(Je),this._element.classList.add(Je),j.trigger(this._element,"shown.bs.dropdown",t)}hide(){if(c(this._element)||!this._isShown(this._menu))return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(t){j.trigger(this._element,"hide.bs.dropdown",t).defaultPrevented||("ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>j.off(t,"mouseover",d))),this._popper&&this._popper.destroy(),this._menu.classList.remove(Je),this._element.classList.remove(Je),this._element.setAttribute("aria-expanded","false"),U.removeDataAttribute(this._menu,"popper"),j.trigger(this._element,"hidden.bs.dropdown",t))}_getConfig(t){if(t={...this.constructor.Default,...U.getDataAttributes(this._element),...t},a(Ue,t,this.constructor.DefaultType),"object"==typeof t.reference&&!o(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError(`${Ue.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return t}_createPopper(t){if(void 0===Fe)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");let e=this._element;"parent"===this._config.reference?e=t:o(this._config.reference)?e=r(this._config.reference):"object"==typeof this._config.reference&&(e=this._config.reference);const i=this._getPopperConfig(),n=i.modifiers.find((t=>"applyStyles"===t.name&&!1===t.enabled));this._popper=qe(e,this._menu,i),n&&U.setDataAttribute(this._menu,"popper","static")}_isShown(t=this._element){return t.classList.contains(Je)}_getMenuElement(){return V.next(this._element,ei)[0]}_getPlacement(){const t=this._element.parentNode;if(t.classList.contains("dropend"))return ri;if(t.classList.contains("dropstart"))return ai;const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?ni:ii:e?oi:si}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return"static"===this._config.display&&(t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,..."function"==typeof this._config.popperConfig?this._config.popperConfig(t):this._config.popperConfig}}_selectMenuItem({key:t,target:e}){const i=V.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter(l);i.length&&v(i,e,t===Ye,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=hi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(t&&(2===t.button||"keyup"===t.type&&"Tab"!==t.key))return;const e=V.find(ti);for(let i=0,n=e.length;ie+t)),this._setElementAttributes(di,"paddingRight",(e=>e+t)),this._setElementAttributes(ui,"marginRight",(e=>e-t))}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t)[e];t.style[e]=`${i(Number.parseFloat(s))}px`}))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,"paddingRight"),this._resetElementAttributes(di,"paddingRight"),this._resetElementAttributes(ui,"marginRight")}_saveInitialAttribute(t,e){const i=t.style[e];i&&U.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=U.getDataAttribute(t,e);void 0===i?t.style.removeProperty(e):(U.removeDataAttribute(t,e),t.style[e]=i)}))}_applyManipulationCallback(t,e){o(t)?e(t):V.find(t,this._element).forEach(e)}isOverflowing(){return this.getWidth()>0}}const pi={className:"modal-backdrop",isVisible:!0,isAnimated:!1,rootElement:"body",clickCallback:null},mi={className:"string",isVisible:"boolean",isAnimated:"boolean",rootElement:"(element|string)",clickCallback:"(function|null)"},gi="show",_i="mousedown.bs.backdrop";class bi{constructor(t){this._config=this._getConfig(t),this._isAppended=!1,this._element=null}show(t){this._config.isVisible?(this._append(),this._config.isAnimated&&u(this._getElement()),this._getElement().classList.add(gi),this._emulateAnimation((()=>{_(t)}))):_(t)}hide(t){this._config.isVisible?(this._getElement().classList.remove(gi),this._emulateAnimation((()=>{this.dispose(),_(t)}))):_(t)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_getConfig(t){return(t={...pi,..."object"==typeof t?t:{}}).rootElement=r(t.rootElement),a("backdrop",t,mi),t}_append(){this._isAppended||(this._config.rootElement.append(this._getElement()),j.on(this._getElement(),_i,(()=>{_(this._config.clickCallback)})),this._isAppended=!0)}dispose(){this._isAppended&&(j.off(this._element,_i),this._element.remove(),this._isAppended=!1)}_emulateAnimation(t){b(t,this._getElement(),this._config.isAnimated)}}const vi={trapElement:null,autofocus:!0},yi={trapElement:"element",autofocus:"boolean"},wi=".bs.focustrap",Ei="backward";class Ai{constructor(t){this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}activate(){const{trapElement:t,autofocus:e}=this._config;this._isActive||(e&&t.focus(),j.off(document,wi),j.on(document,"focusin.bs.focustrap",(t=>this._handleFocusin(t))),j.on(document,"keydown.tab.bs.focustrap",(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,j.off(document,wi))}_handleFocusin(t){const{target:e}=t,{trapElement:i}=this._config;if(e===document||e===i||i.contains(e))return;const n=V.focusableChildren(i);0===n.length?i.focus():this._lastTabNavDirection===Ei?n[n.length-1].focus():n[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?Ei:"forward")}_getConfig(t){return t={...vi,..."object"==typeof t?t:{}},a("focustrap",t,yi),t}}const Ti="modal",Oi="Escape",Ci={backdrop:!0,keyboard:!0,focus:!0},ki={backdrop:"(boolean|string)",keyboard:"boolean",focus:"boolean"},Li="hidden.bs.modal",xi="show.bs.modal",Di="resize.bs.modal",Si="click.dismiss.bs.modal",Ni="keydown.dismiss.bs.modal",Ii="mousedown.dismiss.bs.modal",Pi="modal-open",ji="show",Mi="modal-static";class Hi extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._dialog=V.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._ignoreBackdropClick=!1,this._isTransitioning=!1,this._scrollBar=new fi}static get Default(){return Ci}static get NAME(){return Ti}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||j.trigger(this._element,xi,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isAnimated()&&(this._isTransitioning=!0),this._scrollBar.hide(),document.body.classList.add(Pi),this._adjustDialog(),this._setEscapeEvent(),this._setResizeEvent(),j.on(this._dialog,Ii,(()=>{j.one(this._element,"mouseup.dismiss.bs.modal",(t=>{t.target===this._element&&(this._ignoreBackdropClick=!0)}))})),this._showBackdrop((()=>this._showElement(t))))}hide(){if(!this._isShown||this._isTransitioning)return;if(j.trigger(this._element,"hide.bs.modal").defaultPrevented)return;this._isShown=!1;const t=this._isAnimated();t&&(this._isTransitioning=!0),this._setEscapeEvent(),this._setResizeEvent(),this._focustrap.deactivate(),this._element.classList.remove(ji),j.off(this._element,Si),j.off(this._dialog,Ii),this._queueCallback((()=>this._hideModal()),this._element,t)}dispose(){[window,this._dialog].forEach((t=>j.off(t,".bs.modal"))),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new bi({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new Ai({trapElement:this._element})}_getConfig(t){return t={...Ci,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(Ti,t,ki),t}_showElement(t){const e=this._isAnimated(),i=V.findOne(".modal-body",this._dialog);this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0,i&&(i.scrollTop=0),e&&u(this._element),this._element.classList.add(ji),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,j.trigger(this._element,"shown.bs.modal",{relatedTarget:t})}),this._dialog,e)}_setEscapeEvent(){this._isShown?j.on(this._element,Ni,(t=>{this._config.keyboard&&t.key===Oi?(t.preventDefault(),this.hide()):this._config.keyboard||t.key!==Oi||this._triggerBackdropTransition()})):j.off(this._element,Ni)}_setResizeEvent(){this._isShown?j.on(window,Di,(()=>this._adjustDialog())):j.off(window,Di)}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(Pi),this._resetAdjustments(),this._scrollBar.reset(),j.trigger(this._element,Li)}))}_showBackdrop(t){j.on(this._element,Si,(t=>{this._ignoreBackdropClick?this._ignoreBackdropClick=!1:t.target===t.currentTarget&&(!0===this._config.backdrop?this.hide():"static"===this._config.backdrop&&this._triggerBackdropTransition())})),this._backdrop.show(t)}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(j.trigger(this._element,"hidePrevented.bs.modal").defaultPrevented)return;const{classList:t,scrollHeight:e,style:i}=this._element,n=e>document.documentElement.clientHeight;!n&&"hidden"===i.overflowY||t.contains(Mi)||(n||(i.overflowY="hidden"),t.add(Mi),this._queueCallback((()=>{t.remove(Mi),n||this._queueCallback((()=>{i.overflowY=""}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;(!i&&t&&!m()||i&&!t&&m())&&(this._element.style.paddingLeft=`${e}px`),(i&&!t&&!m()||!i&&t&&m())&&(this._element.style.paddingRight=`${e}px`)}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=Hi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}j.on(document,"click.bs.modal.data-api",'[data-bs-toggle="modal"]',(function(t){const e=n(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),j.one(e,xi,(t=>{t.defaultPrevented||j.one(e,Li,(()=>{l(this)&&this.focus()}))}));const i=V.findOne(".modal.show");i&&Hi.getInstance(i).hide(),Hi.getOrCreateInstance(e).toggle(this)})),R(Hi),g(Hi);const Bi="offcanvas",Ri={backdrop:!0,keyboard:!0,scroll:!1},Wi={backdrop:"boolean",keyboard:"boolean",scroll:"boolean"},$i="show",zi=".offcanvas.show",qi="hidden.bs.offcanvas";class Fi extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get NAME(){return Bi}static get Default(){return Ri}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||j.trigger(this._element,"show.bs.offcanvas",{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._element.style.visibility="visible",this._backdrop.show(),this._config.scroll||(new fi).hide(),this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add($i),this._queueCallback((()=>{this._config.scroll||this._focustrap.activate(),j.trigger(this._element,"shown.bs.offcanvas",{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(j.trigger(this._element,"hide.bs.offcanvas").defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.remove($i),this._backdrop.hide(),this._queueCallback((()=>{this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._element.style.visibility="hidden",this._config.scroll||(new fi).reset(),j.trigger(this._element,qi)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_getConfig(t){return t={...Ri,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(Bi,t,Wi),t}_initializeBackDrop(){return new bi({className:"offcanvas-backdrop",isVisible:this._config.backdrop,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:()=>this.hide()})}_initializeFocusTrap(){return new Ai({trapElement:this._element})}_addEventListeners(){j.on(this._element,"keydown.dismiss.bs.offcanvas",(t=>{this._config.keyboard&&"Escape"===t.key&&this.hide()}))}static jQueryInterface(t){return this.each((function(){const e=Fi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}j.on(document,"click.bs.offcanvas.data-api",'[data-bs-toggle="offcanvas"]',(function(t){const e=n(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),c(this))return;j.one(e,qi,(()=>{l(this)&&this.focus()}));const i=V.findOne(zi);i&&i!==e&&Fi.getInstance(i).hide(),Fi.getOrCreateInstance(e).toggle(this)})),j.on(window,"load.bs.offcanvas.data-api",(()=>V.find(zi).forEach((t=>Fi.getOrCreateInstance(t).show())))),R(Fi),g(Fi);const Ui=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Vi=/^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i,Ki=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i,Xi=(t,e)=>{const i=t.nodeName.toLowerCase();if(e.includes(i))return!Ui.has(i)||Boolean(Vi.test(t.nodeValue)||Ki.test(t.nodeValue));const n=e.filter((t=>t instanceof RegExp));for(let t=0,e=n.length;t{Xi(t,r)||i.removeAttribute(t.nodeName)}))}return n.body.innerHTML}const Qi="tooltip",Gi=new Set(["sanitize","allowList","sanitizeFn"]),Zi={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(array|string|function)",container:"(string|element|boolean)",fallbackPlacements:"array",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",allowList:"object",popperConfig:"(null|object|function)"},Ji={AUTO:"auto",TOP:"top",RIGHT:m()?"left":"right",BOTTOM:"bottom",LEFT:m()?"right":"left"},tn={animation:!0,template:'',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:[0,0],container:!1,fallbackPlacements:["top","right","bottom","left"],boundary:"clippingParents",customClass:"",sanitize:!0,sanitizeFn:null,allowList:{"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},popperConfig:null},en={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"},nn="fade",sn="show",on="show",rn="out",an=".tooltip-inner",ln=".modal",cn="hide.bs.modal",hn="hover",dn="focus";class un extends B{constructor(t,e){if(void 0===Fe)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t),this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this._config=this._getConfig(e),this.tip=null,this._setListeners()}static get Default(){return tn}static get NAME(){return Qi}static get Event(){return en}static get DefaultType(){return Zi}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(t){if(this._isEnabled)if(t){const e=this._initializeOnDelegatedTarget(t);e._activeTrigger.click=!e._activeTrigger.click,e._isWithActiveTrigger()?e._enter(null,e):e._leave(null,e)}else{if(this.getTipElement().classList.contains(sn))return void this._leave(null,this);this._enter(null,this)}}dispose(){clearTimeout(this._timeout),j.off(this._element.closest(ln),cn,this._hideModalHandler),this.tip&&this.tip.remove(),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this.isWithContent()||!this._isEnabled)return;const t=j.trigger(this._element,this.constructor.Event.SHOW),e=h(this._element),i=null===e?this._element.ownerDocument.documentElement.contains(this._element):e.contains(this._element);if(t.defaultPrevented||!i)return;"tooltip"===this.constructor.NAME&&this.tip&&this.getTitle()!==this.tip.querySelector(an).innerHTML&&(this._disposePopper(),this.tip.remove(),this.tip=null);const n=this.getTipElement(),s=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME);n.setAttribute("id",s),this._element.setAttribute("aria-describedby",s),this._config.animation&&n.classList.add(nn);const o="function"==typeof this._config.placement?this._config.placement.call(this,n,this._element):this._config.placement,r=this._getAttachment(o);this._addAttachmentClass(r);const{container:a}=this._config;H.set(n,this.constructor.DATA_KEY,this),this._element.ownerDocument.documentElement.contains(this.tip)||(a.append(n),j.trigger(this._element,this.constructor.Event.INSERTED)),this._popper?this._popper.update():this._popper=qe(this._element,n,this._getPopperConfig(r)),n.classList.add(sn);const l=this._resolvePossibleFunction(this._config.customClass);l&&n.classList.add(...l.split(" ")),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>{j.on(t,"mouseover",d)}));const c=this.tip.classList.contains(nn);this._queueCallback((()=>{const t=this._hoverState;this._hoverState=null,j.trigger(this._element,this.constructor.Event.SHOWN),t===rn&&this._leave(null,this)}),this.tip,c)}hide(){if(!this._popper)return;const t=this.getTipElement();if(j.trigger(this._element,this.constructor.Event.HIDE).defaultPrevented)return;t.classList.remove(sn),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>j.off(t,"mouseover",d))),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1;const e=this.tip.classList.contains(nn);this._queueCallback((()=>{this._isWithActiveTrigger()||(this._hoverState!==on&&t.remove(),this._cleanTipClass(),this._element.removeAttribute("aria-describedby"),j.trigger(this._element,this.constructor.Event.HIDDEN),this._disposePopper())}),this.tip,e),this._hoverState=""}update(){null!==this._popper&&this._popper.update()}isWithContent(){return Boolean(this.getTitle())}getTipElement(){if(this.tip)return this.tip;const t=document.createElement("div");t.innerHTML=this._config.template;const e=t.children[0];return this.setContent(e),e.classList.remove(nn,sn),this.tip=e,this.tip}setContent(t){this._sanitizeAndSetContent(t,this.getTitle(),an)}_sanitizeAndSetContent(t,e,i){const n=V.findOne(i,t);e||!n?this.setElementContent(n,e):n.remove()}setElementContent(t,e){if(null!==t)return o(e)?(e=r(e),void(this._config.html?e.parentNode!==t&&(t.innerHTML="",t.append(e)):t.textContent=e.textContent)):void(this._config.html?(this._config.sanitize&&(e=Yi(e,this._config.allowList,this._config.sanitizeFn)),t.innerHTML=e):t.textContent=e)}getTitle(){const t=this._element.getAttribute("data-bs-original-title")||this._config.title;return this._resolvePossibleFunction(t)}updateAttachment(t){return"right"===t?"end":"left"===t?"start":t}_initializeOnDelegatedTarget(t,e){return e||this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return"function"==typeof t?t.call(this._element):t}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"onChange",enabled:!0,phase:"afterWrite",fn:t=>this._handlePopperPlacementChange(t)}],onFirstUpdate:t=>{t.options.placement!==t.placement&&this._handlePopperPlacementChange(t)}};return{...e,..."function"==typeof this._config.popperConfig?this._config.popperConfig(e):this._config.popperConfig}}_addAttachmentClass(t){this.getTipElement().classList.add(`${this._getBasicClassPrefix()}-${this.updateAttachment(t)}`)}_getAttachment(t){return Ji[t.toUpperCase()]}_setListeners(){this._config.trigger.split(" ").forEach((t=>{if("click"===t)j.on(this._element,this.constructor.Event.CLICK,this._config.selector,(t=>this.toggle(t)));else if("manual"!==t){const e=t===hn?this.constructor.Event.MOUSEENTER:this.constructor.Event.FOCUSIN,i=t===hn?this.constructor.Event.MOUSELEAVE:this.constructor.Event.FOCUSOUT;j.on(this._element,e,this._config.selector,(t=>this._enter(t))),j.on(this._element,i,this._config.selector,(t=>this._leave(t)))}})),this._hideModalHandler=()=>{this._element&&this.hide()},j.on(this._element.closest(ln),cn,this._hideModalHandler),this._config.selector?this._config={...this._config,trigger:"manual",selector:""}:this._fixTitle()}_fixTitle(){const t=this._element.getAttribute("title"),e=typeof this._element.getAttribute("data-bs-original-title");(t||"string"!==e)&&(this._element.setAttribute("data-bs-original-title",t||""),!t||this._element.getAttribute("aria-label")||this._element.textContent||this._element.setAttribute("aria-label",t),this._element.setAttribute("title",""))}_enter(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusin"===t.type?dn:hn]=!0),e.getTipElement().classList.contains(sn)||e._hoverState===on?e._hoverState=on:(clearTimeout(e._timeout),e._hoverState=on,e._config.delay&&e._config.delay.show?e._timeout=setTimeout((()=>{e._hoverState===on&&e.show()}),e._config.delay.show):e.show())}_leave(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusout"===t.type?dn:hn]=e._element.contains(t.relatedTarget)),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=rn,e._config.delay&&e._config.delay.hide?e._timeout=setTimeout((()=>{e._hoverState===rn&&e.hide()}),e._config.delay.hide):e.hide())}_isWithActiveTrigger(){for(const t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1}_getConfig(t){const e=U.getDataAttributes(this._element);return Object.keys(e).forEach((t=>{Gi.has(t)&&delete e[t]})),(t={...this.constructor.Default,...e,..."object"==typeof t&&t?t:{}}).container=!1===t.container?document.body:r(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),a(Qi,t,this.constructor.DefaultType),t.sanitize&&(t.template=Yi(t.template,t.allowList,t.sanitizeFn)),t}_getDelegateConfig(){const t={};for(const e in this._config)this.constructor.Default[e]!==this._config[e]&&(t[e]=this._config[e]);return t}_cleanTipClass(){const t=this.getTipElement(),e=new RegExp(`(^|\\s)${this._getBasicClassPrefix()}\\S+`,"g"),i=t.getAttribute("class").match(e);null!==i&&i.length>0&&i.map((t=>t.trim())).forEach((e=>t.classList.remove(e)))}_getBasicClassPrefix(){return"bs-tooltip"}_handlePopperPlacementChange(t){const{state:e}=t;e&&(this.tip=e.elements.popper,this._cleanTipClass(),this._addAttachmentClass(this._getAttachment(e.placement)))}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null)}static jQueryInterface(t){return this.each((function(){const e=un.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(un);const fn={...un.Default,placement:"right",offset:[0,8],trigger:"click",content:"",template:''},pn={...un.DefaultType,content:"(string|element|function)"},mn={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"};class gn extends un{static get Default(){return fn}static get NAME(){return"popover"}static get Event(){return mn}static get DefaultType(){return pn}isWithContent(){return this.getTitle()||this._getContent()}setContent(t){this._sanitizeAndSetContent(t,this.getTitle(),".popover-header"),this._sanitizeAndSetContent(t,this._getContent(),".popover-body")}_getContent(){return this._resolvePossibleFunction(this._config.content)}_getBasicClassPrefix(){return"bs-popover"}static jQueryInterface(t){return this.each((function(){const e=gn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(gn);const _n="scrollspy",bn={offset:10,method:"auto",target:""},vn={offset:"number",method:"string",target:"(string|element)"},yn="active",wn=".nav-link, .list-group-item, .dropdown-item",En="position";class An extends B{constructor(t,e){super(t),this._scrollElement="BODY"===this._element.tagName?window:this._element,this._config=this._getConfig(e),this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,j.on(this._scrollElement,"scroll.bs.scrollspy",(()=>this._process())),this.refresh(),this._process()}static get Default(){return bn}static get NAME(){return _n}refresh(){const t=this._scrollElement===this._scrollElement.window?"offset":En,e="auto"===this._config.method?t:this._config.method,n=e===En?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),V.find(wn,this._config.target).map((t=>{const s=i(t),o=s?V.findOne(s):null;if(o){const t=o.getBoundingClientRect();if(t.width||t.height)return[U[e](o).top+n,s]}return null})).filter((t=>t)).sort(((t,e)=>t[0]-e[0])).forEach((t=>{this._offsets.push(t[0]),this._targets.push(t[1])}))}dispose(){j.off(this._scrollElement,".bs.scrollspy"),super.dispose()}_getConfig(t){return(t={...bn,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}}).target=r(t.target)||document.documentElement,a(_n,t,vn),t}_getScrollTop(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop}_getScrollHeight(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)}_getOffsetHeight(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height}_process(){const t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),i=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=i){const t=this._targets[this._targets.length-1];this._activeTarget!==t&&this._activate(t)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(let e=this._offsets.length;e--;)this._activeTarget!==this._targets[e]&&t>=this._offsets[e]&&(void 0===this._offsets[e+1]||t`${e}[data-bs-target="${t}"],${e}[href="${t}"]`)),i=V.findOne(e.join(","),this._config.target);i.classList.add(yn),i.classList.contains("dropdown-item")?V.findOne(".dropdown-toggle",i.closest(".dropdown")).classList.add(yn):V.parents(i,".nav, .list-group").forEach((t=>{V.prev(t,".nav-link, .list-group-item").forEach((t=>t.classList.add(yn))),V.prev(t,".nav-item").forEach((t=>{V.children(t,".nav-link").forEach((t=>t.classList.add(yn)))}))})),j.trigger(this._scrollElement,"activate.bs.scrollspy",{relatedTarget:t})}_clear(){V.find(wn,this._config.target).filter((t=>t.classList.contains(yn))).forEach((t=>t.classList.remove(yn)))}static jQueryInterface(t){return this.each((function(){const e=An.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}j.on(window,"load.bs.scrollspy.data-api",(()=>{V.find('[data-bs-spy="scroll"]').forEach((t=>new An(t)))})),g(An);const Tn="active",On="fade",Cn="show",kn=".active",Ln=":scope > li > .active";class xn extends B{static get NAME(){return"tab"}show(){if(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&this._element.classList.contains(Tn))return;let t;const e=n(this._element),i=this._element.closest(".nav, .list-group");if(i){const e="UL"===i.nodeName||"OL"===i.nodeName?Ln:kn;t=V.find(e,i),t=t[t.length-1]}const s=t?j.trigger(t,"hide.bs.tab",{relatedTarget:this._element}):null;if(j.trigger(this._element,"show.bs.tab",{relatedTarget:t}).defaultPrevented||null!==s&&s.defaultPrevented)return;this._activate(this._element,i);const o=()=>{j.trigger(t,"hidden.bs.tab",{relatedTarget:this._element}),j.trigger(this._element,"shown.bs.tab",{relatedTarget:t})};e?this._activate(e,e.parentNode,o):o()}_activate(t,e,i){const n=(!e||"UL"!==e.nodeName&&"OL"!==e.nodeName?V.children(e,kn):V.find(Ln,e))[0],s=i&&n&&n.classList.contains(On),o=()=>this._transitionComplete(t,n,i);n&&s?(n.classList.remove(Cn),this._queueCallback(o,t,!0)):o()}_transitionComplete(t,e,i){if(e){e.classList.remove(Tn);const t=V.findOne(":scope > .dropdown-menu .active",e.parentNode);t&&t.classList.remove(Tn),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!1)}t.classList.add(Tn),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),u(t),t.classList.contains(On)&&t.classList.add(Cn);let n=t.parentNode;if(n&&"LI"===n.nodeName&&(n=n.parentNode),n&&n.classList.contains("dropdown-menu")){const e=t.closest(".dropdown");e&&V.find(".dropdown-toggle",e).forEach((t=>t.classList.add(Tn))),t.setAttribute("aria-expanded",!0)}i&&i()}static jQueryInterface(t){return this.each((function(){const e=xn.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}j.on(document,"click.bs.tab.data-api",'[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),c(this)||xn.getOrCreateInstance(this).show()})),g(xn);const Dn="toast",Sn="hide",Nn="show",In="showing",Pn={animation:"boolean",autohide:"boolean",delay:"number"},jn={animation:!0,autohide:!0,delay:5e3};class Mn extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get DefaultType(){return Pn}static get Default(){return jn}static get NAME(){return Dn}show(){j.trigger(this._element,"show.bs.toast").defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(Sn),u(this._element),this._element.classList.add(Nn),this._element.classList.add(In),this._queueCallback((()=>{this._element.classList.remove(In),j.trigger(this._element,"shown.bs.toast"),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this._element.classList.contains(Nn)&&(j.trigger(this._element,"hide.bs.toast").defaultPrevented||(this._element.classList.add(In),this._queueCallback((()=>{this._element.classList.add(Sn),this._element.classList.remove(In),this._element.classList.remove(Nn),j.trigger(this._element,"hidden.bs.toast")}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this._element.classList.contains(Nn)&&this._element.classList.remove(Nn),super.dispose()}_getConfig(t){return t={...jn,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}},a(Dn,t,this.constructor.DefaultType),t}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){j.on(this._element,"mouseover.bs.toast",(t=>this._onInteraction(t,!0))),j.on(this._element,"mouseout.bs.toast",(t=>this._onInteraction(t,!1))),j.on(this._element,"focusin.bs.toast",(t=>this._onInteraction(t,!0))),j.on(this._element,"focusout.bs.toast",(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=Mn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return R(Mn),g(Mn),{Alert:W,Button:z,Carousel:st,Collapse:pt,Dropdown:hi,Modal:Hi,Offcanvas:Fi,Popover:gn,ScrollSpy:An,Tab:xn,Toast:Mn,Tooltip:un}})); -//# sourceMappingURL=bootstrap.bundle.min.js.map \ No newline at end of file diff --git a/_proc/_docs/site_libs/clipboard/clipboard.min.js b/_proc/_docs/site_libs/clipboard/clipboard.min.js deleted file mode 100644 index 41c6a0f..0000000 --- a/_proc/_docs/site_libs/clipboard/clipboard.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * clipboard.js v2.0.10 - * https://clipboardjs.com/ - * - * Licensed MIT © Zeno Rocha - */ -!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return o}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),c=n.n(e);function a(t){try{return document.execCommand(t)}catch(t){return}}var f=function(t){t=c()(t);return a("cut"),t};var l=function(t){var e,n,o,r=1.anchorjs-link,.anchorjs-link:focus{opacity:1}",u.sheet.cssRules.length),u.sheet.insertRule("[data-anchorjs-icon]::after{content:attr(data-anchorjs-icon)}",u.sheet.cssRules.length),u.sheet.insertRule('@font-face{font-family:anchorjs-icons;src:url(data:n/a;base64,AAEAAAALAIAAAwAwT1MvMg8yG2cAAAE4AAAAYGNtYXDp3gC3AAABpAAAAExnYXNwAAAAEAAAA9wAAAAIZ2x5ZlQCcfwAAAH4AAABCGhlYWQHFvHyAAAAvAAAADZoaGVhBnACFwAAAPQAAAAkaG10eASAADEAAAGYAAAADGxvY2EACACEAAAB8AAAAAhtYXhwAAYAVwAAARgAAAAgbmFtZQGOH9cAAAMAAAAAunBvc3QAAwAAAAADvAAAACAAAQAAAAEAAHzE2p9fDzz1AAkEAAAAAADRecUWAAAAANQA6R8AAAAAAoACwAAAAAgAAgAAAAAAAAABAAADwP/AAAACgAAA/9MCrQABAAAAAAAAAAAAAAAAAAAAAwABAAAAAwBVAAIAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAMCQAGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAg//0DwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAAIAAAACgAAxAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADAAAAAIAAgAAgAAACDpy//9//8AAAAg6cv//f///+EWNwADAAEAAAAAAAAAAAAAAAAACACEAAEAAAAAAAAAAAAAAAAxAAACAAQARAKAAsAAKwBUAAABIiYnJjQ3NzY2MzIWFxYUBwcGIicmNDc3NjQnJiYjIgYHBwYUFxYUBwYGIwciJicmNDc3NjIXFhQHBwYUFxYWMzI2Nzc2NCcmNDc2MhcWFAcHBgYjARQGDAUtLXoWOR8fORYtLTgKGwoKCjgaGg0gEhIgDXoaGgkJBQwHdR85Fi0tOAobCgoKOBoaDSASEiANehoaCQkKGwotLXoWOR8BMwUFLYEuehYXFxYugC44CQkKGwo4GkoaDQ0NDXoaShoKGwoFBe8XFi6ALjgJCQobCjgaShoNDQ0NehpKGgobCgoKLYEuehYXAAAADACWAAEAAAAAAAEACAAAAAEAAAAAAAIAAwAIAAEAAAAAAAMACAAAAAEAAAAAAAQACAAAAAEAAAAAAAUAAQALAAEAAAAAAAYACAAAAAMAAQQJAAEAEAAMAAMAAQQJAAIABgAcAAMAAQQJAAMAEAAMAAMAAQQJAAQAEAAMAAMAAQQJAAUAAgAiAAMAAQQJAAYAEAAMYW5jaG9yanM0MDBAAGEAbgBjAGgAbwByAGoAcwA0ADAAMABAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAH//wAP) format("truetype")}',u.sheet.cssRules.length)),u=document.querySelectorAll("[id]"),t=[].map.call(u,function(A){return A.id}),i=0;i\]./()*\\\n\t\b\v\u00A0]/g,"-").replace(/-{2,}/g,"-").substring(0,this.options.truncate).replace(/^-+|-+$/gm,"").toLowerCase()},this.hasAnchorJSLink=function(A){var e=A.firstChild&&-1<(" "+A.firstChild.className+" ").indexOf(" anchorjs-link "),A=A.lastChild&&-1<(" "+A.lastChild.className+" ").indexOf(" anchorjs-link ");return e||A||!1}}}); -// @license-end \ No newline at end of file diff --git a/_proc/_docs/site_libs/quarto-html/popper.min.js b/_proc/_docs/site_libs/quarto-html/popper.min.js deleted file mode 100644 index 2269d66..0000000 --- a/_proc/_docs/site_libs/quarto-html/popper.min.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * @popperjs/core v2.11.4 - MIT License - */ - -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Popper={})}(this,(function(e){"use strict";function t(e){if(null==e)return window;if("[object Window]"!==e.toString()){var t=e.ownerDocument;return t&&t.defaultView||window}return e}function n(e){return e instanceof t(e).Element||e instanceof Element}function r(e){return e instanceof t(e).HTMLElement||e instanceof HTMLElement}function o(e){return"undefined"!=typeof ShadowRoot&&(e instanceof t(e).ShadowRoot||e instanceof ShadowRoot)}var i=Math.max,a=Math.min,s=Math.round;function f(e,t){void 0===t&&(t=!1);var n=e.getBoundingClientRect(),o=1,i=1;if(r(e)&&t){var a=e.offsetHeight,f=e.offsetWidth;f>0&&(o=s(n.width)/f||1),a>0&&(i=s(n.height)/a||1)}return{width:n.width/o,height:n.height/i,top:n.top/i,right:n.right/o,bottom:n.bottom/i,left:n.left/o,x:n.left/o,y:n.top/i}}function c(e){var n=t(e);return{scrollLeft:n.pageXOffset,scrollTop:n.pageYOffset}}function p(e){return e?(e.nodeName||"").toLowerCase():null}function u(e){return((n(e)?e.ownerDocument:e.document)||window.document).documentElement}function l(e){return f(u(e)).left+c(e).scrollLeft}function d(e){return t(e).getComputedStyle(e)}function h(e){var t=d(e),n=t.overflow,r=t.overflowX,o=t.overflowY;return/auto|scroll|overlay|hidden/.test(n+o+r)}function m(e,n,o){void 0===o&&(o=!1);var i,a,d=r(n),m=r(n)&&function(e){var t=e.getBoundingClientRect(),n=s(t.width)/e.offsetWidth||1,r=s(t.height)/e.offsetHeight||1;return 1!==n||1!==r}(n),v=u(n),g=f(e,m),y={scrollLeft:0,scrollTop:0},b={x:0,y:0};return(d||!d&&!o)&&(("body"!==p(n)||h(v))&&(y=(i=n)!==t(i)&&r(i)?{scrollLeft:(a=i).scrollLeft,scrollTop:a.scrollTop}:c(i)),r(n)?((b=f(n,!0)).x+=n.clientLeft,b.y+=n.clientTop):v&&(b.x=l(v))),{x:g.left+y.scrollLeft-b.x,y:g.top+y.scrollTop-b.y,width:g.width,height:g.height}}function v(e){var t=f(e),n=e.offsetWidth,r=e.offsetHeight;return Math.abs(t.width-n)<=1&&(n=t.width),Math.abs(t.height-r)<=1&&(r=t.height),{x:e.offsetLeft,y:e.offsetTop,width:n,height:r}}function g(e){return"html"===p(e)?e:e.assignedSlot||e.parentNode||(o(e)?e.host:null)||u(e)}function y(e){return["html","body","#document"].indexOf(p(e))>=0?e.ownerDocument.body:r(e)&&h(e)?e:y(g(e))}function b(e,n){var r;void 0===n&&(n=[]);var o=y(e),i=o===(null==(r=e.ownerDocument)?void 0:r.body),a=t(o),s=i?[a].concat(a.visualViewport||[],h(o)?o:[]):o,f=n.concat(s);return i?f:f.concat(b(g(s)))}function x(e){return["table","td","th"].indexOf(p(e))>=0}function w(e){return r(e)&&"fixed"!==d(e).position?e.offsetParent:null}function O(e){for(var n=t(e),i=w(e);i&&x(i)&&"static"===d(i).position;)i=w(i);return i&&("html"===p(i)||"body"===p(i)&&"static"===d(i).position)?n:i||function(e){var t=-1!==navigator.userAgent.toLowerCase().indexOf("firefox");if(-1!==navigator.userAgent.indexOf("Trident")&&r(e)&&"fixed"===d(e).position)return null;var n=g(e);for(o(n)&&(n=n.host);r(n)&&["html","body"].indexOf(p(n))<0;){var i=d(n);if("none"!==i.transform||"none"!==i.perspective||"paint"===i.contain||-1!==["transform","perspective"].indexOf(i.willChange)||t&&"filter"===i.willChange||t&&i.filter&&"none"!==i.filter)return n;n=n.parentNode}return null}(e)||n}var j="top",E="bottom",D="right",A="left",L="auto",P=[j,E,D,A],M="start",k="end",W="viewport",B="popper",H=P.reduce((function(e,t){return e.concat([t+"-"+M,t+"-"+k])}),[]),T=[].concat(P,[L]).reduce((function(e,t){return e.concat([t,t+"-"+M,t+"-"+k])}),[]),R=["beforeRead","read","afterRead","beforeMain","main","afterMain","beforeWrite","write","afterWrite"];function S(e){var t=new Map,n=new Set,r=[];function o(e){n.add(e.name),[].concat(e.requires||[],e.requiresIfExists||[]).forEach((function(e){if(!n.has(e)){var r=t.get(e);r&&o(r)}})),r.push(e)}return e.forEach((function(e){t.set(e.name,e)})),e.forEach((function(e){n.has(e.name)||o(e)})),r}function C(e){return e.split("-")[0]}function q(e,t){var n=t.getRootNode&&t.getRootNode();if(e.contains(t))return!0;if(n&&o(n)){var r=t;do{if(r&&e.isSameNode(r))return!0;r=r.parentNode||r.host}while(r)}return!1}function V(e){return Object.assign({},e,{left:e.x,top:e.y,right:e.x+e.width,bottom:e.y+e.height})}function N(e,r){return r===W?V(function(e){var n=t(e),r=u(e),o=n.visualViewport,i=r.clientWidth,a=r.clientHeight,s=0,f=0;return o&&(i=o.width,a=o.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(s=o.offsetLeft,f=o.offsetTop)),{width:i,height:a,x:s+l(e),y:f}}(e)):n(r)?function(e){var t=f(e);return t.top=t.top+e.clientTop,t.left=t.left+e.clientLeft,t.bottom=t.top+e.clientHeight,t.right=t.left+e.clientWidth,t.width=e.clientWidth,t.height=e.clientHeight,t.x=t.left,t.y=t.top,t}(r):V(function(e){var t,n=u(e),r=c(e),o=null==(t=e.ownerDocument)?void 0:t.body,a=i(n.scrollWidth,n.clientWidth,o?o.scrollWidth:0,o?o.clientWidth:0),s=i(n.scrollHeight,n.clientHeight,o?o.scrollHeight:0,o?o.clientHeight:0),f=-r.scrollLeft+l(e),p=-r.scrollTop;return"rtl"===d(o||n).direction&&(f+=i(n.clientWidth,o?o.clientWidth:0)-a),{width:a,height:s,x:f,y:p}}(u(e)))}function I(e,t,o){var s="clippingParents"===t?function(e){var t=b(g(e)),o=["absolute","fixed"].indexOf(d(e).position)>=0&&r(e)?O(e):e;return n(o)?t.filter((function(e){return n(e)&&q(e,o)&&"body"!==p(e)})):[]}(e):[].concat(t),f=[].concat(s,[o]),c=f[0],u=f.reduce((function(t,n){var r=N(e,n);return t.top=i(r.top,t.top),t.right=a(r.right,t.right),t.bottom=a(r.bottom,t.bottom),t.left=i(r.left,t.left),t}),N(e,c));return u.width=u.right-u.left,u.height=u.bottom-u.top,u.x=u.left,u.y=u.top,u}function _(e){return e.split("-")[1]}function F(e){return["top","bottom"].indexOf(e)>=0?"x":"y"}function U(e){var t,n=e.reference,r=e.element,o=e.placement,i=o?C(o):null,a=o?_(o):null,s=n.x+n.width/2-r.width/2,f=n.y+n.height/2-r.height/2;switch(i){case j:t={x:s,y:n.y-r.height};break;case E:t={x:s,y:n.y+n.height};break;case D:t={x:n.x+n.width,y:f};break;case A:t={x:n.x-r.width,y:f};break;default:t={x:n.x,y:n.y}}var c=i?F(i):null;if(null!=c){var p="y"===c?"height":"width";switch(a){case M:t[c]=t[c]-(n[p]/2-r[p]/2);break;case k:t[c]=t[c]+(n[p]/2-r[p]/2)}}return t}function z(e){return Object.assign({},{top:0,right:0,bottom:0,left:0},e)}function X(e,t){return t.reduce((function(t,n){return t[n]=e,t}),{})}function Y(e,t){void 0===t&&(t={});var r=t,o=r.placement,i=void 0===o?e.placement:o,a=r.boundary,s=void 0===a?"clippingParents":a,c=r.rootBoundary,p=void 0===c?W:c,l=r.elementContext,d=void 0===l?B:l,h=r.altBoundary,m=void 0!==h&&h,v=r.padding,g=void 0===v?0:v,y=z("number"!=typeof g?g:X(g,P)),b=d===B?"reference":B,x=e.rects.popper,w=e.elements[m?b:d],O=I(n(w)?w:w.contextElement||u(e.elements.popper),s,p),A=f(e.elements.reference),L=U({reference:A,element:x,strategy:"absolute",placement:i}),M=V(Object.assign({},x,L)),k=d===B?M:A,H={top:O.top-k.top+y.top,bottom:k.bottom-O.bottom+y.bottom,left:O.left-k.left+y.left,right:k.right-O.right+y.right},T=e.modifiersData.offset;if(d===B&&T){var R=T[i];Object.keys(H).forEach((function(e){var t=[D,E].indexOf(e)>=0?1:-1,n=[j,E].indexOf(e)>=0?"y":"x";H[e]+=R[n]*t}))}return H}var G={placement:"bottom",modifiers:[],strategy:"absolute"};function J(){for(var e=arguments.length,t=new Array(e),n=0;n=0?-1:1,i="function"==typeof n?n(Object.assign({},t,{placement:e})):n,a=i[0],s=i[1];return a=a||0,s=(s||0)*o,[A,D].indexOf(r)>=0?{x:s,y:a}:{x:a,y:s}}(n,t.rects,i),e}),{}),s=a[t.placement],f=s.x,c=s.y;null!=t.modifiersData.popperOffsets&&(t.modifiersData.popperOffsets.x+=f,t.modifiersData.popperOffsets.y+=c),t.modifiersData[r]=a}},ie={left:"right",right:"left",bottom:"top",top:"bottom"};function ae(e){return e.replace(/left|right|bottom|top/g,(function(e){return ie[e]}))}var se={start:"end",end:"start"};function fe(e){return e.replace(/start|end/g,(function(e){return se[e]}))}function ce(e,t){void 0===t&&(t={});var n=t,r=n.placement,o=n.boundary,i=n.rootBoundary,a=n.padding,s=n.flipVariations,f=n.allowedAutoPlacements,c=void 0===f?T:f,p=_(r),u=p?s?H:H.filter((function(e){return _(e)===p})):P,l=u.filter((function(e){return c.indexOf(e)>=0}));0===l.length&&(l=u);var d=l.reduce((function(t,n){return t[n]=Y(e,{placement:n,boundary:o,rootBoundary:i,padding:a})[C(n)],t}),{});return Object.keys(d).sort((function(e,t){return d[e]-d[t]}))}var pe={name:"flip",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name;if(!t.modifiersData[r]._skip){for(var o=n.mainAxis,i=void 0===o||o,a=n.altAxis,s=void 0===a||a,f=n.fallbackPlacements,c=n.padding,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.flipVariations,h=void 0===d||d,m=n.allowedAutoPlacements,v=t.options.placement,g=C(v),y=f||(g===v||!h?[ae(v)]:function(e){if(C(e)===L)return[];var t=ae(e);return[fe(e),t,fe(t)]}(v)),b=[v].concat(y).reduce((function(e,n){return e.concat(C(n)===L?ce(t,{placement:n,boundary:p,rootBoundary:u,padding:c,flipVariations:h,allowedAutoPlacements:m}):n)}),[]),x=t.rects.reference,w=t.rects.popper,O=new Map,P=!0,k=b[0],W=0;W=0,S=R?"width":"height",q=Y(t,{placement:B,boundary:p,rootBoundary:u,altBoundary:l,padding:c}),V=R?T?D:A:T?E:j;x[S]>w[S]&&(V=ae(V));var N=ae(V),I=[];if(i&&I.push(q[H]<=0),s&&I.push(q[V]<=0,q[N]<=0),I.every((function(e){return e}))){k=B,P=!1;break}O.set(B,I)}if(P)for(var F=function(e){var t=b.find((function(t){var n=O.get(t);if(n)return n.slice(0,e).every((function(e){return e}))}));if(t)return k=t,"break"},U=h?3:1;U>0;U--){if("break"===F(U))break}t.placement!==k&&(t.modifiersData[r]._skip=!0,t.placement=k,t.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function ue(e,t,n){return i(e,a(t,n))}var le={name:"preventOverflow",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name,o=n.mainAxis,s=void 0===o||o,f=n.altAxis,c=void 0!==f&&f,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.padding,h=n.tether,m=void 0===h||h,g=n.tetherOffset,y=void 0===g?0:g,b=Y(t,{boundary:p,rootBoundary:u,padding:d,altBoundary:l}),x=C(t.placement),w=_(t.placement),L=!w,P=F(x),k="x"===P?"y":"x",W=t.modifiersData.popperOffsets,B=t.rects.reference,H=t.rects.popper,T="function"==typeof y?y(Object.assign({},t.rects,{placement:t.placement})):y,R="number"==typeof T?{mainAxis:T,altAxis:T}:Object.assign({mainAxis:0,altAxis:0},T),S=t.modifiersData.offset?t.modifiersData.offset[t.placement]:null,q={x:0,y:0};if(W){if(s){var V,N="y"===P?j:A,I="y"===P?E:D,U="y"===P?"height":"width",z=W[P],X=z+b[N],G=z-b[I],J=m?-H[U]/2:0,K=w===M?B[U]:H[U],Q=w===M?-H[U]:-B[U],Z=t.elements.arrow,$=m&&Z?v(Z):{width:0,height:0},ee=t.modifiersData["arrow#persistent"]?t.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},te=ee[N],ne=ee[I],re=ue(0,B[U],$[U]),oe=L?B[U]/2-J-re-te-R.mainAxis:K-re-te-R.mainAxis,ie=L?-B[U]/2+J+re+ne+R.mainAxis:Q+re+ne+R.mainAxis,ae=t.elements.arrow&&O(t.elements.arrow),se=ae?"y"===P?ae.clientTop||0:ae.clientLeft||0:0,fe=null!=(V=null==S?void 0:S[P])?V:0,ce=z+ie-fe,pe=ue(m?a(X,z+oe-fe-se):X,z,m?i(G,ce):G);W[P]=pe,q[P]=pe-z}if(c){var le,de="x"===P?j:A,he="x"===P?E:D,me=W[k],ve="y"===k?"height":"width",ge=me+b[de],ye=me-b[he],be=-1!==[j,A].indexOf(x),xe=null!=(le=null==S?void 0:S[k])?le:0,we=be?ge:me-B[ve]-H[ve]-xe+R.altAxis,Oe=be?me+B[ve]+H[ve]-xe-R.altAxis:ye,je=m&&be?function(e,t,n){var r=ue(e,t,n);return r>n?n:r}(we,me,Oe):ue(m?we:ge,me,m?Oe:ye);W[k]=je,q[k]=je-me}t.modifiersData[r]=q}},requiresIfExists:["offset"]};var de={name:"arrow",enabled:!0,phase:"main",fn:function(e){var t,n=e.state,r=e.name,o=e.options,i=n.elements.arrow,a=n.modifiersData.popperOffsets,s=C(n.placement),f=F(s),c=[A,D].indexOf(s)>=0?"height":"width";if(i&&a){var p=function(e,t){return z("number"!=typeof(e="function"==typeof e?e(Object.assign({},t.rects,{placement:t.placement})):e)?e:X(e,P))}(o.padding,n),u=v(i),l="y"===f?j:A,d="y"===f?E:D,h=n.rects.reference[c]+n.rects.reference[f]-a[f]-n.rects.popper[c],m=a[f]-n.rects.reference[f],g=O(i),y=g?"y"===f?g.clientHeight||0:g.clientWidth||0:0,b=h/2-m/2,x=p[l],w=y-u[c]-p[d],L=y/2-u[c]/2+b,M=ue(x,L,w),k=f;n.modifiersData[r]=((t={})[k]=M,t.centerOffset=M-L,t)}},effect:function(e){var t=e.state,n=e.options.element,r=void 0===n?"[data-popper-arrow]":n;null!=r&&("string"!=typeof r||(r=t.elements.popper.querySelector(r)))&&q(t.elements.popper,r)&&(t.elements.arrow=r)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function he(e,t,n){return void 0===n&&(n={x:0,y:0}),{top:e.top-t.height-n.y,right:e.right-t.width+n.x,bottom:e.bottom-t.height+n.y,left:e.left-t.width-n.x}}function me(e){return[j,D,E,A].some((function(t){return e[t]>=0}))}var ve={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(e){var t=e.state,n=e.name,r=t.rects.reference,o=t.rects.popper,i=t.modifiersData.preventOverflow,a=Y(t,{elementContext:"reference"}),s=Y(t,{altBoundary:!0}),f=he(a,r),c=he(s,o,i),p=me(f),u=me(c);t.modifiersData[n]={referenceClippingOffsets:f,popperEscapeOffsets:c,isReferenceHidden:p,hasPopperEscaped:u},t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-reference-hidden":p,"data-popper-escaped":u})}},ge=K({defaultModifiers:[Z,$,ne,re]}),ye=[Z,$,ne,re,oe,pe,le,de,ve],be=K({defaultModifiers:ye});e.applyStyles=re,e.arrow=de,e.computeStyles=ne,e.createPopper=be,e.createPopperLite=ge,e.defaultModifiers=ye,e.detectOverflow=Y,e.eventListeners=Z,e.flip=pe,e.hide=ve,e.offset=oe,e.popperGenerator=K,e.popperOffsets=$,e.preventOverflow=le,Object.defineProperty(e,"__esModule",{value:!0})})); - diff --git a/_proc/_docs/site_libs/quarto-html/quarto-syntax-highlighting.css b/_proc/_docs/site_libs/quarto-html/quarto-syntax-highlighting.css deleted file mode 100644 index 36cb328..0000000 --- a/_proc/_docs/site_libs/quarto-html/quarto-syntax-highlighting.css +++ /dev/null @@ -1,171 +0,0 @@ -/* quarto syntax highlight colors */ -:root { - --quarto-hl-ot-color: #003B4F; - --quarto-hl-at-color: #657422; - --quarto-hl-ss-color: #20794D; - --quarto-hl-an-color: #5E5E5E; - --quarto-hl-fu-color: #4758AB; - --quarto-hl-st-color: #20794D; - --quarto-hl-cf-color: #003B4F; - --quarto-hl-op-color: #5E5E5E; - --quarto-hl-er-color: #AD0000; - --quarto-hl-bn-color: #AD0000; - --quarto-hl-al-color: #AD0000; - --quarto-hl-va-color: #111111; - --quarto-hl-bu-color: inherit; - --quarto-hl-ex-color: inherit; - --quarto-hl-pp-color: #AD0000; - --quarto-hl-in-color: #5E5E5E; - --quarto-hl-vs-color: #20794D; - --quarto-hl-wa-color: #5E5E5E; - --quarto-hl-do-color: #5E5E5E; - --quarto-hl-im-color: #00769E; - --quarto-hl-ch-color: #20794D; - --quarto-hl-dt-color: #AD0000; - --quarto-hl-fl-color: #AD0000; - --quarto-hl-co-color: #5E5E5E; - --quarto-hl-cv-color: #5E5E5E; - --quarto-hl-cn-color: #8f5902; - --quarto-hl-sc-color: #5E5E5E; - --quarto-hl-dv-color: #AD0000; - --quarto-hl-kw-color: #003B4F; -} - -/* other quarto variables */ -:root { - --quarto-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; -} - -pre > code.sourceCode > span { - color: #003B4F; -} - -code span { - color: #003B4F; -} - -code.sourceCode > span { - color: #003B4F; -} - -div.sourceCode, -div.sourceCode pre.sourceCode { - color: #003B4F; -} - -code span.ot { - color: #003B4F; -} - -code span.at { - color: #657422; -} - -code span.ss { - color: #20794D; -} - -code span.an { - color: #5E5E5E; -} - -code span.fu { - color: #4758AB; -} - -code span.st { - color: #20794D; -} - -code span.cf { - color: #003B4F; -} - -code span.op { - color: #5E5E5E; -} - -code span.er { - color: #AD0000; -} - -code span.bn { - color: #AD0000; -} - -code span.al { - color: #AD0000; -} - -code span.va { - color: #111111; -} - -code span.pp { - color: #AD0000; -} - -code span.in { - color: #5E5E5E; -} - -code span.vs { - color: #20794D; -} - -code span.wa { - color: #5E5E5E; - font-style: italic; -} - -code span.do { - color: #5E5E5E; - font-style: italic; -} - -code span.im { - color: #00769E; -} - -code span.ch { - color: #20794D; -} - -code span.dt { - color: #AD0000; -} - -code span.fl { - color: #AD0000; -} - -code span.co { - color: #5E5E5E; -} - -code span.cv { - color: #5E5E5E; - font-style: italic; -} - -code span.cn { - color: #8f5902; -} - -code span.sc { - color: #5E5E5E; -} - -code span.dv { - color: #AD0000; -} - -code span.kw { - color: #003B4F; -} - -.prevent-inlining { - content: " { - const sibling = el.previousElementSibling; - if (sibling && sibling.tagName === "A") { - return sibling.classList.contains("active"); - } else { - return false; - } - }; - - // fire slideEnter for bootstrap tab activations (for htmlwidget resize behavior) - function fireSlideEnter(e) { - const event = window.document.createEvent("Event"); - event.initEvent("slideenter", true, true); - window.document.dispatchEvent(event); - } - const tabs = window.document.querySelectorAll('a[data-bs-toggle="tab"]'); - tabs.forEach((tab) => { - tab.addEventListener("shown.bs.tab", fireSlideEnter); - }); - - // fire slideEnter for tabby tab activations (for htmlwidget resize behavior) - document.addEventListener("tabby", fireSlideEnter, false); - - // Track scrolling and mark TOC links as active - // get table of contents and sidebar (bail if we don't have at least one) - const tocLinks = tocEl - ? [...tocEl.querySelectorAll("a[data-scroll-target]")] - : []; - const makeActive = (link) => tocLinks[link].classList.add("active"); - const removeActive = (link) => tocLinks[link].classList.remove("active"); - const removeAllActive = () => - [...Array(tocLinks.length).keys()].forEach((link) => removeActive(link)); - - // activate the anchor for a section associated with this TOC entry - tocLinks.forEach((link) => { - link.addEventListener("click", () => { - if (link.href.indexOf("#") !== -1) { - const anchor = link.href.split("#")[1]; - const heading = window.document.querySelector( - `[data-anchor-id=${anchor}]` - ); - if (heading) { - // Add the class - heading.classList.add("reveal-anchorjs-link"); - - // function to show the anchor - const handleMouseout = () => { - heading.classList.remove("reveal-anchorjs-link"); - heading.removeEventListener("mouseout", handleMouseout); - }; - - // add a function to clear the anchor when the user mouses out of it - heading.addEventListener("mouseout", handleMouseout); - } - } - }); - }); - - const sections = tocLinks.map((link) => { - const target = link.getAttribute("data-scroll-target"); - if (target.startsWith("#")) { - return window.document.getElementById(decodeURI(`${target.slice(1)}`)); - } else { - return window.document.querySelector(decodeURI(`${target}`)); - } - }); - - const sectionMargin = 200; - let currentActive = 0; - // track whether we've initialized state the first time - let init = false; - - const updateActiveLink = () => { - // The index from bottom to top (e.g. reversed list) - let sectionIndex = -1; - if ( - window.innerHeight + window.pageYOffset >= - window.document.body.offsetHeight - ) { - sectionIndex = 0; - } else { - sectionIndex = [...sections].reverse().findIndex((section) => { - if (section) { - return window.pageYOffset >= section.offsetTop - sectionMargin; - } else { - return false; - } - }); - } - if (sectionIndex > -1) { - const current = sections.length - sectionIndex - 1; - if (current !== currentActive) { - removeAllActive(); - currentActive = current; - makeActive(current); - if (init) { - window.dispatchEvent(sectionChanged); - } - init = true; - } - } - }; - - const inHiddenRegion = (top, bottom, hiddenRegions) => { - for (const region of hiddenRegions) { - if (top <= region.bottom && bottom >= region.top) { - return true; - } - } - return false; - }; - - const categorySelector = "header.quarto-title-block .quarto-category"; - const activateCategories = (href) => { - // Find any categories - // Surround them with a link pointing back to: - // #category=Authoring - try { - const categoryEls = window.document.querySelectorAll(categorySelector); - for (const categoryEl of categoryEls) { - const categoryText = categoryEl.textContent; - if (categoryText) { - const link = `${href}#category=${encodeURIComponent(categoryText)}`; - const linkEl = window.document.createElement("a"); - linkEl.setAttribute("href", link); - for (const child of categoryEl.childNodes) { - linkEl.append(child); - } - categoryEl.appendChild(linkEl); - } - } - } catch { - // Ignore errors - } - }; - function hasTitleCategories() { - return window.document.querySelector(categorySelector) !== null; - } - - function offsetRelativeUrl(url) { - const offset = getMeta("quarto:offset"); - return offset ? offset + url : url; - } - - function offsetAbsoluteUrl(url) { - const offset = getMeta("quarto:offset"); - const baseUrl = new URL(offset, window.location); - - const projRelativeUrl = url.replace(baseUrl, ""); - if (projRelativeUrl.startsWith("/")) { - return projRelativeUrl; - } else { - return "/" + projRelativeUrl; - } - } - - // read a meta tag value - function getMeta(metaName) { - const metas = window.document.getElementsByTagName("meta"); - for (let i = 0; i < metas.length; i++) { - if (metas[i].getAttribute("name") === metaName) { - return metas[i].getAttribute("content"); - } - } - return ""; - } - - async function findAndActivateCategories() { - const currentPagePath = offsetAbsoluteUrl(window.location.href); - const response = await fetch(offsetRelativeUrl("listings.json")); - if (response.status == 200) { - return response.json().then(function (listingPaths) { - const listingHrefs = []; - for (const listingPath of listingPaths) { - const pathWithoutLeadingSlash = listingPath.listing.substring(1); - for (const item of listingPath.items) { - if ( - item === currentPagePath || - item === currentPagePath + "index.html" - ) { - // Resolve this path against the offset to be sure - // we already are using the correct path to the listing - // (this adjusts the listing urls to be rooted against - // whatever root the page is actually running against) - const relative = offsetRelativeUrl(pathWithoutLeadingSlash); - const baseUrl = window.location; - const resolvedPath = new URL(relative, baseUrl); - listingHrefs.push(resolvedPath.pathname); - break; - } - } - } - - // Look up the tree for a nearby linting and use that if we find one - const nearestListing = findNearestParentListing( - offsetAbsoluteUrl(window.location.pathname), - listingHrefs - ); - if (nearestListing) { - activateCategories(nearestListing); - } else { - // See if the referrer is a listing page for this item - const referredRelativePath = offsetAbsoluteUrl(document.referrer); - const referrerListing = listingHrefs.find((listingHref) => { - const isListingReferrer = - listingHref === referredRelativePath || - listingHref === referredRelativePath + "index.html"; - return isListingReferrer; - }); - - if (referrerListing) { - // Try to use the referrer if possible - activateCategories(referrerListing); - } else if (listingHrefs.length > 0) { - // Otherwise, just fall back to the first listing - activateCategories(listingHrefs[0]); - } - } - }); - } - } - if (hasTitleCategories()) { - findAndActivateCategories(); - } - - const findNearestParentListing = (href, listingHrefs) => { - if (!href || !listingHrefs) { - return undefined; - } - // Look up the tree for a nearby linting and use that if we find one - const relativeParts = href.substring(1).split("/"); - while (relativeParts.length > 0) { - const path = relativeParts.join("/"); - for (const listingHref of listingHrefs) { - if (listingHref.startsWith(path)) { - return listingHref; - } - } - relativeParts.pop(); - } - - return undefined; - }; - - const manageSidebarVisiblity = (el, placeholderDescriptor) => { - let isVisible = true; - - return (hiddenRegions) => { - if (el === null) { - return; - } - - // Find the last element of the TOC - const lastChildEl = el.lastElementChild; - - if (lastChildEl) { - // Find the top and bottom o the element that is being managed - const elTop = el.offsetTop; - const elBottom = - elTop + lastChildEl.offsetTop + lastChildEl.offsetHeight; - - // Converts the sidebar to a menu - const convertToMenu = () => { - for (const child of el.children) { - child.style.opacity = 0; - child.style.overflow = "hidden"; - } - - const toggleContainer = window.document.createElement("div"); - toggleContainer.style.width = "100%"; - toggleContainer.classList.add("zindex-over-content"); - toggleContainer.classList.add("quarto-sidebar-toggle"); - toggleContainer.classList.add("headroom-target"); // Marks this to be managed by headeroom - toggleContainer.id = placeholderDescriptor.id; - toggleContainer.style.position = "fixed"; - - const toggleIcon = window.document.createElement("i"); - toggleIcon.classList.add("quarto-sidebar-toggle-icon"); - toggleIcon.classList.add("bi"); - toggleIcon.classList.add("bi-caret-down-fill"); - - const toggleTitle = window.document.createElement("div"); - const titleEl = window.document.body.querySelector( - placeholderDescriptor.titleSelector - ); - if (titleEl) { - toggleTitle.append(titleEl.innerText, toggleIcon); - } - toggleTitle.classList.add("zindex-over-content"); - toggleTitle.classList.add("quarto-sidebar-toggle-title"); - toggleContainer.append(toggleTitle); - - const toggleContents = window.document.createElement("div"); - toggleContents.classList = el.classList; - toggleContents.classList.add("zindex-over-content"); - toggleContents.classList.add("quarto-sidebar-toggle-contents"); - for (const child of el.children) { - if (child.id === "toc-title") { - continue; - } - - const clone = child.cloneNode(true); - clone.style.opacity = 1; - clone.style.display = null; - toggleContents.append(clone); - } - toggleContents.style.height = "0px"; - toggleContainer.append(toggleContents); - el.parentElement.prepend(toggleContainer); - - // Process clicks - let tocShowing = false; - // Allow the caller to control whether this is dismissed - // when it is clicked (e.g. sidebar navigation supports - // opening and closing the nav tree, so don't dismiss on click) - const clickEl = placeholderDescriptor.dismissOnClick - ? toggleContainer - : toggleTitle; - - const closeToggle = () => { - if (tocShowing) { - toggleContainer.classList.remove("expanded"); - toggleContents.style.height = "0px"; - tocShowing = false; - } - }; - - const positionToggle = () => { - // position the element (top left of parent, same width as parent) - const elRect = el.getBoundingClientRect(); - toggleContainer.style.left = `${elRect.left}px`; - toggleContainer.style.top = `${elRect.top}px`; - toggleContainer.style.width = `${elRect.width}px`; - }; - - // Get rid of any expanded toggle if the user scrolls - window.document.addEventListener( - "scroll", - throttle(() => { - closeToggle(); - }, 50) - ); - - // Handle positioning of the toggle - window.addEventListener( - "resize", - throttle(() => { - positionToggle(); - }, 50) - ); - positionToggle(); - - // Process the click - clickEl.onclick = () => { - if (!tocShowing) { - toggleContainer.classList.add("expanded"); - toggleContents.style.height = null; - tocShowing = true; - } else { - closeToggle(); - } - }; - }; - - // Converts a sidebar from a menu back to a sidebar - const convertToSidebar = () => { - for (const child of el.children) { - child.style.opacity = 1; - child.style.overflow = null; - } - - const placeholderEl = window.document.getElementById( - placeholderDescriptor.id - ); - if (placeholderEl) { - placeholderEl.remove(); - } - - el.classList.remove("rollup"); - }; - - if (isReaderMode()) { - convertToMenu(); - isVisible = false; - } else { - if (!isVisible) { - // If the element is current not visible reveal if there are - // no conflicts with overlay regions - if (!inHiddenRegion(elTop, elBottom, hiddenRegions)) { - convertToSidebar(); - isVisible = true; - } - } else { - // If the element is visible, hide it if it conflicts with overlay regions - // and insert a placeholder toggle (or if we're in reader mode) - if (inHiddenRegion(elTop, elBottom, hiddenRegions)) { - convertToMenu(); - isVisible = false; - } - } - } - } - }; - }; - - // Find any conflicting margin elements and add margins to the - // top to prevent overlap - const marginChildren = window.document.querySelectorAll( - ".column-margin.column-container > * " - ); - - nexttick(() => { - let lastBottom = 0; - for (const marginChild of marginChildren) { - const top = marginChild.getBoundingClientRect().top + window.scrollY; - if (top < lastBottom) { - const margin = lastBottom - top; - marginChild.style.marginTop = `${margin}px`; - } - const styles = window.getComputedStyle(marginChild); - const marginTop = parseFloat(styles["marginTop"]); - - lastBottom = top + marginChild.getBoundingClientRect().height + marginTop; - } - }); - - // Manage the visibility of the toc and the sidebar - const marginScrollVisibility = manageSidebarVisiblity(marginSidebarEl, { - id: "quarto-toc-toggle", - titleSelector: "#toc-title", - dismissOnClick: true, - }); - const sidebarScrollVisiblity = manageSidebarVisiblity(sidebarEl, { - id: "quarto-sidebarnav-toggle", - titleSelector: ".title", - dismissOnClick: false, - }); - let tocLeftScrollVisibility; - if (leftTocEl) { - tocLeftScrollVisibility = manageSidebarVisiblity(leftTocEl, { - id: "quarto-lefttoc-toggle", - titleSelector: "#toc-title", - dismissOnClick: true, - }); - } - - // Find the first element that uses formatting in special columns - const conflictingEls = window.document.body.querySelectorAll( - '[class^="column-"], [class*=" column-"], aside, [class*="margin-caption"], [class*=" margin-caption"], [class*="margin-ref"], [class*=" margin-ref"]' - ); - - // Filter all the possibly conflicting elements into ones - // the do conflict on the left or ride side - const arrConflictingEls = Array.from(conflictingEls); - const leftSideConflictEls = arrConflictingEls.filter((el) => { - if (el.tagName === "ASIDE") { - return false; - } - return Array.from(el.classList).find((className) => { - return ( - className !== "column-body" && - className.startsWith("column-") && - !className.endsWith("right") && - !className.endsWith("container") && - className !== "column-margin" - ); - }); - }); - const rightSideConflictEls = arrConflictingEls.filter((el) => { - if (el.tagName === "ASIDE") { - return true; - } - - const hasMarginCaption = Array.from(el.classList).find((className) => { - return className == "margin-caption"; - }); - if (hasMarginCaption) { - return true; - } - - return Array.from(el.classList).find((className) => { - return ( - className !== "column-body" && - !className.endsWith("container") && - className.startsWith("column-") && - !className.endsWith("left") - ); - }); - }); - - const kOverlapPaddingSize = 10; - function toRegions(els) { - return els.map((el) => { - const top = - el.getBoundingClientRect().top + - document.documentElement.scrollTop - - kOverlapPaddingSize; - return { - top, - bottom: top + el.scrollHeight + 2 * kOverlapPaddingSize, - }; - }); - } - - const hideOverlappedSidebars = () => { - marginScrollVisibility(toRegions(rightSideConflictEls)); - sidebarScrollVisiblity(toRegions(leftSideConflictEls)); - if (tocLeftScrollVisibility) { - tocLeftScrollVisibility(toRegions(leftSideConflictEls)); - } - }; - - window.quartoToggleReader = () => { - // Applies a slow class (or removes it) - // to update the transition speed - const slowTransition = (slow) => { - const manageTransition = (id, slow) => { - const el = document.getElementById(id); - if (el) { - if (slow) { - el.classList.add("slow"); - } else { - el.classList.remove("slow"); - } - } - }; - - manageTransition("TOC", slow); - manageTransition("quarto-sidebar", slow); - }; - - const readerMode = !isReaderMode(); - setReaderModeValue(readerMode); - - // If we're entering reader mode, slow the transition - if (readerMode) { - slowTransition(readerMode); - } - highlightReaderToggle(readerMode); - hideOverlappedSidebars(); - - // If we're exiting reader mode, restore the non-slow transition - if (!readerMode) { - slowTransition(!readerMode); - } - }; - - const highlightReaderToggle = (readerMode) => { - const els = document.querySelectorAll(".quarto-reader-toggle"); - if (els) { - els.forEach((el) => { - if (readerMode) { - el.classList.add("reader"); - } else { - el.classList.remove("reader"); - } - }); - } - }; - - const setReaderModeValue = (val) => { - if (window.location.protocol !== "file:") { - window.localStorage.setItem("quarto-reader-mode", val); - } else { - localReaderMode = val; - } - }; - - const isReaderMode = () => { - if (window.location.protocol !== "file:") { - return window.localStorage.getItem("quarto-reader-mode") === "true"; - } else { - return localReaderMode; - } - }; - let localReaderMode = null; - - // Walk the TOC and collapse/expand nodes - // Nodes are expanded if: - // - they are top level - // - they have children that are 'active' links - // - they are directly below an link that is 'active' - const walk = (el, depth) => { - // Tick depth when we enter a UL - if (el.tagName === "UL") { - depth = depth + 1; - } - - // It this is active link - let isActiveNode = false; - if (el.tagName === "A" && el.classList.contains("active")) { - isActiveNode = true; - } - - // See if there is an active child to this element - let hasActiveChild = false; - for (child of el.children) { - hasActiveChild = walk(child, depth) || hasActiveChild; - } - - // Process the collapse state if this is an UL - if (el.tagName === "UL") { - if (depth === 1 || hasActiveChild || prevSiblingIsActiveLink(el)) { - el.classList.remove("collapse"); - } else { - el.classList.add("collapse"); - } - - // untick depth when we leave a UL - depth = depth - 1; - } - return hasActiveChild || isActiveNode; - }; - - // walk the TOC and expand / collapse any items that should be shown - - if (tocEl) { - walk(tocEl, 0); - updateActiveLink(); - } - - // Throttle the scroll event and walk peridiocally - window.document.addEventListener( - "scroll", - throttle(() => { - if (tocEl) { - updateActiveLink(); - walk(tocEl, 0); - } - if (!isReaderMode()) { - hideOverlappedSidebars(); - } - }, 5) - ); - window.addEventListener( - "resize", - throttle(() => { - if (!isReaderMode()) { - hideOverlappedSidebars(); - } - }, 10) - ); - hideOverlappedSidebars(); - highlightReaderToggle(isReaderMode()); -}); - -// grouped tabsets -window.addEventListener("pageshow", (_event) => { - function getTabSettings() { - const data = localStorage.getItem("quarto-persistent-tabsets-data"); - if (!data) { - localStorage.setItem("quarto-persistent-tabsets-data", "{}"); - return {}; - } - if (data) { - return JSON.parse(data); - } - } - - function setTabSettings(data) { - localStorage.setItem( - "quarto-persistent-tabsets-data", - JSON.stringify(data) - ); - } - - function setTabState(groupName, groupValue) { - const data = getTabSettings(); - data[groupName] = groupValue; - setTabSettings(data); - } - - function toggleTab(tab, active) { - const tabPanelId = tab.getAttribute("aria-controls"); - const tabPanel = document.getElementById(tabPanelId); - if (active) { - tab.classList.add("active"); - tabPanel.classList.add("active"); - } else { - tab.classList.remove("active"); - tabPanel.classList.remove("active"); - } - } - - function toggleAll(selectedGroup, selectorsToSync) { - for (const [thisGroup, tabs] of Object.entries(selectorsToSync)) { - const active = selectedGroup === thisGroup; - for (const tab of tabs) { - toggleTab(tab, active); - } - } - } - - function findSelectorsToSyncByLanguage() { - const result = {}; - const tabs = Array.from( - document.querySelectorAll(`div[data-group] a[id^='tabset-']`) - ); - for (const item of tabs) { - const div = item.parentElement.parentElement.parentElement; - const group = div.getAttribute("data-group"); - if (!result[group]) { - result[group] = {}; - } - const selectorsToSync = result[group]; - const value = item.innerHTML; - if (!selectorsToSync[value]) { - selectorsToSync[value] = []; - } - selectorsToSync[value].push(item); - } - return result; - } - - function setupSelectorSync() { - const selectorsToSync = findSelectorsToSyncByLanguage(); - Object.entries(selectorsToSync).forEach(([group, tabSetsByValue]) => { - Object.entries(tabSetsByValue).forEach(([value, items]) => { - items.forEach((item) => { - item.addEventListener("click", (_event) => { - setTabState(group, value); - toggleAll(value, selectorsToSync[group]); - }); - }); - }); - }); - return selectorsToSync; - } - - const selectorsToSync = setupSelectorSync(); - for (const [group, selectedName] of Object.entries(getTabSettings())) { - const selectors = selectorsToSync[group]; - // it's possible that stale state gives us empty selections, so we explicitly check here. - if (selectors) { - toggleAll(selectedName, selectors); - } - } -}); - -function throttle(func, wait) { - let waiting = false; - return function () { - if (!waiting) { - func.apply(this, arguments); - waiting = true; - setTimeout(function () { - waiting = false; - }, wait); - } - }; -} - -function nexttick(func) { - return setTimeout(func, 0); -} diff --git a/_proc/_docs/site_libs/quarto-html/tippy.css b/_proc/_docs/site_libs/quarto-html/tippy.css deleted file mode 100644 index e6ae635..0000000 --- a/_proc/_docs/site_libs/quarto-html/tippy.css +++ /dev/null @@ -1 +0,0 @@ -.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;white-space:normal;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1} \ No newline at end of file diff --git a/_proc/_docs/site_libs/quarto-html/tippy.umd.min.js b/_proc/_docs/site_libs/quarto-html/tippy.umd.min.js deleted file mode 100644 index ca292be..0000000 --- a/_proc/_docs/site_libs/quarto-html/tippy.umd.min.js +++ /dev/null @@ -1,2 +0,0 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("@popperjs/core")):"function"==typeof define&&define.amd?define(["@popperjs/core"],t):(e=e||self).tippy=t(e.Popper)}(this,(function(e){"use strict";var t={passive:!0,capture:!0},n=function(){return document.body};function r(e,t,n){if(Array.isArray(e)){var r=e[t];return null==r?Array.isArray(n)?n[t]:n:r}return e}function o(e,t){var n={}.toString.call(e);return 0===n.indexOf("[object")&&n.indexOf(t+"]")>-1}function i(e,t){return"function"==typeof e?e.apply(void 0,t):e}function a(e,t){return 0===t?e:function(r){clearTimeout(n),n=setTimeout((function(){e(r)}),t)};var n}function s(e,t){var n=Object.assign({},e);return t.forEach((function(e){delete n[e]})),n}function u(e){return[].concat(e)}function c(e,t){-1===e.indexOf(t)&&e.push(t)}function p(e){return e.split("-")[0]}function f(e){return[].slice.call(e)}function l(e){return Object.keys(e).reduce((function(t,n){return void 0!==e[n]&&(t[n]=e[n]),t}),{})}function d(){return document.createElement("div")}function v(e){return["Element","Fragment"].some((function(t){return o(e,t)}))}function m(e){return o(e,"MouseEvent")}function g(e){return!(!e||!e._tippy||e._tippy.reference!==e)}function h(e){return v(e)?[e]:function(e){return o(e,"NodeList")}(e)?f(e):Array.isArray(e)?e:f(document.querySelectorAll(e))}function b(e,t){e.forEach((function(e){e&&(e.style.transitionDuration=t+"ms")}))}function y(e,t){e.forEach((function(e){e&&e.setAttribute("data-state",t)}))}function w(e){var t,n=u(e)[0];return null!=n&&null!=(t=n.ownerDocument)&&t.body?n.ownerDocument:document}function E(e,t,n){var r=t+"EventListener";["transitionend","webkitTransitionEnd"].forEach((function(t){e[r](t,n)}))}function O(e,t){for(var n=t;n;){var r;if(e.contains(n))return!0;n=null==n.getRootNode||null==(r=n.getRootNode())?void 0:r.host}return!1}var x={isTouch:!1},C=0;function T(){x.isTouch||(x.isTouch=!0,window.performance&&document.addEventListener("mousemove",A))}function A(){var e=performance.now();e-C<20&&(x.isTouch=!1,document.removeEventListener("mousemove",A)),C=e}function L(){var e=document.activeElement;if(g(e)){var t=e._tippy;e.blur&&!t.state.isVisible&&e.blur()}}var D=!!("undefined"!=typeof window&&"undefined"!=typeof document)&&!!window.msCrypto,R=Object.assign({appendTo:n,aria:{content:"auto",expanded:"auto"},delay:0,duration:[300,250],getReferenceClientRect:null,hideOnClick:!0,ignoreAttributes:!1,interactive:!1,interactiveBorder:2,interactiveDebounce:0,moveTransition:"",offset:[0,10],onAfterUpdate:function(){},onBeforeUpdate:function(){},onCreate:function(){},onDestroy:function(){},onHidden:function(){},onHide:function(){},onMount:function(){},onShow:function(){},onShown:function(){},onTrigger:function(){},onUntrigger:function(){},onClickOutside:function(){},placement:"top",plugins:[],popperOptions:{},render:null,showOnCreate:!1,touch:!0,trigger:"mouseenter focus",triggerTarget:null},{animateFill:!1,followCursor:!1,inlinePositioning:!1,sticky:!1},{allowHTML:!1,animation:"fade",arrow:!0,content:"",inertia:!1,maxWidth:350,role:"tooltip",theme:"",zIndex:9999}),k=Object.keys(R);function P(e){var t=(e.plugins||[]).reduce((function(t,n){var r,o=n.name,i=n.defaultValue;o&&(t[o]=void 0!==e[o]?e[o]:null!=(r=R[o])?r:i);return t}),{});return Object.assign({},e,t)}function j(e,t){var n=Object.assign({},t,{content:i(t.content,[e])},t.ignoreAttributes?{}:function(e,t){return(t?Object.keys(P(Object.assign({},R,{plugins:t}))):k).reduce((function(t,n){var r=(e.getAttribute("data-tippy-"+n)||"").trim();if(!r)return t;if("content"===n)t[n]=r;else try{t[n]=JSON.parse(r)}catch(e){t[n]=r}return t}),{})}(e,t.plugins));return n.aria=Object.assign({},R.aria,n.aria),n.aria={expanded:"auto"===n.aria.expanded?t.interactive:n.aria.expanded,content:"auto"===n.aria.content?t.interactive?null:"describedby":n.aria.content},n}function M(e,t){e.innerHTML=t}function V(e){var t=d();return!0===e?t.className="tippy-arrow":(t.className="tippy-svg-arrow",v(e)?t.appendChild(e):M(t,e)),t}function I(e,t){v(t.content)?(M(e,""),e.appendChild(t.content)):"function"!=typeof t.content&&(t.allowHTML?M(e,t.content):e.textContent=t.content)}function S(e){var t=e.firstElementChild,n=f(t.children);return{box:t,content:n.find((function(e){return e.classList.contains("tippy-content")})),arrow:n.find((function(e){return e.classList.contains("tippy-arrow")||e.classList.contains("tippy-svg-arrow")})),backdrop:n.find((function(e){return e.classList.contains("tippy-backdrop")}))}}function N(e){var t=d(),n=d();n.className="tippy-box",n.setAttribute("data-state","hidden"),n.setAttribute("tabindex","-1");var r=d();function o(n,r){var o=S(t),i=o.box,a=o.content,s=o.arrow;r.theme?i.setAttribute("data-theme",r.theme):i.removeAttribute("data-theme"),"string"==typeof r.animation?i.setAttribute("data-animation",r.animation):i.removeAttribute("data-animation"),r.inertia?i.setAttribute("data-inertia",""):i.removeAttribute("data-inertia"),i.style.maxWidth="number"==typeof r.maxWidth?r.maxWidth+"px":r.maxWidth,r.role?i.setAttribute("role",r.role):i.removeAttribute("role"),n.content===r.content&&n.allowHTML===r.allowHTML||I(a,e.props),r.arrow?s?n.arrow!==r.arrow&&(i.removeChild(s),i.appendChild(V(r.arrow))):i.appendChild(V(r.arrow)):s&&i.removeChild(s)}return r.className="tippy-content",r.setAttribute("data-state","hidden"),I(r,e.props),t.appendChild(n),n.appendChild(r),o(e.props,e.props),{popper:t,onUpdate:o}}N.$$tippy=!0;var B=1,H=[],U=[];function _(o,s){var v,g,h,C,T,A,L,k,M=j(o,Object.assign({},R,P(l(s)))),V=!1,I=!1,N=!1,_=!1,F=[],W=a(we,M.interactiveDebounce),X=B++,Y=(k=M.plugins).filter((function(e,t){return k.indexOf(e)===t})),$={id:X,reference:o,popper:d(),popperInstance:null,props:M,state:{isEnabled:!0,isVisible:!1,isDestroyed:!1,isMounted:!1,isShown:!1},plugins:Y,clearDelayTimeouts:function(){clearTimeout(v),clearTimeout(g),cancelAnimationFrame(h)},setProps:function(e){if($.state.isDestroyed)return;ae("onBeforeUpdate",[$,e]),be();var t=$.props,n=j(o,Object.assign({},t,l(e),{ignoreAttributes:!0}));$.props=n,he(),t.interactiveDebounce!==n.interactiveDebounce&&(ce(),W=a(we,n.interactiveDebounce));t.triggerTarget&&!n.triggerTarget?u(t.triggerTarget).forEach((function(e){e.removeAttribute("aria-expanded")})):n.triggerTarget&&o.removeAttribute("aria-expanded");ue(),ie(),J&&J(t,n);$.popperInstance&&(Ce(),Ae().forEach((function(e){requestAnimationFrame(e._tippy.popperInstance.forceUpdate)})));ae("onAfterUpdate",[$,e])},setContent:function(e){$.setProps({content:e})},show:function(){var e=$.state.isVisible,t=$.state.isDestroyed,o=!$.state.isEnabled,a=x.isTouch&&!$.props.touch,s=r($.props.duration,0,R.duration);if(e||t||o||a)return;if(te().hasAttribute("disabled"))return;if(ae("onShow",[$],!1),!1===$.props.onShow($))return;$.state.isVisible=!0,ee()&&(z.style.visibility="visible");ie(),de(),$.state.isMounted||(z.style.transition="none");if(ee()){var u=re(),p=u.box,f=u.content;b([p,f],0)}A=function(){var e;if($.state.isVisible&&!_){if(_=!0,z.offsetHeight,z.style.transition=$.props.moveTransition,ee()&&$.props.animation){var t=re(),n=t.box,r=t.content;b([n,r],s),y([n,r],"visible")}se(),ue(),c(U,$),null==(e=$.popperInstance)||e.forceUpdate(),ae("onMount",[$]),$.props.animation&&ee()&&function(e,t){me(e,t)}(s,(function(){$.state.isShown=!0,ae("onShown",[$])}))}},function(){var e,t=$.props.appendTo,r=te();e=$.props.interactive&&t===n||"parent"===t?r.parentNode:i(t,[r]);e.contains(z)||e.appendChild(z);$.state.isMounted=!0,Ce()}()},hide:function(){var e=!$.state.isVisible,t=$.state.isDestroyed,n=!$.state.isEnabled,o=r($.props.duration,1,R.duration);if(e||t||n)return;if(ae("onHide",[$],!1),!1===$.props.onHide($))return;$.state.isVisible=!1,$.state.isShown=!1,_=!1,V=!1,ee()&&(z.style.visibility="hidden");if(ce(),ve(),ie(!0),ee()){var i=re(),a=i.box,s=i.content;$.props.animation&&(b([a,s],o),y([a,s],"hidden"))}se(),ue(),$.props.animation?ee()&&function(e,t){me(e,(function(){!$.state.isVisible&&z.parentNode&&z.parentNode.contains(z)&&t()}))}(o,$.unmount):$.unmount()},hideWithInteractivity:function(e){ne().addEventListener("mousemove",W),c(H,W),W(e)},enable:function(){$.state.isEnabled=!0},disable:function(){$.hide(),$.state.isEnabled=!1},unmount:function(){$.state.isVisible&&$.hide();if(!$.state.isMounted)return;Te(),Ae().forEach((function(e){e._tippy.unmount()})),z.parentNode&&z.parentNode.removeChild(z);U=U.filter((function(e){return e!==$})),$.state.isMounted=!1,ae("onHidden",[$])},destroy:function(){if($.state.isDestroyed)return;$.clearDelayTimeouts(),$.unmount(),be(),delete o._tippy,$.state.isDestroyed=!0,ae("onDestroy",[$])}};if(!M.render)return $;var q=M.render($),z=q.popper,J=q.onUpdate;z.setAttribute("data-tippy-root",""),z.id="tippy-"+$.id,$.popper=z,o._tippy=$,z._tippy=$;var G=Y.map((function(e){return e.fn($)})),K=o.hasAttribute("aria-expanded");return he(),ue(),ie(),ae("onCreate",[$]),M.showOnCreate&&Le(),z.addEventListener("mouseenter",(function(){$.props.interactive&&$.state.isVisible&&$.clearDelayTimeouts()})),z.addEventListener("mouseleave",(function(){$.props.interactive&&$.props.trigger.indexOf("mouseenter")>=0&&ne().addEventListener("mousemove",W)})),$;function Q(){var e=$.props.touch;return Array.isArray(e)?e:[e,0]}function Z(){return"hold"===Q()[0]}function ee(){var e;return!(null==(e=$.props.render)||!e.$$tippy)}function te(){return L||o}function ne(){var e=te().parentNode;return e?w(e):document}function re(){return S(z)}function oe(e){return $.state.isMounted&&!$.state.isVisible||x.isTouch||C&&"focus"===C.type?0:r($.props.delay,e?0:1,R.delay)}function ie(e){void 0===e&&(e=!1),z.style.pointerEvents=$.props.interactive&&!e?"":"none",z.style.zIndex=""+$.props.zIndex}function ae(e,t,n){var r;(void 0===n&&(n=!0),G.forEach((function(n){n[e]&&n[e].apply(n,t)})),n)&&(r=$.props)[e].apply(r,t)}function se(){var e=$.props.aria;if(e.content){var t="aria-"+e.content,n=z.id;u($.props.triggerTarget||o).forEach((function(e){var r=e.getAttribute(t);if($.state.isVisible)e.setAttribute(t,r?r+" "+n:n);else{var o=r&&r.replace(n,"").trim();o?e.setAttribute(t,o):e.removeAttribute(t)}}))}}function ue(){!K&&$.props.aria.expanded&&u($.props.triggerTarget||o).forEach((function(e){$.props.interactive?e.setAttribute("aria-expanded",$.state.isVisible&&e===te()?"true":"false"):e.removeAttribute("aria-expanded")}))}function ce(){ne().removeEventListener("mousemove",W),H=H.filter((function(e){return e!==W}))}function pe(e){if(!x.isTouch||!N&&"mousedown"!==e.type){var t=e.composedPath&&e.composedPath()[0]||e.target;if(!$.props.interactive||!O(z,t)){if(u($.props.triggerTarget||o).some((function(e){return O(e,t)}))){if(x.isTouch)return;if($.state.isVisible&&$.props.trigger.indexOf("click")>=0)return}else ae("onClickOutside",[$,e]);!0===$.props.hideOnClick&&($.clearDelayTimeouts(),$.hide(),I=!0,setTimeout((function(){I=!1})),$.state.isMounted||ve())}}}function fe(){N=!0}function le(){N=!1}function de(){var e=ne();e.addEventListener("mousedown",pe,!0),e.addEventListener("touchend",pe,t),e.addEventListener("touchstart",le,t),e.addEventListener("touchmove",fe,t)}function ve(){var e=ne();e.removeEventListener("mousedown",pe,!0),e.removeEventListener("touchend",pe,t),e.removeEventListener("touchstart",le,t),e.removeEventListener("touchmove",fe,t)}function me(e,t){var n=re().box;function r(e){e.target===n&&(E(n,"remove",r),t())}if(0===e)return t();E(n,"remove",T),E(n,"add",r),T=r}function ge(e,t,n){void 0===n&&(n=!1),u($.props.triggerTarget||o).forEach((function(r){r.addEventListener(e,t,n),F.push({node:r,eventType:e,handler:t,options:n})}))}function he(){var e;Z()&&(ge("touchstart",ye,{passive:!0}),ge("touchend",Ee,{passive:!0})),(e=$.props.trigger,e.split(/\s+/).filter(Boolean)).forEach((function(e){if("manual"!==e)switch(ge(e,ye),e){case"mouseenter":ge("mouseleave",Ee);break;case"focus":ge(D?"focusout":"blur",Oe);break;case"focusin":ge("focusout",Oe)}}))}function be(){F.forEach((function(e){var t=e.node,n=e.eventType,r=e.handler,o=e.options;t.removeEventListener(n,r,o)})),F=[]}function ye(e){var t,n=!1;if($.state.isEnabled&&!xe(e)&&!I){var r="focus"===(null==(t=C)?void 0:t.type);C=e,L=e.currentTarget,ue(),!$.state.isVisible&&m(e)&&H.forEach((function(t){return t(e)})),"click"===e.type&&($.props.trigger.indexOf("mouseenter")<0||V)&&!1!==$.props.hideOnClick&&$.state.isVisible?n=!0:Le(e),"click"===e.type&&(V=!n),n&&!r&&De(e)}}function we(e){var t=e.target,n=te().contains(t)||z.contains(t);"mousemove"===e.type&&n||function(e,t){var n=t.clientX,r=t.clientY;return e.every((function(e){var t=e.popperRect,o=e.popperState,i=e.props.interactiveBorder,a=p(o.placement),s=o.modifiersData.offset;if(!s)return!0;var u="bottom"===a?s.top.y:0,c="top"===a?s.bottom.y:0,f="right"===a?s.left.x:0,l="left"===a?s.right.x:0,d=t.top-r+u>i,v=r-t.bottom-c>i,m=t.left-n+f>i,g=n-t.right-l>i;return d||v||m||g}))}(Ae().concat(z).map((function(e){var t,n=null==(t=e._tippy.popperInstance)?void 0:t.state;return n?{popperRect:e.getBoundingClientRect(),popperState:n,props:M}:null})).filter(Boolean),e)&&(ce(),De(e))}function Ee(e){xe(e)||$.props.trigger.indexOf("click")>=0&&V||($.props.interactive?$.hideWithInteractivity(e):De(e))}function Oe(e){$.props.trigger.indexOf("focusin")<0&&e.target!==te()||$.props.interactive&&e.relatedTarget&&z.contains(e.relatedTarget)||De(e)}function xe(e){return!!x.isTouch&&Z()!==e.type.indexOf("touch")>=0}function Ce(){Te();var t=$.props,n=t.popperOptions,r=t.placement,i=t.offset,a=t.getReferenceClientRect,s=t.moveTransition,u=ee()?S(z).arrow:null,c=a?{getBoundingClientRect:a,contextElement:a.contextElement||te()}:o,p=[{name:"offset",options:{offset:i}},{name:"preventOverflow",options:{padding:{top:2,bottom:2,left:5,right:5}}},{name:"flip",options:{padding:5}},{name:"computeStyles",options:{adaptive:!s}},{name:"$$tippy",enabled:!0,phase:"beforeWrite",requires:["computeStyles"],fn:function(e){var t=e.state;if(ee()){var n=re().box;["placement","reference-hidden","escaped"].forEach((function(e){"placement"===e?n.setAttribute("data-placement",t.placement):t.attributes.popper["data-popper-"+e]?n.setAttribute("data-"+e,""):n.removeAttribute("data-"+e)})),t.attributes.popper={}}}}];ee()&&u&&p.push({name:"arrow",options:{element:u,padding:3}}),p.push.apply(p,(null==n?void 0:n.modifiers)||[]),$.popperInstance=e.createPopper(c,z,Object.assign({},n,{placement:r,onFirstUpdate:A,modifiers:p}))}function Te(){$.popperInstance&&($.popperInstance.destroy(),$.popperInstance=null)}function Ae(){return f(z.querySelectorAll("[data-tippy-root]"))}function Le(e){$.clearDelayTimeouts(),e&&ae("onTrigger",[$,e]),de();var t=oe(!0),n=Q(),r=n[0],o=n[1];x.isTouch&&"hold"===r&&o&&(t=o),t?v=setTimeout((function(){$.show()}),t):$.show()}function De(e){if($.clearDelayTimeouts(),ae("onUntrigger",[$,e]),$.state.isVisible){if(!($.props.trigger.indexOf("mouseenter")>=0&&$.props.trigger.indexOf("click")>=0&&["mouseleave","mousemove"].indexOf(e.type)>=0&&V)){var t=oe(!1);t?g=setTimeout((function(){$.state.isVisible&&$.hide()}),t):h=requestAnimationFrame((function(){$.hide()}))}}else ve()}}function F(e,n){void 0===n&&(n={});var r=R.plugins.concat(n.plugins||[]);document.addEventListener("touchstart",T,t),window.addEventListener("blur",L);var o=Object.assign({},n,{plugins:r}),i=h(e).reduce((function(e,t){var n=t&&_(t,o);return n&&e.push(n),e}),[]);return v(e)?i[0]:i}F.defaultProps=R,F.setDefaultProps=function(e){Object.keys(e).forEach((function(t){R[t]=e[t]}))},F.currentInput=x;var W=Object.assign({},e.applyStyles,{effect:function(e){var t=e.state,n={popper:{position:t.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};Object.assign(t.elements.popper.style,n.popper),t.styles=n,t.elements.arrow&&Object.assign(t.elements.arrow.style,n.arrow)}}),X={mouseover:"mouseenter",focusin:"focus",click:"click"};var Y={name:"animateFill",defaultValue:!1,fn:function(e){var t;if(null==(t=e.props.render)||!t.$$tippy)return{};var n=S(e.popper),r=n.box,o=n.content,i=e.props.animateFill?function(){var e=d();return e.className="tippy-backdrop",y([e],"hidden"),e}():null;return{onCreate:function(){i&&(r.insertBefore(i,r.firstElementChild),r.setAttribute("data-animatefill",""),r.style.overflow="hidden",e.setProps({arrow:!1,animation:"shift-away"}))},onMount:function(){if(i){var e=r.style.transitionDuration,t=Number(e.replace("ms",""));o.style.transitionDelay=Math.round(t/10)+"ms",i.style.transitionDuration=e,y([i],"visible")}},onShow:function(){i&&(i.style.transitionDuration="0ms")},onHide:function(){i&&y([i],"hidden")}}}};var $={clientX:0,clientY:0},q=[];function z(e){var t=e.clientX,n=e.clientY;$={clientX:t,clientY:n}}var J={name:"followCursor",defaultValue:!1,fn:function(e){var t=e.reference,n=w(e.props.triggerTarget||t),r=!1,o=!1,i=!0,a=e.props;function s(){return"initial"===e.props.followCursor&&e.state.isVisible}function u(){n.addEventListener("mousemove",f)}function c(){n.removeEventListener("mousemove",f)}function p(){r=!0,e.setProps({getReferenceClientRect:null}),r=!1}function f(n){var r=!n.target||t.contains(n.target),o=e.props.followCursor,i=n.clientX,a=n.clientY,s=t.getBoundingClientRect(),u=i-s.left,c=a-s.top;!r&&e.props.interactive||e.setProps({getReferenceClientRect:function(){var e=t.getBoundingClientRect(),n=i,r=a;"initial"===o&&(n=e.left+u,r=e.top+c);var s="horizontal"===o?e.top:r,p="vertical"===o?e.right:n,f="horizontal"===o?e.bottom:r,l="vertical"===o?e.left:n;return{width:p-l,height:f-s,top:s,right:p,bottom:f,left:l}}})}function l(){e.props.followCursor&&(q.push({instance:e,doc:n}),function(e){e.addEventListener("mousemove",z)}(n))}function d(){0===(q=q.filter((function(t){return t.instance!==e}))).filter((function(e){return e.doc===n})).length&&function(e){e.removeEventListener("mousemove",z)}(n)}return{onCreate:l,onDestroy:d,onBeforeUpdate:function(){a=e.props},onAfterUpdate:function(t,n){var i=n.followCursor;r||void 0!==i&&a.followCursor!==i&&(d(),i?(l(),!e.state.isMounted||o||s()||u()):(c(),p()))},onMount:function(){e.props.followCursor&&!o&&(i&&(f($),i=!1),s()||u())},onTrigger:function(e,t){m(t)&&($={clientX:t.clientX,clientY:t.clientY}),o="focus"===t.type},onHidden:function(){e.props.followCursor&&(p(),c(),i=!0)}}}};var G={name:"inlinePositioning",defaultValue:!1,fn:function(e){var t,n=e.reference;var r=-1,o=!1,i=[],a={name:"tippyInlinePositioning",enabled:!0,phase:"afterWrite",fn:function(o){var a=o.state;e.props.inlinePositioning&&(-1!==i.indexOf(a.placement)&&(i=[]),t!==a.placement&&-1===i.indexOf(a.placement)&&(i.push(a.placement),e.setProps({getReferenceClientRect:function(){return function(e){return function(e,t,n,r){if(n.length<2||null===e)return t;if(2===n.length&&r>=0&&n[0].left>n[1].right)return n[r]||t;switch(e){case"top":case"bottom":var o=n[0],i=n[n.length-1],a="top"===e,s=o.top,u=i.bottom,c=a?o.left:i.left,p=a?o.right:i.right;return{top:s,bottom:u,left:c,right:p,width:p-c,height:u-s};case"left":case"right":var f=Math.min.apply(Math,n.map((function(e){return e.left}))),l=Math.max.apply(Math,n.map((function(e){return e.right}))),d=n.filter((function(t){return"left"===e?t.left===f:t.right===l})),v=d[0].top,m=d[d.length-1].bottom;return{top:v,bottom:m,left:f,right:l,width:l-f,height:m-v};default:return t}}(p(e),n.getBoundingClientRect(),f(n.getClientRects()),r)}(a.placement)}})),t=a.placement)}};function s(){var t;o||(t=function(e,t){var n;return{popperOptions:Object.assign({},e.popperOptions,{modifiers:[].concat(((null==(n=e.popperOptions)?void 0:n.modifiers)||[]).filter((function(e){return e.name!==t.name})),[t])})}}(e.props,a),o=!0,e.setProps(t),o=!1)}return{onCreate:s,onAfterUpdate:s,onTrigger:function(t,n){if(m(n)){var o=f(e.reference.getClientRects()),i=o.find((function(e){return e.left-2<=n.clientX&&e.right+2>=n.clientX&&e.top-2<=n.clientY&&e.bottom+2>=n.clientY})),a=o.indexOf(i);r=a>-1?a:r}},onHidden:function(){r=-1}}}};var K={name:"sticky",defaultValue:!1,fn:function(e){var t=e.reference,n=e.popper;function r(t){return!0===e.props.sticky||e.props.sticky===t}var o=null,i=null;function a(){var s=r("reference")?(e.popperInstance?e.popperInstance.state.elements.reference:t).getBoundingClientRect():null,u=r("popper")?n.getBoundingClientRect():null;(s&&Q(o,s)||u&&Q(i,u))&&e.popperInstance&&e.popperInstance.update(),o=s,i=u,e.state.isMounted&&requestAnimationFrame(a)}return{onMount:function(){e.props.sticky&&a()}}}};function Q(e,t){return!e||!t||(e.top!==t.top||e.right!==t.right||e.bottom!==t.bottom||e.left!==t.left)}return F.setDefaultProps({plugins:[Y,J,G,K],render:N}),F.createSingleton=function(e,t){var n;void 0===t&&(t={});var r,o=e,i=[],a=[],c=t.overrides,p=[],f=!1;function l(){a=o.map((function(e){return u(e.props.triggerTarget||e.reference)})).reduce((function(e,t){return e.concat(t)}),[])}function v(){i=o.map((function(e){return e.reference}))}function m(e){o.forEach((function(t){e?t.enable():t.disable()}))}function g(e){return o.map((function(t){var n=t.setProps;return t.setProps=function(o){n(o),t.reference===r&&e.setProps(o)},function(){t.setProps=n}}))}function h(e,t){var n=a.indexOf(t);if(t!==r){r=t;var s=(c||[]).concat("content").reduce((function(e,t){return e[t]=o[n].props[t],e}),{});e.setProps(Object.assign({},s,{getReferenceClientRect:"function"==typeof s.getReferenceClientRect?s.getReferenceClientRect:function(){var e;return null==(e=i[n])?void 0:e.getBoundingClientRect()}}))}}m(!1),v(),l();var b={fn:function(){return{onDestroy:function(){m(!0)},onHidden:function(){r=null},onClickOutside:function(e){e.props.showOnCreate&&!f&&(f=!0,r=null)},onShow:function(e){e.props.showOnCreate&&!f&&(f=!0,h(e,i[0]))},onTrigger:function(e,t){h(e,t.currentTarget)}}}},y=F(d(),Object.assign({},s(t,["overrides"]),{plugins:[b].concat(t.plugins||[]),triggerTarget:a,popperOptions:Object.assign({},t.popperOptions,{modifiers:[].concat((null==(n=t.popperOptions)?void 0:n.modifiers)||[],[W])})})),w=y.show;y.show=function(e){if(w(),!r&&null==e)return h(y,i[0]);if(!r||null!=e){if("number"==typeof e)return i[e]&&h(y,i[e]);if(o.indexOf(e)>=0){var t=e.reference;return h(y,t)}return i.indexOf(e)>=0?h(y,e):void 0}},y.showNext=function(){var e=i[0];if(!r)return y.show(0);var t=i.indexOf(r);y.show(i[t+1]||e)},y.showPrevious=function(){var e=i[i.length-1];if(!r)return y.show(e);var t=i.indexOf(r),n=i[t-1]||e;y.show(n)};var E=y.setProps;return y.setProps=function(e){c=e.overrides||c,E(e)},y.setInstances=function(e){m(!0),p.forEach((function(e){return e()})),o=e,m(!1),v(),l(),p=g(y),y.setProps({triggerTarget:a})},p=g(y),y},F.delegate=function(e,n){var r=[],o=[],i=!1,a=n.target,c=s(n,["target"]),p=Object.assign({},c,{trigger:"manual",touch:!1}),f=Object.assign({touch:R.touch},c,{showOnCreate:!0}),l=F(e,p);function d(e){if(e.target&&!i){var t=e.target.closest(a);if(t){var r=t.getAttribute("data-tippy-trigger")||n.trigger||R.trigger;if(!t._tippy&&!("touchstart"===e.type&&"boolean"==typeof f.touch||"touchstart"!==e.type&&r.indexOf(X[e.type])<0)){var s=F(t,f);s&&(o=o.concat(s))}}}}function v(e,t,n,o){void 0===o&&(o=!1),e.addEventListener(t,n,o),r.push({node:e,eventType:t,handler:n,options:o})}return u(l).forEach((function(e){var n=e.destroy,a=e.enable,s=e.disable;e.destroy=function(e){void 0===e&&(e=!0),e&&o.forEach((function(e){e.destroy()})),o=[],r.forEach((function(e){var t=e.node,n=e.eventType,r=e.handler,o=e.options;t.removeEventListener(n,r,o)})),r=[],n()},e.enable=function(){a(),o.forEach((function(e){return e.enable()})),i=!1},e.disable=function(){s(),o.forEach((function(e){return e.disable()})),i=!0},function(e){var n=e.reference;v(n,"touchstart",d,t),v(n,"mouseover",d),v(n,"focusin",d),v(n,"click",d)}(e)})),l},F.hideAll=function(e){var t=void 0===e?{}:e,n=t.exclude,r=t.duration;U.forEach((function(e){var t=!1;if(n&&(t=g(n)?e.reference===n:e.popper===n.popper),!t){var o=e.props.duration;e.setProps({duration:r}),e.hide(),e.state.isDestroyed||e.setProps({duration:o})}}))},F.roundArrow='',F})); - diff --git a/_proc/_docs/site_libs/quarto-listing/list.min.js b/_proc/_docs/site_libs/quarto-listing/list.min.js deleted file mode 100644 index 8131881..0000000 --- a/_proc/_docs/site_libs/quarto-listing/list.min.js +++ /dev/null @@ -1,2 +0,0 @@ -var List;List=function(){var t={"./src/add-async.js":function(t){t.exports=function(t){return function e(r,n,s){var i=r.splice(0,50);s=(s=s||[]).concat(t.add(i)),r.length>0?setTimeout((function(){e(r,n,s)}),1):(t.update(),n(s))}}},"./src/filter.js":function(t){t.exports=function(t){return t.handlers.filterStart=t.handlers.filterStart||[],t.handlers.filterComplete=t.handlers.filterComplete||[],function(e){if(t.trigger("filterStart"),t.i=1,t.reset.filter(),void 0===e)t.filtered=!1;else{t.filtered=!0;for(var r=t.items,n=0,s=r.length;nv.page,a=new g(t[s],void 0,n),v.items.push(a),r.push(a)}return v.update(),r}m(t.slice(0),e)}},this.show=function(t,e){return this.i=t,this.page=e,v.update(),v},this.remove=function(t,e,r){for(var n=0,s=0,i=v.items.length;s-1&&r.splice(n,1),v},this.trigger=function(t){for(var e=v.handlers[t].length;e--;)v.handlers[t][e](v);return v},this.reset={filter:function(){for(var t=v.items,e=t.length;e--;)t[e].filtered=!1;return v},search:function(){for(var t=v.items,e=t.length;e--;)t[e].found=!1;return v}},this.update=function(){var t=v.items,e=t.length;v.visibleItems=[],v.matchingItems=[],v.templater.clear();for(var r=0;r=v.i&&v.visibleItems.lengthe},innerWindow:function(t,e,r){return t>=e-r&&t<=e+r},dotted:function(t,e,r,n,s,i,a){return this.dottedLeft(t,e,r,n,s,i)||this.dottedRight(t,e,r,n,s,i,a)},dottedLeft:function(t,e,r,n,s,i){return e==r+1&&!this.innerWindow(e,s,i)&&!this.right(e,n)},dottedRight:function(t,e,r,n,s,i,a){return!t.items[a-1].values().dotted&&(e==n&&!this.innerWindow(e,s,i)&&!this.right(e,n))}};return function(e){var n=new i(t.listContainer.id,{listClass:e.paginationClass||"pagination",item:e.item||"
  • ",valueNames:["page","dotted"],searchClass:"pagination-search-that-is-not-supposed-to-exist",sortClass:"pagination-sort-that-is-not-supposed-to-exist"});s.bind(n.listContainer,"click",(function(e){var r=e.target||e.srcElement,n=t.utils.getAttribute(r,"data-page"),s=t.utils.getAttribute(r,"data-i");s&&t.show((s-1)*n+1,n)})),t.on("updated",(function(){r(n,e)})),r(n,e)}}},"./src/parse.js":function(t,e,r){t.exports=function(t){var e=r("./src/item.js")(t),n=function(r,n){for(var s=0,i=r.length;s0?setTimeout((function(){e(r,s)}),1):(t.update(),t.trigger("parseComplete"))};return t.handlers.parseComplete=t.handlers.parseComplete||[],function(){var e=function(t){for(var e=t.childNodes,r=[],n=0,s=e.length;n]/g.exec(t)){var e=document.createElement("tbody");return e.innerHTML=t,e.firstElementChild}if(-1!==t.indexOf("<")){var r=document.createElement("div");return r.innerHTML=t,r.firstElementChild}}},a=function(e,r,n){var s=void 0,i=function(e){for(var r=0,n=t.valueNames.length;r=1;)t.list.removeChild(t.list.firstChild)},function(){var r;if("function"!=typeof t.item){if(!(r="string"==typeof t.item?-1===t.item.indexOf("<")?document.getElementById(t.item):i(t.item):s()))throw new Error("The list needs to have at least one item on init otherwise you'll have to add a template.");r=n(r,t.valueNames),e=function(){return r.cloneNode(!0)}}else e=function(e){var r=t.item(e);return i(r)}}()};t.exports=function(t){return new e(t)}},"./src/utils/classes.js":function(t,e,r){var n=r("./src/utils/index-of.js"),s=/\s+/;Object.prototype.toString;function i(t){if(!t||!t.nodeType)throw new Error("A DOM element reference is required");this.el=t,this.list=t.classList}t.exports=function(t){return new i(t)},i.prototype.add=function(t){if(this.list)return this.list.add(t),this;var e=this.array();return~n(e,t)||e.push(t),this.el.className=e.join(" "),this},i.prototype.remove=function(t){if(this.list)return this.list.remove(t),this;var e=this.array(),r=n(e,t);return~r&&e.splice(r,1),this.el.className=e.join(" "),this},i.prototype.toggle=function(t,e){return this.list?(void 0!==e?e!==this.list.toggle(t,e)&&this.list.toggle(t):this.list.toggle(t),this):(void 0!==e?e?this.add(t):this.remove(t):this.has(t)?this.remove(t):this.add(t),this)},i.prototype.array=function(){var t=(this.el.getAttribute("class")||"").replace(/^\s+|\s+$/g,"").split(s);return""===t[0]&&t.shift(),t},i.prototype.has=i.prototype.contains=function(t){return this.list?this.list.contains(t):!!~n(this.array(),t)}},"./src/utils/events.js":function(t,e,r){var n=window.addEventListener?"addEventListener":"attachEvent",s=window.removeEventListener?"removeEventListener":"detachEvent",i="addEventListener"!==n?"on":"",a=r("./src/utils/to-array.js");e.bind=function(t,e,r,s){for(var o=0,l=(t=a(t)).length;o32)return!1;var a=n,o=function(){var t,r={};for(t=0;t=p;b--){var j=o[t.charAt(b-1)];if(C[b]=0===m?(C[b+1]<<1|1)&j:(C[b+1]<<1|1)&j|(v[b+1]|v[b])<<1|1|v[b+1],C[b]&d){var x=l(m,b-1);if(x<=u){if(u=x,!((c=b-1)>a))break;p=Math.max(1,2*a-c)}}}if(l(m+1,a)>u)break;v=C}return!(c<0)}},"./src/utils/get-attribute.js":function(t){t.exports=function(t,e){var r=t.getAttribute&&t.getAttribute(e)||null;if(!r)for(var n=t.attributes,s=n.length,i=0;i=48&&t<=57}function i(t,e){for(var i=(t+="").length,a=(e+="").length,o=0,l=0;o=i&&l=a?-1:l>=a&&o=i?1:i-a}i.caseInsensitive=i.i=function(t,e){return i((""+t).toLowerCase(),(""+e).toLowerCase())},Object.defineProperties(i,{alphabet:{get:function(){return e},set:function(t){r=[];var s=0;if(e=t)for(;s { - if (categoriesLoaded) { - activateCategory(category); - setCategoryHash(category); - } -}; - -window["quarto-listing-loaded"] = () => { - // Process any existing hash - const hash = getHash(); - - if (hash) { - // If there is a category, switch to that - if (hash.category) { - activateCategory(hash.category); - } - // Paginate a specific listing - const listingIds = Object.keys(window["quarto-listings"]); - for (const listingId of listingIds) { - const page = hash[getListingPageKey(listingId)]; - if (page) { - showPage(listingId, page); - } - } - } - - const listingIds = Object.keys(window["quarto-listings"]); - for (const listingId of listingIds) { - // The actual list - const list = window["quarto-listings"][listingId]; - - // Update the handlers for pagination events - refreshPaginationHandlers(listingId); - - // Render any visible items that need it - renderVisibleProgressiveImages(list); - - // Whenever the list is updated, we also need to - // attach handlers to the new pagination elements - // and refresh any newly visible items. - list.on("updated", function () { - renderVisibleProgressiveImages(list); - setTimeout(() => refreshPaginationHandlers(listingId)); - - // Show or hide the no matching message - toggleNoMatchingMessage(list); - }); - } -}; - -window.document.addEventListener("DOMContentLoaded", function (_event) { - // Attach click handlers to categories - const categoryEls = window.document.querySelectorAll( - ".quarto-listing-category .category" - ); - - for (const categoryEl of categoryEls) { - const category = categoryEl.getAttribute("data-category"); - categoryEl.onclick = () => { - activateCategory(category); - setCategoryHash(category); - }; - } - - // Attach a click handler to the category title - // (there should be only one, but since it is a class name, handle N) - const categoryTitleEls = window.document.querySelectorAll( - ".quarto-listing-category-title" - ); - for (const categoryTitleEl of categoryTitleEls) { - categoryTitleEl.onclick = () => { - activateCategory(""); - setCategoryHash(""); - }; - } - - categoriesLoaded = true; -}); - -function toggleNoMatchingMessage(list) { - const selector = `#${list.listContainer.id} .listing-no-matching`; - const noMatchingEl = window.document.querySelector(selector); - if (noMatchingEl) { - if (list.visibleItems.length === 0) { - noMatchingEl.classList.remove("d-none"); - } else { - if (!noMatchingEl.classList.contains("d-none")) { - noMatchingEl.classList.add("d-none"); - } - } - } -} - -function setCategoryHash(category) { - setHash({ category }); -} - -function setPageHash(listingId, page) { - const currentHash = getHash() || {}; - currentHash[getListingPageKey(listingId)] = page; - setHash(currentHash); -} - -function getListingPageKey(listingId) { - return `${listingId}-page`; -} - -function refreshPaginationHandlers(listingId) { - const listingEl = window.document.getElementById(listingId); - const paginationEls = listingEl.querySelectorAll( - ".pagination li.page-item:not(.disabled) .page.page-link" - ); - for (const paginationEl of paginationEls) { - paginationEl.onclick = (sender) => { - setPageHash(listingId, sender.target.getAttribute("data-i")); - showPage(listingId, sender.target.getAttribute("data-i")); - return false; - }; - } -} - -function renderVisibleProgressiveImages(list) { - // Run through the visible items and render any progressive images - for (const item of list.visibleItems) { - const itemEl = item.elm; - if (itemEl) { - const progressiveImgs = itemEl.querySelectorAll( - `img[${kProgressiveAttr}]` - ); - for (const progressiveImg of progressiveImgs) { - const srcValue = progressiveImg.getAttribute(kProgressiveAttr); - if (srcValue) { - progressiveImg.setAttribute("src", srcValue); - } - progressiveImg.removeAttribute(kProgressiveAttr); - } - } - } -} - -function getHash() { - // Hashes are of the form - // #name:value|name1:value1|name2:value2 - const currentUrl = new URL(window.location); - const hashRaw = currentUrl.hash ? currentUrl.hash.slice(1) : undefined; - return parseHash(hashRaw); -} - -const kAnd = "&"; -const kEquals = "="; - -function parseHash(hash) { - if (!hash) { - return undefined; - } - const hasValuesStrs = hash.split(kAnd); - const hashValues = hasValuesStrs - .map((hashValueStr) => { - const vals = hashValueStr.split(kEquals); - if (vals.length === 2) { - return { name: vals[0], value: vals[1] }; - } else { - return undefined; - } - }) - .filter((value) => { - return value !== undefined; - }); - - const hashObj = {}; - hashValues.forEach((hashValue) => { - hashObj[hashValue.name] = decodeURIComponent(hashValue.value); - }); - return hashObj; -} - -function makeHash(obj) { - return Object.keys(obj) - .map((key) => { - return `${key}${kEquals}${obj[key]}`; - }) - .join(kAnd); -} - -function setHash(obj) { - const hash = makeHash(obj); - window.history.pushState(null, null, `#${hash}`); -} - -function showPage(listingId, page) { - const list = window["quarto-listings"][listingId]; - if (list) { - list.show((page - 1) * list.page + 1, list.page); - } -} - -function activateCategory(category) { - // Deactivate existing categories - const activeEls = window.document.querySelectorAll( - ".quarto-listing-category .category.active" - ); - for (const activeEl of activeEls) { - activeEl.classList.remove("active"); - } - - // Activate this category - const categoryEl = window.document.querySelector( - `.quarto-listing-category .category[data-category='${category}'` - ); - if (categoryEl) { - categoryEl.classList.add("active"); - } - - // Filter the listings to this category - filterListingCategory(category); -} - -function filterListingCategory(category) { - const listingIds = Object.keys(window["quarto-listings"]); - for (const listingId of listingIds) { - const list = window["quarto-listings"][listingId]; - if (list) { - if (category === "") { - // resets the filter - list.filter(); - } else { - // filter to this category - list.filter(function (item) { - const itemValues = item.values(); - if (itemValues.categories !== null) { - const categories = itemValues.categories.split(","); - return categories.includes(category); - } else { - return false; - } - }); - } - } - } -} diff --git a/_proc/_docs/site_libs/quarto-nav/headroom.min.js b/_proc/_docs/site_libs/quarto-nav/headroom.min.js deleted file mode 100644 index b08f1df..0000000 --- a/_proc/_docs/site_libs/quarto-nav/headroom.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * headroom.js v0.12.0 - Give your page some headroom. Hide your header until you need it - * Copyright (c) 2020 Nick Williams - http://wicky.nillia.ms/headroom.js - * License: MIT - */ - -!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(t=t||self).Headroom=n()}(this,function(){"use strict";function t(){return"undefined"!=typeof window}function d(t){return function(t){return t&&t.document&&function(t){return 9===t.nodeType}(t.document)}(t)?function(t){var n=t.document,o=n.body,s=n.documentElement;return{scrollHeight:function(){return Math.max(o.scrollHeight,s.scrollHeight,o.offsetHeight,s.offsetHeight,o.clientHeight,s.clientHeight)},height:function(){return t.innerHeight||s.clientHeight||o.clientHeight},scrollY:function(){return void 0!==t.pageYOffset?t.pageYOffset:(s||o.parentNode||o).scrollTop}}}(t):function(t){return{scrollHeight:function(){return Math.max(t.scrollHeight,t.offsetHeight,t.clientHeight)},height:function(){return Math.max(t.offsetHeight,t.clientHeight)},scrollY:function(){return t.scrollTop}}}(t)}function n(t,s,e){var n,o=function(){var n=!1;try{var t={get passive(){n=!0}};window.addEventListener("test",t,t),window.removeEventListener("test",t,t)}catch(t){n=!1}return n}(),i=!1,r=d(t),l=r.scrollY(),a={};function c(){var t=Math.round(r.scrollY()),n=r.height(),o=r.scrollHeight();a.scrollY=t,a.lastScrollY=l,a.direction=ls.tolerance[a.direction],e(a),l=t,i=!1}function h(){i||(i=!0,n=requestAnimationFrame(c))}var u=!!o&&{passive:!0,capture:!1};return t.addEventListener("scroll",h,u),c(),{destroy:function(){cancelAnimationFrame(n),t.removeEventListener("scroll",h,u)}}}function o(t){return t===Object(t)?t:{down:t,up:t}}function s(t,n){n=n||{},Object.assign(this,s.options,n),this.classes=Object.assign({},s.options.classes,n.classes),this.elem=t,this.tolerance=o(this.tolerance),this.offset=o(this.offset),this.initialised=!1,this.frozen=!1}return s.prototype={constructor:s,init:function(){return s.cutsTheMustard&&!this.initialised&&(this.addClass("initial"),this.initialised=!0,setTimeout(function(t){t.scrollTracker=n(t.scroller,{offset:t.offset,tolerance:t.tolerance},t.update.bind(t))},100,this)),this},destroy:function(){this.initialised=!1,Object.keys(this.classes).forEach(this.removeClass,this),this.scrollTracker.destroy()},unpin:function(){!this.hasClass("pinned")&&this.hasClass("unpinned")||(this.addClass("unpinned"),this.removeClass("pinned"),this.onUnpin&&this.onUnpin.call(this))},pin:function(){this.hasClass("unpinned")&&(this.addClass("pinned"),this.removeClass("unpinned"),this.onPin&&this.onPin.call(this))},freeze:function(){this.frozen=!0,this.addClass("frozen")},unfreeze:function(){this.frozen=!1,this.removeClass("frozen")},top:function(){this.hasClass("top")||(this.addClass("top"),this.removeClass("notTop"),this.onTop&&this.onTop.call(this))},notTop:function(){this.hasClass("notTop")||(this.addClass("notTop"),this.removeClass("top"),this.onNotTop&&this.onNotTop.call(this))},bottom:function(){this.hasClass("bottom")||(this.addClass("bottom"),this.removeClass("notBottom"),this.onBottom&&this.onBottom.call(this))},notBottom:function(){this.hasClass("notBottom")||(this.addClass("notBottom"),this.removeClass("bottom"),this.onNotBottom&&this.onNotBottom.call(this))},shouldUnpin:function(t){return"down"===t.direction&&!t.top&&t.toleranceExceeded},shouldPin:function(t){return"up"===t.direction&&t.toleranceExceeded||t.top},addClass:function(t){this.elem.classList.add.apply(this.elem.classList,this.classes[t].split(" "))},removeClass:function(t){this.elem.classList.remove.apply(this.elem.classList,this.classes[t].split(" "))},hasClass:function(t){return this.classes[t].split(" ").every(function(t){return this.classList.contains(t)},this.elem)},update:function(t){t.isOutOfBounds||!0!==this.frozen&&(t.top?this.top():this.notTop(),t.bottom?this.bottom():this.notBottom(),this.shouldUnpin(t)?this.unpin():this.shouldPin(t)&&this.pin())}},s.options={tolerance:{up:0,down:0},offset:0,scroller:t()?window:null,classes:{frozen:"headroom--frozen",pinned:"headroom--pinned",unpinned:"headroom--unpinned",top:"headroom--top",notTop:"headroom--not-top",bottom:"headroom--bottom",notBottom:"headroom--not-bottom",initial:"headroom"}},s.cutsTheMustard=!!(t()&&function(){}.bind&&"classList"in document.documentElement&&Object.assign&&Object.keys&&requestAnimationFrame),s}); diff --git a/_proc/_docs/site_libs/quarto-nav/quarto-nav.js b/_proc/_docs/site_libs/quarto-nav/quarto-nav.js deleted file mode 100644 index b41b31e..0000000 --- a/_proc/_docs/site_libs/quarto-nav/quarto-nav.js +++ /dev/null @@ -1,222 +0,0 @@ -const headroomChanged = new CustomEvent("quarto-hrChanged", { - detail: {}, - bubbles: true, - cancelable: false, - composed: false, -}); - -window.document.addEventListener("DOMContentLoaded", function () { - let init = false; - - function throttle(func, wait) { - var timeout; - return function () { - const context = this; - const args = arguments; - const later = function () { - clearTimeout(timeout); - timeout = null; - func.apply(context, args); - }; - - if (!timeout) { - timeout = setTimeout(later, wait); - } - }; - } - - function headerOffset() { - // Set an offset if there is are fixed top navbar - const headerEl = window.document.querySelector("header.fixed-top"); - if (headerEl) { - return headerEl.clientHeight; - } else { - return 0; - } - } - - function footerOffset() { - const footerEl = window.document.querySelector("footer.footer"); - if (footerEl) { - return footerEl.clientHeight; - } else { - return 0; - } - } - - function updateDocumentOffsetWithoutAnimation() { - updateDocumentOffset(false); - } - - function updateDocumentOffset(animated) { - // set body offset - const topOffset = headerOffset(); - const bodyOffset = topOffset + footerOffset(); - const bodyEl = window.document.body; - bodyEl.setAttribute("data-bs-offset", topOffset); - bodyEl.style.paddingTop = topOffset + "px"; - - // deal with sidebar offsets - const sidebars = window.document.querySelectorAll( - ".sidebar, .headroom-target" - ); - sidebars.forEach((sidebar) => { - if (!animated) { - sidebar.classList.add("notransition"); - // Remove the no transition class after the animation has time to complete - setTimeout(function () { - sidebar.classList.remove("notransition"); - }, 201); - } - - if (window.Headroom && sidebar.classList.contains("sidebar-unpinned")) { - sidebar.style.top = "0"; - sidebar.style.maxHeight = "100vh"; - } else { - sidebar.style.top = topOffset + "px"; - sidebar.style.maxHeight = "calc(100vh - " + topOffset + "px)"; - } - }); - - // allow space for footer - const mainContainer = window.document.querySelector(".quarto-container"); - if (mainContainer) { - mainContainer.style.minHeight = "calc(100vh - " + bodyOffset + "px)"; - } - - // link offset - let linkStyle = window.document.querySelector("#quarto-target-style"); - if (!linkStyle) { - linkStyle = window.document.createElement("style"); - linkStyle.setAttribute("id", "quarto-target-style"); - window.document.head.appendChild(linkStyle); - } - while (linkStyle.firstChild) { - linkStyle.removeChild(linkStyle.firstChild); - } - if (topOffset > 0) { - linkStyle.appendChild( - window.document.createTextNode(` - section:target::before { - content: ""; - display: block; - height: ${topOffset}px; - margin: -${topOffset}px 0 0; - }`) - ); - } - if (init) { - window.dispatchEvent(headroomChanged); - } - init = true; - } - - // initialize headroom - var header = window.document.querySelector("#quarto-header"); - if (header && window.Headroom) { - const headroom = new window.Headroom(header, { - tolerance: 5, - onPin: function () { - const sidebars = window.document.querySelectorAll( - ".sidebar, .headroom-target" - ); - sidebars.forEach((sidebar) => { - sidebar.classList.remove("sidebar-unpinned"); - }); - updateDocumentOffset(); - }, - onUnpin: function () { - const sidebars = window.document.querySelectorAll( - ".sidebar, .headroom-target" - ); - sidebars.forEach((sidebar) => { - sidebar.classList.add("sidebar-unpinned"); - }); - updateDocumentOffset(); - }, - }); - headroom.init(); - - let frozen = false; - window.quartoToggleHeadroom = function () { - if (frozen) { - headroom.unfreeze(); - frozen = false; - } else { - headroom.freeze(); - frozen = true; - } - }; - } - - // Observe size changed for the header - const headerEl = window.document.querySelector("header.fixed-top"); - if (headerEl && window.ResizeObserver) { - const observer = new window.ResizeObserver( - updateDocumentOffsetWithoutAnimation - ); - observer.observe(headerEl, { - attributes: true, - childList: true, - characterData: true, - }); - } else { - window.addEventListener( - "resize", - throttle(updateDocumentOffsetWithoutAnimation, 50) - ); - } - setTimeout(updateDocumentOffsetWithoutAnimation, 250); - - // fixup index.html links if we aren't on the filesystem - if (window.location.protocol !== "file:") { - const links = window.document.querySelectorAll("a"); - for (let i = 0; i < links.length; i++) { - links[i].href = links[i].href.replace(/\/index\.html/, "/"); - } - - // Fixup any sharing links that require urls - // Append url to any sharing urls - const sharingLinks = window.document.querySelectorAll( - "a.sidebar-tools-main-item" - ); - for (let i = 0; i < sharingLinks.length; i++) { - const sharingLink = sharingLinks[i]; - const href = sharingLink.getAttribute("href"); - if (href) { - sharingLink.setAttribute( - "href", - href.replace("|url|", window.location.href) - ); - } - } - - // Scroll the active navigation item into view, if necessary - const navSidebar = window.document.querySelector("nav#quarto-sidebar"); - if (navSidebar) { - // Find the active item - const activeItem = navSidebar.querySelector("li.sidebar-item a.active"); - if (activeItem) { - // Wait for the scroll height and height to resolve by observing size changes on the - // nav element that is scrollable - const resizeObserver = new ResizeObserver((_entries) => { - // The bottom of the element - const elBottom = activeItem.offsetTop; - const viewBottom = navSidebar.scrollTop + navSidebar.clientHeight; - - // The element height and scroll height are the same, then we are still loading - if (viewBottom !== navSidebar.scrollHeight) { - // Determine if the item isn't visible and scroll to it - if (elBottom >= viewBottom) { - navSidebar.scrollTop = elBottom; - } - - // stop observing now since we've completed the scroll - resizeObserver.unobserve(navSidebar); - } - }); - resizeObserver.observe(navSidebar); - } - } - } -}); diff --git a/_proc/_docs/site_libs/quarto-search/autocomplete.umd.js b/_proc/_docs/site_libs/quarto-search/autocomplete.umd.js deleted file mode 100644 index 3f2dcf0..0000000 --- a/_proc/_docs/site_libs/quarto-search/autocomplete.umd.js +++ /dev/null @@ -1,3 +0,0 @@ -/*! @algolia/autocomplete-js 1.5.3 | MIT License | © Algolia, Inc. and contributors | https://github.com/algolia/autocomplete */ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self)["@algolia/autocomplete-js"]={})}(this,(function(e){"use strict";function t(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function n(e){for(var n=1;n=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function a(e){return function(e){if(Array.isArray(e))return c(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,t){if(!e)return;if("string"==typeof e)return c(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return c(e,t)}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function c(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n=n?null===r?null:0:o}function j(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function w(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function S(e,t){var n=[];return Promise.resolve(e(t)).then((function(e){return Promise.all(e.filter((function(e){return Boolean(e)})).map((function(e){if(e.sourceId,n.includes(e.sourceId))throw new Error("[Autocomplete] The `sourceId` ".concat(JSON.stringify(e.sourceId)," is not unique."));n.push(e.sourceId);var t=function(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=new Array(t);ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var oe,ie,ue,ae=null,ce=(oe=-1,ie=-1,ue=void 0,function(e){var t=++oe;return Promise.resolve(e).then((function(e){return ue&&t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var me=["props","refresh","store"],he=["inputElement","formElement","panelElement"],ge=["inputElement"],ye=["inputElement","maxLength"],be=["item","source"];function Oe(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function _e(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function we(e){var t=e.props,n=e.refresh,r=e.store,o=je(e,me);return{getEnvironmentProps:function(e){var n=e.inputElement,o=e.formElement,i=e.panelElement;return _e({onTouchStart:function(e){!r.getState().isOpen&&r.pendingRequests.isEmpty()||e.target===n||!1===[o,i].some((function(t){return n=t,r=e.target,n===r||n.contains(r);var n,r}))&&(r.dispatch("blur",null),t.debug||r.pendingRequests.cancelAll())},onTouchMove:function(e){!1!==r.getState().isOpen&&n===t.environment.document.activeElement&&e.target!==n&&n.blur()}},je(e,he))},getRootProps:function(e){return _e({role:"combobox","aria-expanded":r.getState().isOpen,"aria-haspopup":"listbox","aria-owns":r.getState().isOpen?"".concat(t.id,"-list"):void 0,"aria-labelledby":"".concat(t.id,"-label")},e)},getFormProps:function(e){return e.inputElement,_e({action:"",noValidate:!0,role:"search",onSubmit:function(i){var u;i.preventDefault(),t.onSubmit(_e({event:i,refresh:n,state:r.getState()},o)),r.dispatch("submit",null),null===(u=e.inputElement)||void 0===u||u.blur()},onReset:function(i){var u;i.preventDefault(),t.onReset(_e({event:i,refresh:n,state:r.getState()},o)),r.dispatch("reset",null),null===(u=e.inputElement)||void 0===u||u.focus()}},je(e,ge))},getLabelProps:function(e){return _e({htmlFor:"".concat(t.id,"-input"),id:"".concat(t.id,"-label")},e)},getInputProps:function(e){function i(e){(t.openOnFocus||Boolean(r.getState().query))&&le(_e({event:e,props:t,query:r.getState().completion||r.getState().query,refresh:n,store:r},o)),r.dispatch("focus",null)}var u="ontouchstart"in t.environment,a=e||{};a.inputElement;var c=a.maxLength,l=void 0===c?512:c,s=je(a,ye),p=I(r.getState());return _e({"aria-autocomplete":"both","aria-activedescendant":r.getState().isOpen&&null!==r.getState().activeItemId?"".concat(t.id,"-item-").concat(r.getState().activeItemId):void 0,"aria-controls":r.getState().isOpen?"".concat(t.id,"-list"):void 0,"aria-labelledby":"".concat(t.id,"-label"),value:r.getState().completion||r.getState().query,id:"".concat(t.id,"-input"),autoComplete:"off",autoCorrect:"off",autoCapitalize:"off",enterKeyHint:null!=p&&p.itemUrl?"go":"search",spellCheck:"false",autoFocus:t.autoFocus,placeholder:t.placeholder,maxLength:l,type:"search",onChange:function(e){le(_e({event:e,props:t,query:e.currentTarget.value.slice(0,l),refresh:n,store:r},o))},onKeyDown:function(e){!function(e){var t=e.event,n=e.props,r=e.refresh,o=e.store,i=ve(e,se);if("ArrowUp"===t.key||"ArrowDown"===t.key){var u=function(){var e=n.environment.document.getElementById("".concat(n.id,"-item-").concat(o.getState().activeItemId));e&&(e.scrollIntoViewIfNeeded?e.scrollIntoViewIfNeeded(!1):e.scrollIntoView(!1))},a=function(){var e=I(o.getState());if(null!==o.getState().activeItemId&&e){var n=e.item,u=e.itemInputValue,a=e.itemUrl,c=e.source;c.onActive(fe({event:t,item:n,itemInputValue:u,itemUrl:a,refresh:r,source:c,state:o.getState()},i))}};t.preventDefault(),!1===o.getState().isOpen&&(n.openOnFocus||Boolean(o.getState().query))?le(fe({event:t,props:n,query:o.getState().query,refresh:r,store:o},i)).then((function(){o.dispatch(t.key,{nextActiveItemId:n.defaultActiveItemId}),a(),setTimeout(u,0)})):(o.dispatch(t.key,{}),a(),u())}else if("Escape"===t.key)t.preventDefault(),o.dispatch(t.key,null),o.pendingRequests.cancelAll();else if("Enter"===t.key){if(null===o.getState().activeItemId||o.getState().collections.every((function(e){return 0===e.items.length})))return;t.preventDefault();var c=I(o.getState()),l=c.item,s=c.itemInputValue,p=c.itemUrl,f=c.source;if(t.metaKey||t.ctrlKey)void 0!==p&&(f.onSelect(fe({event:t,item:l,itemInputValue:s,itemUrl:p,refresh:r,source:f,state:o.getState()},i)),n.navigator.navigateNewTab({itemUrl:p,item:l,state:o.getState()}));else if(t.shiftKey)void 0!==p&&(f.onSelect(fe({event:t,item:l,itemInputValue:s,itemUrl:p,refresh:r,source:f,state:o.getState()},i)),n.navigator.navigateNewWindow({itemUrl:p,item:l,state:o.getState()}));else if(t.altKey);else{if(void 0!==p)return f.onSelect(fe({event:t,item:l,itemInputValue:s,itemUrl:p,refresh:r,source:f,state:o.getState()},i)),void n.navigator.navigate({itemUrl:p,item:l,state:o.getState()});le(fe({event:t,nextState:{isOpen:!1},props:n,query:s,refresh:r,store:o},i)).then((function(){f.onSelect(fe({event:t,item:l,itemInputValue:s,itemUrl:p,refresh:r,source:f,state:o.getState()},i))}))}}}(_e({event:e,props:t,refresh:n,store:r},o))},onFocus:i,onBlur:function(){u||(r.dispatch("blur",null),t.debug||r.pendingRequests.cancelAll())},onClick:function(n){e.inputElement!==t.environment.document.activeElement||r.getState().isOpen||i(n)}},s)},getPanelProps:function(e){return _e({onMouseDown:function(e){e.preventDefault()},onMouseLeave:function(){r.dispatch("mouseleave",null)}},e)},getListProps:function(e){return _e({role:"listbox","aria-labelledby":"".concat(t.id,"-label"),id:"".concat(t.id,"-list")},e)},getItemProps:function(e){var i=e.item,u=e.source,a=je(e,be);return _e({id:"".concat(t.id,"-item-").concat(i.__autocomplete_id),role:"option","aria-selected":r.getState().activeItemId===i.__autocomplete_id,onMouseMove:function(e){if(i.__autocomplete_id!==r.getState().activeItemId){r.dispatch("mousemove",i.__autocomplete_id);var t=I(r.getState());if(null!==r.getState().activeItemId&&t){var u=t.item,a=t.itemInputValue,c=t.itemUrl,l=t.source;l.onActive(_e({event:e,item:u,itemInputValue:a,itemUrl:c,refresh:n,source:l,state:r.getState()},o))}}},onMouseDown:function(e){e.preventDefault()},onClick:function(e){var a=u.getItemInputValue({item:i,state:r.getState()}),c=u.getItemUrl({item:i,state:r.getState()});(c?Promise.resolve():le(_e({event:e,nextState:{isOpen:!1},props:t,query:a,refresh:n,store:r},o))).then((function(){u.onSelect(_e({event:e,item:i,itemInputValue:a,itemUrl:c,refresh:n,source:u,state:r.getState()},o))}))}},a)}}}function Se(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function Ie(e){for(var t=1;t0},reshape:function(e){return e.sources}},e),{},{id:null!==(n=e.id)&&void 0!==n?n:d(),plugins:o,initialState:F({activeItemId:null,query:"",completion:null,collections:[],isOpen:!1,status:"idle",context:{}},e.initialState),onStateChange:function(t){var n;null===(n=e.onStateChange)||void 0===n||n.call(e,t),o.forEach((function(e){var n;return null===(n=e.onStateChange)||void 0===n?void 0:n.call(e,t)}))},onSubmit:function(t){var n;null===(n=e.onSubmit)||void 0===n||n.call(e,t),o.forEach((function(e){var n;return null===(n=e.onSubmit)||void 0===n?void 0:n.call(e,t)}))},onReset:function(t){var n;null===(n=e.onReset)||void 0===n||n.call(e,t),o.forEach((function(e){var n;return null===(n=e.onReset)||void 0===n?void 0:n.call(e,t)}))},getSources:function(n){return Promise.all([].concat(R(o.map((function(e){return e.getSources}))),[e.getSources]).filter(Boolean).map((function(e){return S(e,n)}))).then((function(e){return p(e)})).then((function(e){return e.map((function(e){return F(F({},e),{},{onSelect:function(n){e.onSelect(n),t.forEach((function(e){var t;return null===(t=e.onSelect)||void 0===t?void 0:t.call(e,n)}))},onActive:function(n){e.onActive(n),t.forEach((function(e){var t;return null===(t=e.onActive)||void 0===t?void 0:t.call(e,n)}))}})}))}))},navigator:F({navigate:function(e){var t=e.itemUrl;r.location.assign(t)},navigateNewTab:function(e){var t=e.itemUrl,n=r.open(t,"_blank","noopener");null==n||n.focus()},navigateNewWindow:function(e){var t=e.itemUrl;r.open(t,"_blank","noopener")}},e.navigator)})}(e,t),r=x(qe,n,(function(e){var t=e.prevState,r=e.state;n.onStateChange(Le({prevState:t,state:r,refresh:u},o))})),o=function(e){var t=e.store;return{setActiveItemId:function(e){t.dispatch("setActiveItemId",e)},setQuery:function(e){t.dispatch("setQuery",e)},setCollections:function(e){var n=0,r=e.map((function(e){return N(N({},e),{},{items:p(e.items).map((function(e){return N(N({},e),{},{__autocomplete_id:n++})}))})}));t.dispatch("setCollections",r)},setIsOpen:function(e){t.dispatch("setIsOpen",e)},setStatus:function(e){t.dispatch("setStatus",e)},setContext:function(e){t.dispatch("setContext",e)}}}({store:r}),i=we(Le({props:n,refresh:u,store:r},o));function u(){return le(Le({event:new Event("input"),nextState:{isOpen:r.getState().isOpen},props:n,query:r.getState().query,refresh:u,store:r},o))}return n.plugins.forEach((function(e){var n;return null===(n=e.subscribe)||void 0===n?void 0:n.call(e,Le(Le({},o),{},{refresh:u,onSelect:function(e){t.push({onSelect:e})},onActive:function(e){t.push({onActive:e})}}))})),function(e){var t,n=e.metadata,r=e.environment;if(null===(t=r.navigator)||void 0===t?void 0:t.userAgent.includes("Algolia Crawler")){var o=r.document.createElement("meta"),i=r.document.querySelector("head");o.name="algolia:metadata",setTimeout((function(){o.content=JSON.stringify(n),i.appendChild(o)}),0)}}({metadata:Ae({plugins:n.plugins,options:e}),environment:n.environment}),Le(Le({refresh:u},i),o)}var Te=function(e){var t=e.environment,n=t.document.createElementNS("http://www.w3.org/2000/svg","svg");n.setAttribute("class","aa-ClearIcon"),n.setAttribute("viewBox","0 0 24 24"),n.setAttribute("width","18"),n.setAttribute("height","18"),n.setAttribute("fill","currentColor");var r=t.document.createElementNS("http://www.w3.org/2000/svg","path");return r.setAttribute("d","M5.293 6.707l5.293 5.293-5.293 5.293c-0.391 0.391-0.391 1.024 0 1.414s1.024 0.391 1.414 0l5.293-5.293 5.293 5.293c0.391 0.391 1.024 0.391 1.414 0s0.391-1.024 0-1.414l-5.293-5.293 5.293-5.293c0.391-0.391 0.391-1.024 0-1.414s-1.024-0.391-1.414 0l-5.293 5.293-5.293-5.293c-0.391-0.391-1.024-0.391-1.414 0s-0.391 1.024 0 1.414z"),n.appendChild(r),n};function Fe(e,t){if("string"==typeof t){var n=e.document.querySelector(t);return"The element ".concat(JSON.stringify(t)," is not in the document."),n}return t}function Ue(){for(var e=arguments.length,t=new Array(e),n=0;n2&&(u.children=arguments.length>3?tt.call(arguments,2):n),"function"==typeof e&&null!=e.defaultProps)for(i in e.defaultProps)void 0===u[i]&&(u[i]=e.defaultProps[i]);return dt(e,u,r,o,null)}function dt(e,t,n,r,o){var i={type:e,props:t,key:n,ref:r,__k:null,__:null,__b:0,__e:null,__d:void 0,__c:null,__h:null,constructor:void 0,__v:null==o?++rt:o};return null==o&&null!=nt.vnode&&nt.vnode(i),i}function vt(e){return e.children}function mt(e,t){this.props=e,this.context=t}function ht(e,t){if(null==t)return e.__?ht(e.__,e.__.__k.indexOf(e)+1):null;for(var n;t0?dt(d.type,d.props,d.key,null,d.__v):d)){if(d.__=n,d.__b=n.__b+1,null===(f=g[s])||f&&d.key==f.key&&d.type===f.type)g[s]=void 0;else for(p=0;p0&&void 0!==arguments[0]?arguments[0]:[];return{get:function(){return e},add:function(t){var n=e[e.length-1];(null==n?void 0:n.isHighlighted)===t.isHighlighted?e[e.length-1]={value:n.value+t.value,isHighlighted:n.isHighlighted}:e.push(t)}}}(n?[{value:n,isHighlighted:!1}]:[]);return t.forEach((function(e){var t=e.split(Nt);r.add({value:t[0],isHighlighted:!0}),""!==t[1]&&r.add({value:t[1],isHighlighted:!1})})),r.get()}function Rt(e){return function(e){if(Array.isArray(e))return Bt(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,t){if(!e)return;if("string"==typeof e)return Bt(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return Bt(e,t)}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function Bt(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n",""":'"',"'":"'"},Ut=new RegExp(/\w/i),Mt=/&(amp|quot|lt|gt|#39);/g,Ht=RegExp(Mt.source);function Vt(e,t){var n,r,o,i=e[t],u=(null===(n=e[t+1])||void 0===n?void 0:n.isHighlighted)||!0,a=(null===(r=e[t-1])||void 0===r?void 0:r.isHighlighted)||!0;return Ut.test((o=i.value)&&Ht.test(o)?o.replace(Mt,(function(e){return Ft[e]})):o)||a!==u?i.isHighlighted:a}function Wt(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function Qt(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function un(e){return function(e){if(Array.isArray(e))return an(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,t){if(!e)return;if("string"==typeof e)return an(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return an(e,t)}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function an(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0;if(!O.value.core.openOnFocus&&!t.query)return n;var r=Boolean(g.current||O.value.renderer.renderNoResults);return!n&&r||n},__autocomplete_metadata:{userAgents:hn,options:e}}))})),j=l(n({collections:[],completion:null,context:{},isOpen:!1,query:"",activeItemId:null,status:"idle"},O.value.core.initialState)),w={getEnvironmentProps:O.value.renderer.getEnvironmentProps,getFormProps:O.value.renderer.getFormProps,getInputProps:O.value.renderer.getInputProps,getItemProps:O.value.renderer.getItemProps,getLabelProps:O.value.renderer.getLabelProps,getListProps:O.value.renderer.getListProps,getPanelProps:O.value.renderer.getPanelProps,getRootProps:O.value.renderer.getRootProps},S={setActiveItemId:P.value.setActiveItemId,setQuery:P.value.setQuery,setCollections:P.value.setCollections,setIsOpen:P.value.setIsOpen,setStatus:P.value.setStatus,setContext:P.value.setContext,refresh:P.value.refresh},I=v((function(){return et({autocomplete:P.value,autocompleteScopeApi:S,classNames:O.value.renderer.classNames,environment:O.value.core.environment,isDetached:_.value,placeholder:O.value.core.placeholder,propGetters:w,setIsModalOpen:D,state:j.current,translations:O.value.renderer.translations})}));function E(){ze(I.value.panel,{style:_.value?{}:mn({panelPlacement:O.value.renderer.panelPlacement,container:I.value.root,form:I.value.form,environment:O.value.core.environment})})}function A(e){j.current=e;var t={autocomplete:P.value,autocompleteScopeApi:S,classNames:O.value.renderer.classNames,components:O.value.renderer.components,container:O.value.renderer.container,createElement:O.value.renderer.renderer.createElement,dom:I.value,Fragment:O.value.renderer.renderer.Fragment,panelContainer:_.value?I.value.detachedContainer:O.value.renderer.panelContainer,propGetters:w,state:j.current},r=!m(e)&&!g.current&&O.value.renderer.renderNoResults||O.value.renderer.render;!function(e){var t=e.autocomplete,r=e.autocompleteScopeApi,o=e.dom,i=e.propGetters,u=e.state;Ge(o.root,i.getRootProps(n({state:u,props:t.getRootProps({})},r))),Ge(o.input,i.getInputProps(n({state:u,props:t.getInputProps({inputElement:o.input}),inputElement:o.input},r))),ze(o.label,{hidden:"stalled"===u.status}),ze(o.loadingIndicator,{hidden:"stalled"!==u.status}),ze(o.clearButton,{hidden:!u.query})}(t),function(e,t){var r=t.autocomplete,o=t.autocompleteScopeApi,u=t.classNames,a=t.createElement,c=t.dom,l=t.Fragment,s=t.panelContainer,p=t.propGetters,f=t.state,d=t.components;if(f.isOpen){s.contains(c.panel)||"loading"===f.status||s.appendChild(c.panel),c.panel.classList.toggle("aa-Panel--stalled","stalled"===f.status);var v=f.collections.filter((function(e){var t=e.source,n=e.items;return t.templates.noResults||n.length>0})).map((function(e,t){var c=e.source,s=e.items;return a("section",{key:t,className:u.source,"data-autocomplete-source-id":c.sourceId},c.templates.header&&a("div",{className:u.sourceHeader},c.templates.header({components:d,createElement:a,Fragment:l,items:s,source:c,state:f})),c.templates.noResults&&0===s.length?a("div",{className:u.sourceNoResults},c.templates.noResults({components:d,createElement:a,Fragment:l,source:c,state:f})):a("ul",i({className:u.list},p.getListProps(n({state:f,props:r.getListProps({})},o))),s.map((function(e){var t=r.getItemProps({item:e,source:c});return a("li",i({key:t.id,className:u.item},p.getItemProps(n({state:f,props:t},o))),c.templates.item({components:d,createElement:a,Fragment:l,item:e,state:f}))}))),c.templates.footer&&a("div",{className:u.sourceFooter},c.templates.footer({components:d,createElement:a,Fragment:l,items:s,source:c,state:f})))})),m=a(l,null,a("div",{className:u.panelLayout},v),a("div",{className:"aa-GradientBottom"})),h=v.reduce((function(e,t){return e[t.props["data-autocomplete-source-id"]]=t,e}),{});e(n({children:m,state:f,sections:v,elements:h,createElement:a,Fragment:l,components:d},o),c.panel)}else s.contains(c.panel)&&s.removeChild(c.panel)}(r,t)}function C(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};c(),y.current=He(O.value.renderer,O.value.core,{initialState:j.current},e),h(),p(),P.value.refresh().then((function(){A(j.current)}))}function D(e){requestAnimationFrame((function(){var t=O.value.core.environment.document.body.contains(I.value.detachedOverlay);e!==t&&(e?(O.value.core.environment.document.body.appendChild(I.value.detachedOverlay),O.value.core.environment.document.body.classList.add("aa-Detached"),I.value.input.focus()):(O.value.core.environment.document.body.removeChild(I.value.detachedOverlay),O.value.core.environment.document.body.classList.remove("aa-Detached"),P.value.setQuery(""),P.value.refresh()))}))}return a((function(){var e=P.value.getEnvironmentProps({formElement:I.value.form,panelElement:I.value.panel,inputElement:I.value.input});return ze(O.value.core.environment,e),function(){ze(O.value.core.environment,Object.keys(e).reduce((function(e,t){return n(n({},e),{},o({},t,void 0))}),{}))}})),a((function(){var e=_.value?O.value.core.environment.document.body:O.value.renderer.panelContainer,t=_.value?I.value.detachedOverlay:I.value.panel;return _.value&&j.current.isOpen&&D(!0),A(j.current),function(){e.contains(t)&&e.removeChild(t)}})),a((function(){var e=O.value.renderer.container;return e.appendChild(I.value.root),function(){e.removeChild(I.value.root)}})),a((function(){var e=s((function(e){A(e.state)}),0);return b.current=function(t){var n=t.state,r=t.prevState;(_.value&&r.isOpen!==n.isOpen&&D(n.isOpen),_.value||!n.isOpen||r.isOpen||E(),n.query!==r.query)&&O.value.core.environment.document.querySelectorAll(".aa-Panel--scrollable").forEach((function(e){0!==e.scrollTop&&(e.scrollTop=0)}));e({state:n})},function(){b.current=void 0}})),a((function(){var e=s((function(){var e=_.value;_.value=O.value.core.environment.matchMedia(O.value.renderer.detachedMediaQuery).matches,e!==_.value?C({}):requestAnimationFrame(E)}),20);return O.value.core.environment.addEventListener("resize",e),function(){O.value.core.environment.removeEventListener("resize",e)}})),a((function(){if(!_.value)return function(){};function e(e){I.value.detachedContainer.classList.toggle("aa-DetachedContainer--modal",e)}function t(t){e(t.matches)}var n=O.value.core.environment.matchMedia(getComputedStyle(O.value.core.environment.document.documentElement).getPropertyValue("--aa-detached-modal-media-query"));e(n.matches);var r=Boolean(n.addEventListener);return r?n.addEventListener("change",t):n.addListener(t),function(){r?n.removeEventListener("change",t):n.removeListener(t)}})),a((function(){return requestAnimationFrame(E),function(){}})),n(n({},S),{},{update:C,destroy:function(){c()}})},e.getAlgoliaFacets=function(e){var t=gn({transformResponse:function(e){return e.facetHits}}),r=e.queries.map((function(e){return n(n({},e),{},{type:"facet"})}));return t(n(n({},e),{},{queries:r}))},e.getAlgoliaResults=yn,Object.defineProperty(e,"__esModule",{value:!0})})); - diff --git a/_proc/_docs/site_libs/quarto-search/fuse.min.js b/_proc/_docs/site_libs/quarto-search/fuse.min.js deleted file mode 100644 index ca37378..0000000 --- a/_proc/_docs/site_libs/quarto-search/fuse.min.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Fuse.js v6.5.3 - Lightweight fuzzy-search (http://fusejs.io) - * - * Copyright (c) 2021 Kiro Risk (http://kiro.me) - * All Rights Reserved. Apache Software License 2.0 - * - * http://www.apache.org/licenses/LICENSE-2.0 - */ -var e,t;e=this,t=function(){"use strict";function e(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function t(t){for(var n=1;ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n0&&void 0!==arguments[0]?arguments[0]:1,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:3,n=new Map,r=Math.pow(10,t);return{get:function(t){var i=t.match(C).length;if(n.has(i))return n.get(i);var o=1/Math.pow(i,.5*e),c=parseFloat(Math.round(o*r)/r);return n.set(i,c),c},clear:function(){n.clear()}}}var $=function(){function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=t.getFn,i=void 0===n?I.getFn:n,o=t.fieldNormWeight,c=void 0===o?I.fieldNormWeight:o;r(this,e),this.norm=E(c,3),this.getFn=i,this.isCreated=!1,this.setIndexRecords()}return o(e,[{key:"setSources",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.docs=e}},{key:"setIndexRecords",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.records=e}},{key:"setKeys",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.keys=t,this._keysMap={},t.forEach((function(t,n){e._keysMap[t.id]=n}))}},{key:"create",value:function(){var e=this;!this.isCreated&&this.docs.length&&(this.isCreated=!0,g(this.docs[0])?this.docs.forEach((function(t,n){e._addString(t,n)})):this.docs.forEach((function(t,n){e._addObject(t,n)})),this.norm.clear())}},{key:"add",value:function(e){var t=this.size();g(e)?this._addString(e,t):this._addObject(e,t)}},{key:"removeAt",value:function(e){this.records.splice(e,1);for(var t=e,n=this.size();t2&&void 0!==arguments[2]?arguments[2]:{},r=n.getFn,i=void 0===r?I.getFn:r,o=n.fieldNormWeight,c=void 0===o?I.fieldNormWeight:o,a=new $({getFn:i,fieldNormWeight:c});return a.setKeys(e.map(_)),a.setSources(t),a.create(),a}function F(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.errors,r=void 0===n?0:n,i=t.currentLocation,o=void 0===i?0:i,c=t.expectedLocation,a=void 0===c?0:c,s=t.distance,u=void 0===s?I.distance:s,h=t.ignoreLocation,f=void 0===h?I.ignoreLocation:h,l=r/e.length;if(f)return l;var d=Math.abs(a-o);return u?l+d/u:d?1:l}function N(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:I.minMatchCharLength,n=[],r=-1,i=-1,o=0,c=e.length;o=t&&n.push([r,i]),r=-1)}return e[o-1]&&o-r>=t&&n.push([r,o-1]),n}var P=32;function W(e){for(var t={},n=0,r=e.length;n1&&void 0!==arguments[1]?arguments[1]:{},o=i.location,c=void 0===o?I.location:o,a=i.threshold,s=void 0===a?I.threshold:a,u=i.distance,h=void 0===u?I.distance:u,f=i.includeMatches,l=void 0===f?I.includeMatches:f,d=i.findAllMatches,v=void 0===d?I.findAllMatches:d,g=i.minMatchCharLength,y=void 0===g?I.minMatchCharLength:g,p=i.isCaseSensitive,m=void 0===p?I.isCaseSensitive:p,k=i.ignoreLocation,M=void 0===k?I.ignoreLocation:k;if(r(this,e),this.options={location:c,threshold:s,distance:h,includeMatches:l,findAllMatches:v,minMatchCharLength:y,isCaseSensitive:m,ignoreLocation:M},this.pattern=m?t:t.toLowerCase(),this.chunks=[],this.pattern.length){var b=function(e,t){n.chunks.push({pattern:e,alphabet:W(e),startIndex:t})},x=this.pattern.length;if(x>P){for(var w=0,L=x%P,S=x-L;w3&&void 0!==arguments[3]?arguments[3]:{},i=r.location,o=void 0===i?I.location:i,c=r.distance,a=void 0===c?I.distance:c,s=r.threshold,u=void 0===s?I.threshold:s,h=r.findAllMatches,f=void 0===h?I.findAllMatches:h,l=r.minMatchCharLength,d=void 0===l?I.minMatchCharLength:l,v=r.includeMatches,g=void 0===v?I.includeMatches:v,y=r.ignoreLocation,p=void 0===y?I.ignoreLocation:y;if(t.length>P)throw new Error(w(P));for(var m,k=t.length,M=e.length,b=Math.max(0,Math.min(o,M)),x=u,L=b,S=d>1||g,_=S?Array(M):[];(m=e.indexOf(t,L))>-1;){var O=F(t,{currentLocation:m,expectedLocation:b,distance:a,ignoreLocation:p});if(x=Math.min(O,x),L=m+k,S)for(var j=0;j=z;q-=1){var B=q-1,J=n[e.charAt(B)];if(S&&(_[B]=+!!J),K[q]=(K[q+1]<<1|1)&J,R&&(K[q]|=(A[q+1]|A[q])<<1|1|A[q+1]),K[q]&$&&(C=F(t,{errors:R,currentLocation:B,expectedLocation:b,distance:a,ignoreLocation:p}))<=x){if(x=C,(L=B)<=b)break;z=Math.max(1,2*b-L)}}if(F(t,{errors:R+1,currentLocation:b,expectedLocation:b,distance:a,ignoreLocation:p})>x)break;A=K}var U={isMatch:L>=0,score:Math.max(.001,C)};if(S){var V=N(_,d);V.length?g&&(U.indices=V):U.isMatch=!1}return U}(e,n,i,{location:c+o,distance:a,threshold:s,findAllMatches:u,minMatchCharLength:h,includeMatches:r,ignoreLocation:f}),p=y.isMatch,m=y.score,k=y.indices;p&&(g=!0),v+=m,p&&k&&(d=[].concat(l(d),l(k)))}));var y={isMatch:g,score:g?v/this.chunks.length:1};return g&&r&&(y.indices=d),y}}]),e}(),z=function(){function e(t){r(this,e),this.pattern=t}return o(e,[{key:"search",value:function(){}}],[{key:"isMultiMatch",value:function(e){return D(e,this.multiRegex)}},{key:"isSingleMatch",value:function(e){return D(e,this.singleRegex)}}]),e}();function D(e,t){var n=e.match(t);return n?n[1]:null}var K=function(e){a(n,e);var t=f(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=e===this.pattern;return{isMatch:t,score:t?0:1,indices:[0,this.pattern.length-1]}}}],[{key:"type",get:function(){return"exact"}},{key:"multiRegex",get:function(){return/^="(.*)"$/}},{key:"singleRegex",get:function(){return/^=(.*)$/}}]),n}(z),q=function(e){a(n,e);var t=f(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=-1===e.indexOf(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}}],[{key:"type",get:function(){return"inverse-exact"}},{key:"multiRegex",get:function(){return/^!"(.*)"$/}},{key:"singleRegex",get:function(){return/^!(.*)$/}}]),n}(z),B=function(e){a(n,e);var t=f(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=e.startsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,this.pattern.length-1]}}}],[{key:"type",get:function(){return"prefix-exact"}},{key:"multiRegex",get:function(){return/^\^"(.*)"$/}},{key:"singleRegex",get:function(){return/^\^(.*)$/}}]),n}(z),J=function(e){a(n,e);var t=f(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=!e.startsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}}],[{key:"type",get:function(){return"inverse-prefix-exact"}},{key:"multiRegex",get:function(){return/^!\^"(.*)"$/}},{key:"singleRegex",get:function(){return/^!\^(.*)$/}}]),n}(z),U=function(e){a(n,e);var t=f(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=e.endsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[e.length-this.pattern.length,e.length-1]}}}],[{key:"type",get:function(){return"suffix-exact"}},{key:"multiRegex",get:function(){return/^"(.*)"\$$/}},{key:"singleRegex",get:function(){return/^(.*)\$$/}}]),n}(z),V=function(e){a(n,e);var t=f(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=!e.endsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}}],[{key:"type",get:function(){return"inverse-suffix-exact"}},{key:"multiRegex",get:function(){return/^!"(.*)"\$$/}},{key:"singleRegex",get:function(){return/^!(.*)\$$/}}]),n}(z),G=function(e){a(n,e);var t=f(n);function n(e){var i,o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},c=o.location,a=void 0===c?I.location:c,s=o.threshold,u=void 0===s?I.threshold:s,h=o.distance,f=void 0===h?I.distance:h,l=o.includeMatches,d=void 0===l?I.includeMatches:l,v=o.findAllMatches,g=void 0===v?I.findAllMatches:v,y=o.minMatchCharLength,p=void 0===y?I.minMatchCharLength:y,m=o.isCaseSensitive,k=void 0===m?I.isCaseSensitive:m,M=o.ignoreLocation,b=void 0===M?I.ignoreLocation:M;return r(this,n),(i=t.call(this,e))._bitapSearch=new T(e,{location:a,threshold:u,distance:f,includeMatches:d,findAllMatches:g,minMatchCharLength:p,isCaseSensitive:k,ignoreLocation:b}),i}return o(n,[{key:"search",value:function(e){return this._bitapSearch.searchIn(e)}}],[{key:"type",get:function(){return"fuzzy"}},{key:"multiRegex",get:function(){return/^"(.*)"$/}},{key:"singleRegex",get:function(){return/^(.*)$/}}]),n}(z),H=function(e){a(n,e);var t=f(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){for(var t,n=0,r=[],i=this.pattern.length;(t=e.indexOf(this.pattern,n))>-1;)n=t+i,r.push([t,n-1]);var o=!!r.length;return{isMatch:o,score:o?0:1,indices:r}}}],[{key:"type",get:function(){return"include"}},{key:"multiRegex",get:function(){return/^'"(.*)"$/}},{key:"singleRegex",get:function(){return/^'(.*)$/}}]),n}(z),Q=[K,H,B,J,V,U,q,G],X=Q.length,Y=/ +(?=([^\"]*\"[^\"]*\")*[^\"]*$)/;function Z(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e.split("|").map((function(e){for(var n=e.trim().split(Y).filter((function(e){return e&&!!e.trim()})),r=[],i=0,o=n.length;i1&&void 0!==arguments[1]?arguments[1]:{},i=n.isCaseSensitive,o=void 0===i?I.isCaseSensitive:i,c=n.includeMatches,a=void 0===c?I.includeMatches:c,s=n.minMatchCharLength,u=void 0===s?I.minMatchCharLength:s,h=n.ignoreLocation,f=void 0===h?I.ignoreLocation:h,l=n.findAllMatches,d=void 0===l?I.findAllMatches:l,v=n.location,g=void 0===v?I.location:v,y=n.threshold,p=void 0===y?I.threshold:y,m=n.distance,k=void 0===m?I.distance:m;r(this,e),this.query=null,this.options={isCaseSensitive:o,includeMatches:a,minMatchCharLength:u,findAllMatches:d,ignoreLocation:f,location:g,threshold:p,distance:k},this.pattern=o?t:t.toLowerCase(),this.query=Z(this.pattern,this.options)}return o(e,[{key:"searchIn",value:function(e){var t=this.query;if(!t)return{isMatch:!1,score:1};var n=this.options,r=n.includeMatches;e=n.isCaseSensitive?e:e.toLowerCase();for(var i=0,o=[],c=0,a=0,s=t.length;a-1&&(n.refIndex=e.idx),t.matches.push(n)}}))}function ve(e,t){t.score=e.score}function ge(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=n.includeMatches,i=void 0===r?I.includeMatches:r,o=n.includeScore,c=void 0===o?I.includeScore:o,a=[];return i&&a.push(de),c&&a.push(ve),e.map((function(e){var n=e.idx,r={item:t[n],refIndex:n};return a.length&&a.forEach((function(t){t(e,r)})),r}))}var ye=function(){function e(n){var i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},o=arguments.length>2?arguments[2]:void 0;r(this,e),this.options=t(t({},I),i),this.options.useExtendedSearch,this._keyStore=new S(this.options.keys),this.setCollection(n,o)}return o(e,[{key:"setCollection",value:function(e,t){if(this._docs=e,t&&!(t instanceof $))throw new Error("Incorrect 'index' type");this._myIndex=t||R(this.options.keys,this._docs,{getFn:this.options.getFn,fieldNormWeight:this.options.fieldNormWeight})}},{key:"add",value:function(e){k(e)&&(this._docs.push(e),this._myIndex.add(e))}},{key:"remove",value:function(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:function(){return!1},t=[],n=0,r=this._docs.length;n1&&void 0!==arguments[1]?arguments[1]:{},n=t.limit,r=void 0===n?-1:n,i=this.options,o=i.includeMatches,c=i.includeScore,a=i.shouldSort,s=i.sortFn,u=i.ignoreFieldNorm,h=g(e)?g(this._docs[0])?this._searchStringList(e):this._searchObjectList(e):this._searchLogical(e);return le(h,{ignoreFieldNorm:u}),a&&h.sort(s),y(r)&&r>-1&&(h=h.slice(0,r)),ge(h,this._docs,{includeMatches:o,includeScore:c})}},{key:"_searchStringList",value:function(e){var t=re(e,this.options),n=this._myIndex.records,r=[];return n.forEach((function(e){var n=e.v,i=e.i,o=e.n;if(k(n)){var c=t.searchIn(n),a=c.isMatch,s=c.score,u=c.indices;a&&r.push({item:n,idx:i,matches:[{score:s,value:n,norm:o,indices:u}]})}})),r}},{key:"_searchLogical",value:function(e){var t=this,n=function(e,t){var n=(arguments.length>2&&void 0!==arguments[2]?arguments[2]:{}).auto,r=void 0===n||n,i=function e(n){var i=Object.keys(n),o=ue(n);if(!o&&i.length>1&&!se(n))return e(fe(n));if(he(n)){var c=o?n[ce]:i[0],a=o?n[ae]:n[c];if(!g(a))throw new Error(x(c));var s={keyId:j(c),pattern:a};return r&&(s.searcher=re(a,t)),s}var u={children:[],operator:i[0]};return i.forEach((function(t){var r=n[t];v(r)&&r.forEach((function(t){u.children.push(e(t))}))})),u};return se(e)||(e=fe(e)),i(e)}(e,this.options),r=function e(n,r,i){if(!n.children){var o=n.keyId,c=n.searcher,a=t._findMatches({key:t._keyStore.get(o),value:t._myIndex.getValueForItemAtKeyId(r,o),searcher:c});return a&&a.length?[{idx:i,item:r,matches:a}]:[]}for(var s=[],u=0,h=n.children.length;u1&&void 0!==arguments[1]?arguments[1]:{},n=t.getFn,r=void 0===n?I.getFn:n,i=t.fieldNormWeight,o=void 0===i?I.fieldNormWeight:i,c=e.keys,a=e.records,s=new $({getFn:r,fieldNormWeight:o});return s.setKeys(c),s.setIndexRecords(a),s},ye.config=I,function(){ne.push.apply(ne,arguments)}(te),ye},"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).Fuse=t(); \ No newline at end of file diff --git a/_proc/_docs/site_libs/quarto-search/quarto-search.js b/_proc/_docs/site_libs/quarto-search/quarto-search.js deleted file mode 100644 index 6fd4b5b..0000000 --- a/_proc/_docs/site_libs/quarto-search/quarto-search.js +++ /dev/null @@ -1,1123 +0,0 @@ -const kQueryArg = "q"; -const kResultsArg = "show-results"; - -// If items don't provide a URL, then both the navigator and the onSelect -// function aren't called (and therefore, the default implementation is used) -// -// We're using this sentinel URL to signal to those handlers that this -// item is a more item (along with the type) and can be handled appropriately -const kItemTypeMoreHref = "0767FDFD-0422-4E5A-BC8A-3BE11E5BBA05"; - -window.document.addEventListener("DOMContentLoaded", function (_event) { - // Ensure that search is available on this page. If it isn't, - // should return early and not do anything - var searchEl = window.document.getElementById("quarto-search"); - if (!searchEl) return; - - const { autocomplete } = window["@algolia/autocomplete-js"]; - - let quartoSearchOptions = {}; - let language = {}; - const searchOptionEl = window.document.getElementById( - "quarto-search-options" - ); - if (searchOptionEl) { - const jsonStr = searchOptionEl.textContent; - quartoSearchOptions = JSON.parse(jsonStr); - language = quartoSearchOptions.language; - } - - // note the search mode - if (quartoSearchOptions.type === "overlay") { - searchEl.classList.add("type-overlay"); - } else { - searchEl.classList.add("type-textbox"); - } - - // Used to determine highlighting behavior for this page - // A `q` query param is expected when the user follows a search - // to this page - const currentUrl = new URL(window.location); - const query = currentUrl.searchParams.get(kQueryArg); - const showSearchResults = currentUrl.searchParams.get(kResultsArg); - const mainEl = window.document.querySelector("main"); - - // highlight matches on the page - if (query !== null && mainEl) { - // perform any highlighting - highlight(query, mainEl); - - // fix up the URL to remove the q query param - const replacementUrl = new URL(window.location); - replacementUrl.searchParams.delete(kQueryArg); - window.history.replaceState({}, "", replacementUrl); - } - - // function to clear highlighting on the page when the search query changes - // (e.g. if the user edits the query or clears it) - let highlighting = true; - const resetHighlighting = (searchTerm) => { - if (mainEl && highlighting && query !== null && searchTerm !== query) { - clearHighlight(query, mainEl); - highlighting = false; - } - }; - - // Clear search highlighting when the user scrolls sufficiently - const resetFn = () => { - resetHighlighting(""); - window.removeEventListener("quarto-hrChanged", resetFn); - window.removeEventListener("quarto-sectionChanged", resetFn); - }; - - // Register this event after the initial scrolling and settling of events - // on the page - window.addEventListener("quarto-hrChanged", resetFn); - window.addEventListener("quarto-sectionChanged", resetFn); - - // Responsively switch to overlay mode if the search is present on the navbar - // Note that switching the sidebar to overlay mode requires more coordinate (not just - // the media query since we generate different HTML for sidebar overlays than we do - // for sidebar input UI) - const detachedMediaQuery = - quartoSearchOptions.type === "overlay" - ? "all" - : quartoSearchOptions.location === "navbar" - ? "(max-width: 991px)" - : "none"; - - // If configured, include the analytics client to send insights - const plugins = configurePlugins(quartoSearchOptions); - - let lastState = null; - const { setIsOpen } = autocomplete({ - container: searchEl, - detachedMediaQuery: detachedMediaQuery, - defaultActiveItemId: 0, - panelContainer: "#quarto-search-results", - panelPlacement: quartoSearchOptions["panel-placement"], - debug: false, - plugins, - classNames: { - form: "d-flex", - }, - translations: { - clearButtonTitle: language["search-clear-button-title"], - detachedCancelButtonText: language["search-detached-cancel-button-title"], - submitButtonTitle: language["search-submit-button-title"], - }, - initialState: { - query, - }, - getItemUrl({ item }) { - return item.href; - }, - onStateChange({ state }) { - // Perhaps reset highlighting - resetHighlighting(state.query); - - // If the panel just opened, ensure the panel is positioned properly - if (state.isOpen) { - if (lastState && !lastState.isOpen) { - setTimeout(() => { - positionPanel(quartoSearchOptions["panel-placement"]); - }, 150); - } - } - - // Perhaps show the copy link - showCopyLink(state.query, quartoSearchOptions); - - lastState = state; - }, - reshape({ sources, state }) { - return sources.map((source) => { - try { - const items = source.getItems(); - - // Validate the items - validateItems(items); - - // group the items by document - const groupedItems = new Map(); - items.forEach((item) => { - const hrefParts = item.href.split("#"); - const baseHref = hrefParts[0]; - const isDocumentItem = hrefParts.length === 1; - - const items = groupedItems.get(baseHref); - if (!items) { - groupedItems.set(baseHref, [item]); - } else { - // If the href for this item matches the document - // exactly, place this item first as it is the item that represents - // the document itself - if (isDocumentItem) { - items.unshift(item); - } else { - items.push(item); - } - groupedItems.set(baseHref, items); - } - }); - - const reshapedItems = []; - let count = 1; - for (const [_key, value] of groupedItems) { - const firstItem = value[0]; - reshapedItems.push({ - ...firstItem, - type: kItemTypeDoc, - }); - - const collapseMatches = quartoSearchOptions["collapse-after"]; - const collapseCount = - typeof collapseMatches === "number" ? collapseMatches : 1; - - if (value.length > 1) { - const target = `search-more-${count}`; - const isExpanded = - state.context.expanded && - state.context.expanded.includes(target); - - const remainingCount = value.length - collapseCount; - - for (let i = 1; i < value.length; i++) { - if (collapseMatches && i === collapseCount) { - reshapedItems.push({ - target, - title: isExpanded - ? language["search-hide-matches-text"] - : remainingCount === 1 - ? `${remainingCount} ${language["search-more-match-text"]}` - : `${remainingCount} ${language["search-more-matches-text"]}`, - type: kItemTypeMore, - href: kItemTypeMoreHref, - }); - } - - if (isExpanded || !collapseMatches || i < collapseCount) { - reshapedItems.push({ - ...value[i], - type: kItemTypeItem, - target, - }); - } - } - } - count += 1; - } - - return { - ...source, - getItems() { - return reshapedItems; - }, - }; - } catch (error) { - // Some form of error occurred - return { - ...source, - getItems() { - return [ - { - title: error.name || "An Error Occurred While Searching", - text: - error.message || - "An unknown error occurred while attempting to perform the requested search.", - type: kItemTypeError, - }, - ]; - }, - }; - } - }); - }, - navigator: { - navigate({ itemUrl }) { - if (itemUrl !== offsetURL(kItemTypeMoreHref)) { - window.location.assign(itemUrl); - } - }, - navigateNewTab({ itemUrl }) { - if (itemUrl !== offsetURL(kItemTypeMoreHref)) { - const windowReference = window.open(itemUrl, "_blank", "noopener"); - if (windowReference) { - windowReference.focus(); - } - } - }, - navigateNewWindow({ itemUrl }) { - if (itemUrl !== offsetURL(kItemTypeMoreHref)) { - window.open(itemUrl, "_blank", "noopener"); - } - }, - }, - getSources({ state, setContext, setActiveItemId, refresh }) { - return [ - { - sourceId: "documents", - getItemUrl({ item }) { - if (item.href) { - return offsetURL(item.href); - } else { - return undefined; - } - }, - onSelect({ - item, - state, - setContext, - setIsOpen, - setActiveItemId, - refresh, - }) { - if (item.type === kItemTypeMore) { - toggleExpanded(item, state, setContext, setActiveItemId, refresh); - - // Toggle more - setIsOpen(true); - } - }, - getItems({ query }) { - const limit = quartoSearchOptions.limit; - if (quartoSearchOptions.algolia) { - return algoliaSearch(query, limit, quartoSearchOptions.algolia); - } else { - // Fuse search options - const fuseSearchOptions = { - isCaseSensitive: false, - shouldSort: true, - minMatchCharLength: 2, - limit: limit, - }; - - return readSearchData().then(function (fuse) { - return fuseSearch(query, fuse, fuseSearchOptions); - }); - } - }, - templates: { - noResults({ createElement }) { - return createElement( - "div", - { class: "quarto-search-no-results" }, - language["search-no-results-text"] - ); - }, - header({ items, createElement }) { - // count the documents - const count = items.filter((item) => { - return item.type === kItemTypeDoc; - }).length; - - if (count > 0) { - return createElement( - "div", - { class: "search-result-header" }, - `${count} ${language["search-matching-documents-text"]}` - ); - } else { - return createElement( - "div", - { class: "search-result-header-no-results" }, - `` - ); - } - }, - footer({ _items, createElement }) { - if ( - quartoSearchOptions.algolia && - quartoSearchOptions.algolia["show-logo"] - ) { - const libDir = quartoSearchOptions.algolia["libDir"]; - const logo = createElement("img", { - src: offsetURL( - `${libDir}/quarto-search/search-by-algolia.svg` - ), - class: "algolia-search-logo", - }); - return createElement( - "a", - { href: "http://www.algolia.com/" }, - logo - ); - } - }, - - item({ item, createElement }) { - return renderItem( - item, - createElement, - state, - setActiveItemId, - setContext, - refresh - ); - }, - }, - }, - ]; - }, - }); - - // Remove the labeleledby attribute since it is pointing - // to a non-existent label - if (quartoSearchOptions.type === "overlay") { - const inputEl = window.document.querySelector( - "#quarto-search .aa-Autocomplete" - ); - if (inputEl) { - inputEl.removeAttribute("aria-labelledby"); - } - } - - // If the main document scrolls dismiss the search results - // (otherwise, since they're floating in the document they can scroll with the document) - window.document.body.onscroll = () => { - setIsOpen(false); - }; - - if (showSearchResults) { - setIsOpen(true); - focusSearchInput(); - } -}); - -function configurePlugins(quartoSearchOptions) { - const autocompletePlugins = []; - const algoliaOptions = quartoSearchOptions.algolia; - if ( - algoliaOptions && - algoliaOptions["analytics-events"] && - algoliaOptions["search-only-api-key"] && - algoliaOptions["application-id"] - ) { - const apiKey = algoliaOptions["search-only-api-key"]; - const appId = algoliaOptions["application-id"]; - - // Aloglia insights may not be loaded because they require cookie consent - // Use deferred loading so events will start being recorded when/if consent - // is granted. - const algoliaInsightsDeferredPlugin = deferredLoadPlugin(() => { - if ( - window.aa && - window["@algolia/autocomplete-plugin-algolia-insights"] - ) { - window.aa("init", { - appId, - apiKey, - useCookie: true, - }); - - const { createAlgoliaInsightsPlugin } = - window["@algolia/autocomplete-plugin-algolia-insights"]; - // Register the insights client - const algoliaInsightsPlugin = createAlgoliaInsightsPlugin({ - insightsClient: window.aa, - onItemsChange({ insights, insightsEvents }) { - const events = insightsEvents.map((event) => { - const maxEvents = event.objectIDs.slice(0, 20); - return { - ...event, - objectIDs: maxEvents, - }; - }); - - insights.viewedObjectIDs(...events); - }, - }); - return algoliaInsightsPlugin; - } - }); - - // Add the plugin - autocompletePlugins.push(algoliaInsightsDeferredPlugin); - return autocompletePlugins; - } -} - -// For plugins that may not load immediately, create a wrapper -// plugin and forward events and plugin data once the plugin -// is initialized. This is useful for cases like cookie consent -// which may prevent the analytics insights event plugin from initializing -// immediately. -function deferredLoadPlugin(createPlugin) { - let plugin = undefined; - let subscribeObj = undefined; - const wrappedPlugin = () => { - if (!plugin && subscribeObj) { - plugin = createPlugin(); - if (plugin && plugin.subscribe) { - plugin.subscribe(subscribeObj); - } - } - return plugin; - }; - - return { - subscribe: (obj) => { - subscribeObj = obj; - }, - onStateChange: (obj) => { - const plugin = wrappedPlugin(); - if (plugin && plugin.onStateChange) { - plugin.onStateChange(obj); - } - }, - onSubmit: (obj) => { - const plugin = wrappedPlugin(); - if (plugin && plugin.onSubmit) { - plugin.onSubmit(obj); - } - }, - onReset: (obj) => { - const plugin = wrappedPlugin(); - if (plugin && plugin.onReset) { - plugin.onReset(obj); - } - }, - getSources: (obj) => { - const plugin = wrappedPlugin(); - if (plugin && plugin.getSources) { - return plugin.getSources(obj); - } else { - return Promise.resolve([]); - } - }, - data: (obj) => { - const plugin = wrappedPlugin(); - if (plugin && plugin.data) { - plugin.data(obj); - } - }, - }; -} - -function validateItems(items) { - // Validate the first item - if (items.length > 0) { - const item = items[0]; - const missingFields = []; - if (item.href == undefined) { - missingFields.push("href"); - } - if (!item.title == undefined) { - missingFields.push("title"); - } - if (!item.text == undefined) { - missingFields.push("text"); - } - - if (missingFields.length === 1) { - throw { - name: `Error: Search index is missing the ${missingFields[0]} field.`, - message: `The items being returned for this search do not include all the required fields. Please ensure that your index items include the ${missingFields[0]} field or use index-fields in your _quarto.yml file to specify the field names.`, - }; - } else if (missingFields.length > 1) { - const missingFieldList = missingFields - .map((field) => { - return `${field}`; - }) - .join(", "); - - throw { - name: `Error: Search index is missing the following fields: ${missingFieldList}.`, - message: `The items being returned for this search do not include all the required fields. Please ensure that your index items includes the following fields: ${missingFieldList}, or use index-fields in your _quarto.yml file to specify the field names.`, - }; - } - } -} - -let lastQuery = null; -function showCopyLink(query, options) { - const language = options.language; - lastQuery = query; - // Insert share icon - const inputSuffixEl = window.document.body.querySelector( - ".aa-Form .aa-InputWrapperSuffix" - ); - - if (inputSuffixEl) { - let copyButtonEl = window.document.body.querySelector( - ".aa-Form .aa-InputWrapperSuffix .aa-CopyButton" - ); - - if (copyButtonEl === null) { - copyButtonEl = window.document.createElement("button"); - copyButtonEl.setAttribute("class", "aa-CopyButton"); - copyButtonEl.setAttribute("type", "button"); - copyButtonEl.setAttribute("title", language["search-copy-link-title"]); - copyButtonEl.onmousedown = (e) => { - e.preventDefault(); - e.stopPropagation(); - }; - - const linkIcon = "bi-clipboard"; - const checkIcon = "bi-check2"; - - const shareIconEl = window.document.createElement("i"); - shareIconEl.setAttribute("class", `bi ${linkIcon}`); - copyButtonEl.appendChild(shareIconEl); - inputSuffixEl.prepend(copyButtonEl); - - const clipboard = new window.ClipboardJS(".aa-CopyButton", { - text: function (_trigger) { - const copyUrl = new URL(window.location); - copyUrl.searchParams.set(kQueryArg, lastQuery); - copyUrl.searchParams.set(kResultsArg, "1"); - return copyUrl.toString(); - }, - }); - clipboard.on("success", function (e) { - // Focus the input - - // button target - const button = e.trigger; - const icon = button.querySelector("i.bi"); - - // flash "checked" - icon.classList.add(checkIcon); - icon.classList.remove(linkIcon); - setTimeout(function () { - icon.classList.remove(checkIcon); - icon.classList.add(linkIcon); - }, 1000); - }); - } - - // If there is a query, show the link icon - if (copyButtonEl) { - if (lastQuery && options["copy-button"]) { - copyButtonEl.style.display = "flex"; - } else { - copyButtonEl.style.display = "none"; - } - } - } -} - -/* Search Index Handling */ -// create the index -var fuseIndex = undefined; -async function readSearchData() { - // Initialize the search index on demand - if (fuseIndex === undefined) { - // create fuse index - const options = { - keys: [ - { name: "title", weight: 20 }, - { name: "section", weight: 20 }, - { name: "text", weight: 10 }, - ], - ignoreLocation: true, - threshold: 0.1, - }; - const fuse = new window.Fuse([], options); - - // fetch the main search.json - const response = await fetch(offsetURL("search.json")); - if (response.status == 200) { - return response.json().then(function (searchDocs) { - searchDocs.forEach(function (searchDoc) { - fuse.add(searchDoc); - }); - fuseIndex = fuse; - return fuseIndex; - }); - } else { - return Promise.reject( - new Error( - "Unexpected status from search index request: " + response.status - ) - ); - } - } - return fuseIndex; -} - -function inputElement() { - return window.document.body.querySelector(".aa-Form .aa-Input"); -} - -function focusSearchInput() { - setTimeout(() => { - const inputEl = inputElement(); - if (inputEl) { - inputEl.focus(); - } - }, 50); -} - -/* Panels */ -const kItemTypeDoc = "document"; -const kItemTypeMore = "document-more"; -const kItemTypeItem = "document-item"; -const kItemTypeError = "error"; - -function renderItem( - item, - createElement, - state, - setActiveItemId, - setContext, - refresh -) { - switch (item.type) { - case kItemTypeDoc: - return createDocumentCard( - createElement, - "file-richtext", - item.title, - item.section, - item.text, - item.href - ); - case kItemTypeMore: - return createMoreCard( - createElement, - item, - state, - setActiveItemId, - setContext, - refresh - ); - case kItemTypeItem: - return createSectionCard( - createElement, - item.section, - item.text, - item.href - ); - case kItemTypeError: - return createErrorCard(createElement, item.title, item.text); - default: - return undefined; - } -} - -function createDocumentCard(createElement, icon, title, section, text, href) { - const iconEl = createElement("i", { - class: `bi bi-${icon} search-result-icon`, - }); - const titleEl = createElement("p", { class: "search-result-title" }, title); - const titleContainerEl = createElement( - "div", - { class: "search-result-title-container" }, - [iconEl, titleEl] - ); - - const textEls = []; - if (section) { - const sectionEl = createElement( - "p", - { class: "search-result-section" }, - section - ); - textEls.push(sectionEl); - } - const descEl = createElement("p", { - class: "search-result-text", - dangerouslySetInnerHTML: { - __html: text, - }, - }); - textEls.push(descEl); - - const textContainerEl = createElement( - "div", - { class: "search-result-text-container" }, - textEls - ); - - const containerEl = createElement( - "div", - { - class: "search-result-container", - }, - [titleContainerEl, textContainerEl] - ); - - const linkEl = createElement( - "a", - { - href: offsetURL(href), - class: "search-result-link", - }, - containerEl - ); - - const classes = ["search-result-doc", "search-item"]; - if (!section) { - classes.push("document-selectable"); - } - - return createElement( - "div", - { - class: classes.join(" "), - }, - linkEl - ); -} - -function createMoreCard( - createElement, - item, - state, - setActiveItemId, - setContext, - refresh -) { - const moreCardEl = createElement( - "div", - { - class: "search-result-more search-item", - onClick: (e) => { - // Handle expanding the sections by adding the expanded - // section to the list of expanded sections - toggleExpanded(item, state, setContext, setActiveItemId, refresh); - e.stopPropagation(); - }, - }, - item.title - ); - - return moreCardEl; -} - -function toggleExpanded(item, state, setContext, setActiveItemId, refresh) { - const expanded = state.context.expanded || []; - if (expanded.includes(item.target)) { - setContext({ - expanded: expanded.filter((target) => target !== item.target), - }); - } else { - setContext({ expanded: [...expanded, item.target] }); - } - - refresh(); - setActiveItemId(item.__autocomplete_id); -} - -function createSectionCard(createElement, section, text, href) { - const sectionEl = createSection(createElement, section, text, href); - return createElement( - "div", - { - class: "search-result-doc-section search-item", - }, - sectionEl - ); -} - -function createSection(createElement, title, text, href) { - const descEl = createElement("p", { - class: "search-result-text", - dangerouslySetInnerHTML: { - __html: text, - }, - }); - - const titleEl = createElement("p", { class: "search-result-section" }, title); - const linkEl = createElement( - "a", - { - href: offsetURL(href), - class: "search-result-link", - }, - [titleEl, descEl] - ); - return linkEl; -} - -function createErrorCard(createElement, title, text) { - const descEl = createElement("p", { - class: "search-error-text", - dangerouslySetInnerHTML: { - __html: text, - }, - }); - - const titleEl = createElement("p", { - class: "search-error-title", - dangerouslySetInnerHTML: { - __html: ` ${title}`, - }, - }); - const errorEl = createElement("div", { class: "search-error" }, [ - titleEl, - descEl, - ]); - return errorEl; -} - -function positionPanel(pos) { - const panelEl = window.document.querySelector( - "#quarto-search-results .aa-Panel" - ); - const inputEl = window.document.querySelector( - "#quarto-search .aa-Autocomplete" - ); - - if (panelEl && inputEl) { - panelEl.style.top = `${Math.round(panelEl.offsetTop)}px`; - if (pos === "start") { - panelEl.style.left = `${Math.round(inputEl.left)}px`; - } else { - panelEl.style.right = `${Math.round(inputEl.offsetRight)}px`; - } - } -} - -/* Highlighting */ -// highlighting functions -function highlightMatch(query, text) { - if (text) { - const start = text.toLowerCase().indexOf(query.toLowerCase()); - if (start !== -1) { - const startMark = ""; - const endMark = ""; - - const end = start + query.length; - text = - text.slice(0, start) + - startMark + - text.slice(start, end) + - endMark + - text.slice(end); - const startInfo = clipStart(text, start); - const endInfo = clipEnd( - text, - startInfo.position + startMark.length + endMark.length - ); - text = - startInfo.prefix + - text.slice(startInfo.position, endInfo.position) + - endInfo.suffix; - - return text; - } else { - return text; - } - } else { - return text; - } -} - -function clipStart(text, pos) { - const clipStart = pos - 50; - if (clipStart < 0) { - // This will just return the start of the string - return { - position: 0, - prefix: "", - }; - } else { - // We're clipping before the start of the string, walk backwards to the first space. - const spacePos = findSpace(text, pos, -1); - return { - position: spacePos.position, - prefix: "", - }; - } -} - -function clipEnd(text, pos) { - const clipEnd = pos + 200; - if (clipEnd > text.length) { - return { - position: text.length, - suffix: "", - }; - } else { - const spacePos = findSpace(text, clipEnd, 1); - return { - position: spacePos.position, - suffix: spacePos.clipped ? "…" : "", - }; - } -} - -function findSpace(text, start, step) { - let stepPos = start; - while (stepPos > -1 && stepPos < text.length) { - const char = text[stepPos]; - if (char === " " || char === "," || char === ":") { - return { - position: step === 1 ? stepPos : stepPos - step, - clipped: stepPos > 1 && stepPos < text.length, - }; - } - stepPos = stepPos + step; - } - - return { - position: stepPos - step, - clipped: false, - }; -} - -// removes highlighting as implemented by the mark tag -function clearHighlight(searchterm, el) { - const childNodes = el.childNodes; - for (let i = childNodes.length - 1; i >= 0; i--) { - const node = childNodes[i]; - if (node.nodeType === Node.ELEMENT_NODE) { - if ( - node.tagName === "MARK" && - node.innerText.toLowerCase() === searchterm.toLowerCase() - ) { - el.replaceChild(document.createTextNode(node.innerText), node); - } else { - clearHighlight(searchterm, node); - } - } - } -} - -// highlight matches -function highlight(term, el) { - const termRegex = new RegExp(term, "ig"); - const childNodes = el.childNodes; - - // walk back to front avoid mutating elements in front of us - for (let i = childNodes.length - 1; i >= 0; i--) { - const node = childNodes[i]; - - if (node.nodeType === Node.TEXT_NODE) { - // Search text nodes for text to highlight - const text = node.nodeValue; - - let startIndex = 0; - let matchIndex = text.search(termRegex); - if (matchIndex > -1) { - const markFragment = document.createDocumentFragment(); - while (matchIndex > -1) { - const prefix = text.slice(startIndex, matchIndex); - markFragment.appendChild(document.createTextNode(prefix)); - - const mark = document.createElement("mark"); - mark.appendChild( - document.createTextNode( - text.slice(matchIndex, matchIndex + term.length) - ) - ); - markFragment.appendChild(mark); - - startIndex = matchIndex + term.length; - matchIndex = text.slice(startIndex).search(new RegExp(term, "ig")); - if (matchIndex > -1) { - matchIndex = startIndex + matchIndex; - } - } - if (startIndex < text.length) { - markFragment.appendChild( - document.createTextNode(text.slice(startIndex, text.length)) - ); - } - - el.replaceChild(markFragment, node); - } - } else if (node.nodeType === Node.ELEMENT_NODE) { - // recurse through elements - highlight(term, node); - } - } -} - -/* Link Handling */ -// get the offset from this page for a given site root relative url -function offsetURL(url) { - var offset = getMeta("quarto:offset"); - return offset ? offset + url : url; -} - -// read a meta tag value -function getMeta(metaName) { - var metas = window.document.getElementsByTagName("meta"); - for (let i = 0; i < metas.length; i++) { - if (metas[i].getAttribute("name") === metaName) { - return metas[i].getAttribute("content"); - } - } - return ""; -} - -function algoliaSearch(query, limit, algoliaOptions) { - const { getAlgoliaResults } = window["@algolia/autocomplete-preset-algolia"]; - - const applicationId = algoliaOptions["application-id"]; - const searchOnlyApiKey = algoliaOptions["search-only-api-key"]; - const indexName = algoliaOptions["index-name"]; - const indexFields = algoliaOptions["index-fields"]; - const searchClient = window.algoliasearch(applicationId, searchOnlyApiKey); - const searchParams = algoliaOptions["params"]; - const searchAnalytics = !!algoliaOptions["analytics-events"]; - - return getAlgoliaResults({ - searchClient, - queries: [ - { - indexName: indexName, - query, - params: { - hitsPerPage: limit, - clickAnalytics: searchAnalytics, - ...searchParams, - }, - }, - ], - transformResponse: (response) => { - if (!indexFields) { - return response.hits.map((hit) => { - return hit.map((item) => { - return { - ...item, - text: highlightMatch(query, item.text), - }; - }); - }); - } else { - const remappedHits = response.hits.map((hit) => { - return hit.map((item) => { - const newItem = { ...item }; - ["href", "section", "title", "text"].forEach((keyName) => { - const mappedName = indexFields[keyName]; - if ( - mappedName && - item[mappedName] !== undefined && - mappedName !== keyName - ) { - newItem[keyName] = item[mappedName]; - delete newItem[mappedName]; - } - }); - newItem.text = highlightMatch(query, newItem.text); - return newItem; - }); - }); - return remappedHits; - } - }, - }); -} - -function fuseSearch(query, fuse, fuseOptions) { - return fuse.search(query, fuseOptions).map((result) => { - const addParam = (url, name, value) => { - const anchorParts = url.split("#"); - const baseUrl = anchorParts[0]; - const sep = baseUrl.search("\\?") > 0 ? "&" : "?"; - anchorParts[0] = baseUrl + sep + name + "=" + value; - return anchorParts.join("#"); - }; - - return { - title: result.item.title, - section: result.item.section, - href: addParam(result.item.href, kQueryArg, query), - text: highlightMatch(query, result.item.text), - }; - }); -} diff --git a/_proc/_docs/sitemap.xml b/_proc/_docs/sitemap.xml deleted file mode 100644 index 1edfb1a..0000000 --- a/_proc/_docs/sitemap.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - https://FleischerResearchLab.github.io/CanvasGroupy/api/github.html - 2023-05-18T04:34:33.724Z - - - https://FleischerResearchLab.github.io/CanvasGroupy/api/canvas.html - 2023-05-18T04:34:34.083Z - - - https://FleischerResearchLab.github.io/CanvasGroupy/api/index.html - 2023-05-18T04:34:34.412Z - - - https://FleischerResearchLab.github.io/CanvasGroupy/api/project_grading.html - 2023-05-18T04:37:39.711Z - - - https://FleischerResearchLab.github.io/CanvasGroupy/api/groupeng_assign.html - 2023-05-18T04:39:08.471Z - - - https://FleischerResearchLab.github.io/CanvasGroupy/tutorials/create github group from canvas group.html - 2023-05-18T04:34:35.434Z - - - https://FleischerResearchLab.github.io/CanvasGroupy/tutorials/authentications.html - 2023-05-18T04:34:35.724Z - - - https://FleischerResearchLab.github.io/CanvasGroupy/tutorials/create template issues.html - 2023-05-18T04:47:11.033Z - - - https://FleischerResearchLab.github.io/CanvasGroupy/index.html - 2023-05-18T04:50:29.867Z - - - https://FleischerResearchLab.github.io/CanvasGroupy/feedback_template/checkpoint_feedback.html - 2023-05-18T04:34:36.830Z - - - https://FleischerResearchLab.github.io/CanvasGroupy/feedback_template/proposal_feedback.html - 2023-05-18T04:34:37.172Z - - - https://FleischerResearchLab.github.io/CanvasGroupy/feedback_template/final_project_feedback.html - 2023-05-18T04:34:37.491Z - - diff --git a/_proc/_docs/styles.css b/_proc/_docs/styles.css deleted file mode 100644 index 66ccc49..0000000 --- a/_proc/_docs/styles.css +++ /dev/null @@ -1,37 +0,0 @@ -.cell { - margin-bottom: 1rem; -} - -.cell > .sourceCode { - margin-bottom: 0; -} - -.cell-output > pre { - margin-bottom: 0; -} - -.cell-output > pre, .cell-output > .sourceCode > pre, .cell-output-stdout > pre { - margin-left: 0.8rem; - margin-top: 0; - background: none; - border-left: 2px solid lightsalmon; - border-top-left-radius: 0; - border-top-right-radius: 0; -} - -.cell-output > .sourceCode { - border: none; -} - -.cell-output > .sourceCode { - background: none; - margin-top: 0; -} - -div.description { - padding-left: 2px; - padding-top: 5px; - font-style: italic; - font-size: 135%; - opacity: 70%; -} diff --git a/_proc/_docs/tutorials/authentications.html b/_proc/_docs/tutorials/authentications.html deleted file mode 100644 index 59a128c..0000000 --- a/_proc/_docs/tutorials/authentications.html +++ /dev/null @@ -1,436 +0,0 @@ - - - - - - - - - - -CanvasGroupy - GitHub / Canvas Authentications - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    - - -
    - -
    - - - - - -
    - -
    -
    -

    GitHub / Canvas Authentications

    -
    - -
    -
    - In this example, we demonstrated the required credentials information needed to execute this program. -
    -
    - - -
    - - - - -
    - - -
    - - -

    To use this software, which essentially is a wrapper package under a API wrapper, you need to provide the program with the appropriate credentials file. We will use the provided credential to authenticate toward both Canvas and GitHub, in order to fetch and manipulate appropriate information.

    -

    Note: The program itself won’t either store nor stole your credentials information. This program will stay on your personal computer and will only use your credentials when needed. For more information about the implementation details, please refer to the GitHub repository to view the source code.

    -
    -
    from CanvasGroupy.canvas import CanvasGroup
    -from CanvasGroupy.github import GitHubGroup
    -
    -

    The credentials are stored in the following format. You can download a credential template at the following link.

    -
    -
    -
    {'GitHub Token': 'token', 'Canvas Token': 'token'}
    -
    -
    - - - -
    - -
    - - - - \ No newline at end of file diff --git a/_proc/_docs/tutorials/create github group from canvas group.html b/_proc/_docs/tutorials/create github group from canvas group.html deleted file mode 100644 index 685b42f..0000000 --- a/_proc/_docs/tutorials/create github group from canvas group.html +++ /dev/null @@ -1,1473 +0,0 @@ - - - - - - - - - - -CanvasGroupy - Example Workflow - Create GitHub Group from Canvas Group - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    - - -
    - -
    - - - - - -
    - -
    -
    -

    Example Workflow - Create GitHub Group from Canvas Group

    -
    - -
    -
    - In this example, we created GitHub group repositories to student groups based on the group information on canvas. -
    -
    - - -
    - - - - -
    - - -
    - - -

    This example workflow was used in Spring 2023, for COGS 118A at UC San Diego. his workflow demonstrated a component of the CanvasGroupy, where we already have group information based on the canvas group. In addition, students’ GitHub usernames were collected via a canvas quiz, where we fetched, validated, and stored the GitHub Username directly. This workflow was run after the fact that all students were successfully assigned a group, and all students have correctly completed their GitHub Username quiz.

    -

    As usual, to execute those API calls, you will have to provide the system with necessary credentials. You can find more information based on the ???TODO tutorial.

    -

    Note: The output of some cells are long, and the output might affect your reading experience. I recommend you to use the hyperlink on the right hand side bar to skip to the next section if needed.

    -
    -

    Canvas Get Groups / GitHub Username

    -

    We need to get the group member information at Canvas. This is achieved by pulling the people list on the group category page.

    -
    -
    from CanvasGroupy.canvas import CanvasGroup
    -from CanvasGroupy.github import GitHubGroup
    -
    -
    -
    cg = CanvasGroup("Group_Eng/credentials.json", course_id=45059)
    -
    -
    Authorization Successful!
    -Course Set:  COGS 118A - Supvr/Mach Learning Algorithms - Fleischer [SP23] 
    -Getting List of Users... This might take a while...
    -Users Fetch Complete! The course has 161 students.
    -
    -
    -
    -

    Fetch GitHub Username from Quiz

    -

    See more info at CanvasGroup.fetch_username_from_quiz

    -
    -
    github_usernames = cg.fetch_username_from_quiz(quiz_id=139061)
    -
    -
    Quiz: GitHub Username fetch! 
    -Generating Student Analaysis...
    -[====================] 100%
    -Report Generated!
    -The Question asked is 1389031: What is your GitHub Username? Absolutely No Typo Please.. 
    -Make sure this is the correct question where you asked student for their GitHub id.
    -If you need to change the index of columns, change the col_index argument of this call.
    -
    -
    -
    -
    -
    -

    Check GitHub Username Validity

    -

    We use GitHub API to search for a target user. See more info at CanvasGroup.check_github_usernames

    -
    -
    cg.check_github_usernames(github_usernames,
    -                          send_canvas_email=True,
    -                          send_undone_reminder=True,
    -                          quiz_url="https://canvas.ucsd.edu/courses/45059/quizzes/139061"
    -)
    -
    -
    Student ax008708 did not submit their github username.
    -Notification Sent!
    -Student a8chu did not submit their github username.
    -Notification Sent!
    -Student j3dong did not submit their github username.
    -Notification Sent!
    -Student n6garcia did not submit their github username.
    -Notification Sent!
    -Student kehu did not submit their github username.
    -Notification Sent!
    -Student qil016 did not submit their github username.
    -Notification Sent!
    -Student ttp007 did not submit their github username.
    -Notification Sent!
    -Student zshao did not submit their github username.
    -Notification Sent!
    -
    -
    -
    {}
    -
    -
    -
    -
    -

    Get Group Member Information

    -
    -
    groups = cg.get_groups("Final Project")
    -groups
    -
    -
    {'Group001-SP23': ['h5he', 'zmao', 'xiw013', 'j6wen', 'j5zhu'],
    - 'Group002-SP23': ['cmcmanig', 'jup006', 'ssuthar', 'd3yu'],
    - 'Group003-SP23': ['yic055', 'yuz191', 'xiz068'],
    - 'Group004-SP23': ['dac020', 'nilu', 'tyap', 'g6zhu'],
    - 'Group005-SP23': ['kechen', 'a8chu', 'cdelira', 'wolee', 'arshukla'],
    - 'Group006-SP23': ['ax008707', 'ax008724', 'ax008777'],
    - 'Group007-SP23': ['yuchi', 'j3dong', 'y3ge', 'x6he', 'xiz031'],
    - 'Group008-SP23': ['zifeng', 'ax008740', 'jul121', 'yuy047'],
    - 'Group009-SP23': ['ax008573', 'ckavanagh', 'v1lu', 'vvishnus'],
    - 'Group010-SP23': ['jwc002', 'tjamal', 'jsliang', 'tdn003'],
    - 'Group011-SP23': ['khchuang', 'emdavis', 'jejiang', 'nrejai'],
    - 'Group012-SP23': ['afleschn', 'rlharsono', 'jjsanchez', 'asengupt'],
    - 'Group013-SP23': ['kehu', 'jnhuang', 'shperry', 'alvalenc'],
    - 'Group014-SP23': ['gsroberts', 'cvillafa', 'shw089', 'yiz095'],
    - 'Group015-SP23': ['aanna', 'sdsilva', 'asivayog', 'nyanekch'],
    - 'Group016-SP23': ['yuche', 'y3guo', 'e1hu', 'zhl023', 'qil012'],
    - 'Group017-SP23': ['s3chowdhury', 'llennema', 'psodhi', 'svirk'],
    - 'Group018-SP23': ['ajcagle', 'ahewig', 's2malik', 'mnodini', 'musman'],
    - 'Group019-SP23': ['sasingh', 'nsit', 'dsun'],
    - 'Group020-SP23': ['akaji', 'm1manzan', 'j3mendez', 'mpareek', 'atrapena'],
    - 'Group021-SP23': ['jkrentse', 'jmlai', 'zel012', 'sserafin'],
    - 'Group022-SP23': ['ax008708', 'anc024', 'gng', 'reyang'],
    - 'Group023-SP23': ['raguinakang', 'phelcl', 'vjayanan', 'crochez', 'kpstern'],
    - 'Group024-SP23': ['nabansal', 'ccaban', 'mvfang', 'asim'],
    - 'Group025-SP23': ['nschaefe', 'hshaikh', 'jww001', 'bjyan'],
    - 'Group026-SP23': ['e1dong', 'aaolivas', 'h5park', 'yuz821'],
    - 'Group027-SP23': ['jddeleon', 'ndnguyen', 'ttp007', 'jzs002'],
    - 'Group028-SP23': ['mabdilah', 'yuh045', 'ktnakai', 'jonza'],
    - 'Group029-SP23': ['lmitbo', 'jsalce', 'lskerrett', 'jubamadu'],
    - 'Group030-SP23': ['ruc003', 'shh035', 'btn003', 'knino'],
    - 'Group031-SP23': ['shc007', 'n6garcia', 'ken010', 'mmpak'],
    - 'Group032-SP23': ['zachao', 'smurase', 'j1xu', 'z5zhang'],
    - 'Group033-SP23': ['sbodhisartha', 'ndeepak', 'arlu', 'trucker', 'jaxu'],
    - 'Group034-SP23': ['kabalaji', 'rchaklas', 'vspillai', 'jmvillal'],
    - 'Group035-SP23': ['cantoniohernandez', 'rpuranam', 'rsedano', 'zshao'],
    - 'Group036-SP23': ['malkhalifah', 'djjani', 'rkohli', 'smtrived'],
    - 'Group037-SP23': ['nazpeitia', 'g2hong', 'gkweon'],
    - 'Group038-SP23': ['qil016', 'msherrick', 'yiw085', 'r4zhou'],
    - 'Group039-SP23': ['cgutierrezgodoy', 'z8jiang', 'dar005', 'jtaolan']}
    -
    -
    -
    -
    -

    GitHub Repository Creation

    -

    Given the gathered information about both group membership and students’ GitHub Username, we are ready to create group repositories for them.

    -
    -
    ggroup = GitHubGroup("Group_Eng/credentials.json", verbosity=1)
    -ggroup.set_org("COGS118A")
    -
    -
    Successfully Authenticated. GitHub account:  scott-yj-yang 
    -Target Organization Set:  COGS118A 
    -
    -
    -

    In the following for loop, we create the group repositories via a series of GitHubGroup.create_group_repo command. This is the place where we can get personalized (or I shall say groupalized) repositories. Be sure to change the appropriate parameters.

    -
    -
    repos = []
    -for group_name, members in groups.items():
    -    group_git_usernames = []
    -    for email in members:
    -        try:
    -            # try to get the git username for each student.
    -            # not all students completed their quiz.
    -            group_git_usernames.append(github_usernames[email])
    -        except KeyError:
    -            print(f"{email}'s GitHub Username not found")
    -    repo = ggroup.create_group_repo(
    -        repo_name=group_name,
    -        collaborators=group_git_usernames,
    -        permission="write",
    -        repo_template="COGS118A/group_template",
    -        rename_files={
    -            "Checkpoint_groupXXX.ipynb": f"Checkpoint_{group_name}.ipynb",
    -            "FinalProject_groupXXX.ipynb": f"FinalProject_{group_name}.ipynb",
    -            "Proposal_groupXXX.ipynb": f"Proposal_{group_name}.ipynb"
    -        },
    -        private=False,
    -        description=f"COGS118A Final Project {group_name} Repository",
    -        team_slug="Instructors_Sp23",
    -        team_permission="admin"
    -    )
    -    print("")
    -    repos.append(repo)
    -
    -
    Repo  Group001-SP23  Created... Wait for 3 sec to updates
    -File Successfully Renamed from   Checkpoint_groupXXX.ipynb   to  Checkpoint_Group001-SP23.ipynb 
    -File Successfully Renamed from   FinalProject_groupXXX.ipynb   to  FinalProject_Group001-SP23.ipynb 
    -File Successfully Renamed from   Proposal_groupXXX.ipynb   to  Proposal_Group001-SP23.ipynb 
    -Added Collaborator:  TaraaHe  to:  Group001-SP23  with permission:  write 
    -Added Collaborator:  demimao  to:  Group001-SP23  with permission:  write 
    -Added Collaborator:  xiw013  to:  Group001-SP23  with permission:  write 
    -Added Collaborator:  willwen96  to:  Group001-SP23  with permission:  write 
    -Added Collaborator:  Ju-dyz  to:  Group001-SP23  with permission:  write 
    -Team  Instructors_Sp23  added to  Group001-SP23  with permission  admin 
    -Group Repo:  Group001-SP23  successfuly created!
    -Repo URL: https://github.com/COGS118A/Group001-SP23
    -
    -Repo  Group002-SP23  Created... Wait for 3 sec to updates
    -File Successfully Renamed from   Checkpoint_groupXXX.ipynb   to  Checkpoint_Group002-SP23.ipynb 
    -File Successfully Renamed from   FinalProject_groupXXX.ipynb   to  FinalProject_Group002-SP23.ipynb 
    -File Successfully Renamed from   Proposal_groupXXX.ipynb   to  Proposal_Group002-SP23.ipynb 
    -Added Collaborator:  connormcmanigal  to:  Group002-SP23  with permission:  write 
    -Added Collaborator:  jup006  to:  Group002-SP23  with permission:  write 
    -Added Collaborator:  ssutharucsd  to:  Group002-SP23  with permission:  write 
    -Added Collaborator:  d3yu  to:  Group002-SP23  with permission:  write 
    -Team  Instructors_Sp23  added to  Group002-SP23  with permission  admin 
    -Group Repo:  Group002-SP23  successfuly created!
    -Repo URL: https://github.com/COGS118A/Group002-SP23
    -
    -Repo  Group003-SP23  Created... Wait for 3 sec to updates
    -File Successfully Renamed from   Checkpoint_groupXXX.ipynb   to  Checkpoint_Group003-SP23.ipynb 
    -File Successfully Renamed from   FinalProject_groupXXX.ipynb   to  FinalProject_Group003-SP23.ipynb 
    -File Successfully Renamed from   Proposal_groupXXX.ipynb   to  Proposal_Group003-SP23.ipynb 
    -Added Collaborator:  Cyl200215  to:  Group003-SP23  with permission:  write 
    -Added Collaborator:  scottieboyzhang  to:  Group003-SP23  with permission:  write 
    -Added Collaborator:  Orang1s  to:  Group003-SP23  with permission:  write 
    -Team  Instructors_Sp23  added to  Group003-SP23  with permission  admin 
    -Group Repo:  Group003-SP23  successfuly created!
    -Repo URL: https://github.com/COGS118A/Group003-SP23
    -
    -Repo  Group004-SP23  Created... Wait for 3 sec to updates
    -File Successfully Renamed from   Checkpoint_groupXXX.ipynb   to  Checkpoint_Group004-SP23.ipynb 
    -File Successfully Renamed from   FinalProject_groupXXX.ipynb   to  FinalProject_Group004-SP23.ipynb 
    -File Successfully Renamed from   Proposal_groupXXX.ipynb   to  Proposal_Group004-SP23.ipynb 
    -Added Collaborator:  CDavid99  to:  Group004-SP23  with permission:  write 
    -Added Collaborator:  nilu0311  to:  Group004-SP23  with permission:  write 
    -Added Collaborator:  cookingoil88  to:  Group004-SP23  with permission:  write 
    -Added Collaborator:  g6zhu  to:  Group004-SP23  with permission:  write 
    -Team  Instructors_Sp23  added to  Group004-SP23  with permission  admin 
    -Group Repo:  Group004-SP23  successfuly created!
    -Repo URL: https://github.com/COGS118A/Group004-SP23
    -
    -a8chu's GitHub Username not found
    -Repo  Group005-SP23  Created... Wait for 3 sec to updates
    -File Successfully Renamed from   Checkpoint_groupXXX.ipynb   to  Checkpoint_Group005-SP23.ipynb 
    -File Successfully Renamed from   FinalProject_groupXXX.ipynb   to  FinalProject_Group005-SP23.ipynb 
    -File Successfully Renamed from   Proposal_groupXXX.ipynb   to  Proposal_Group005-SP23.ipynb 
    -Added Collaborator:  kchen283  to:  Group005-SP23  with permission:  write 
    -Added Collaborator:  cdelira9  to:  Group005-SP23  with permission:  write 
    -Added Collaborator:  wj6801  to:  Group005-SP23  with permission:  write 
    -Added Collaborator:  arth-shukla  to:  Group005-SP23  with permission:  write 
    -Team  Instructors_Sp23  added to  Group005-SP23  with permission  admin 
    -Group Repo:  Group005-SP23  successfuly created!
    -Repo URL: https://github.com/COGS118A/Group005-SP23
    -
    -Repo  Group006-SP23  Created... Wait for 3 sec to updates
    -File Successfully Renamed from   Checkpoint_groupXXX.ipynb   to  Checkpoint_Group006-SP23.ipynb 
    -File Successfully Renamed from   FinalProject_groupXXX.ipynb   to  FinalProject_Group006-SP23.ipynb 
    -File Successfully Renamed from   Proposal_groupXXX.ipynb   to  Proposal_Group006-SP23.ipynb 
    -Added Collaborator:  Leoooo333  to:  Group006-SP23  with permission:  write 
    -Added Collaborator:  qdh-2002  to:  Group006-SP23  with permission:  write 
    -Added Collaborator:  Chihhsinli  to:  Group006-SP23  with permission:  write 
    -Team  Instructors_Sp23  added to  Group006-SP23  with permission  admin 
    -Group Repo:  Group006-SP23  successfuly created!
    -Repo URL: https://github.com/COGS118A/Group006-SP23
    -
    -j3dong's GitHub Username not found
    -Repo  Group007-SP23  Created... Wait for 3 sec to updates
    -File Successfully Renamed from   Checkpoint_groupXXX.ipynb   to  Checkpoint_Group007-SP23.ipynb 
    -File Successfully Renamed from   FinalProject_groupXXX.ipynb   to  FinalProject_Group007-SP23.ipynb 
    -File Successfully Renamed from   Proposal_groupXXX.ipynb   to  Proposal_Group007-SP23.ipynb 
    -Added Collaborator:  YunxiangChi  to:  Group007-SP23  with permission:  write 
    -Added Collaborator:  alien-invader  to:  Group007-SP23  with permission:  write 
    -Added Collaborator:  XiaoyanHe0713  to:  Group007-SP23  with permission:  write 
    -Added Collaborator:  Andrina-iris  to:  Group007-SP23  with permission:  write 
    -Team  Instructors_Sp23  added to  Group007-SP23  with permission  admin 
    -Group Repo:  Group007-SP23  successfuly created!
    -Repo URL: https://github.com/COGS118A/Group007-SP23
    -
    -Repo  Group008-SP23  Created... Wait for 3 sec to updates
    -File Successfully Renamed from   Checkpoint_groupXXX.ipynb   to  Checkpoint_Group008-SP23.ipynb 
    -File Successfully Renamed from   FinalProject_groupXXX.ipynb   to  FinalProject_Group008-SP23.ipynb 
    -File Successfully Renamed from   Proposal_groupXXX.ipynb   to  Proposal_Group008-SP23.ipynb 
    -Added Collaborator:  wwjasperww  to:  Group008-SP23  with permission:  write 
    -Added Collaborator:  ChengqinLi1206  to:  Group008-SP23  with permission:  write 
    -Added Collaborator:  junyuelin  to:  Group008-SP23  with permission:  write 
    -Added Collaborator:  fergusyyang  to:  Group008-SP23  with permission:  write 
    -Team  Instructors_Sp23  added to  Group008-SP23  with permission  admin 
    -Group Repo:  Group008-SP23  successfuly created!
    -Repo URL: https://github.com/COGS118A/Group008-SP23
    -
    -
    -
    -
    Repo  Group009-SP23  Created... Wait for 3 sec to updates
    -File Successfully Renamed from   Checkpoint_groupXXX.ipynb   to  Checkpoint_Group009-SP23.ipynb 
    -File Successfully Renamed from   FinalProject_groupXXX.ipynb   to  FinalProject_Group009-SP23.ipynb 
    -File Successfully Renamed from   Proposal_groupXXX.ipynb   to  Proposal_Group009-SP23.ipynb 
    -Added Collaborator:  thaiscodafond  to:  Group009-SP23  with permission:  write 
    -Added Collaborator:  ckavanagh21  to:  Group009-SP23  with permission:  write 
    -Added Collaborator:  404EZRA  to:  Group009-SP23  with permission:  write 
    -Added Collaborator:  vvishnus  to:  Group009-SP23  with permission:  write 
    -Team  Instructors_Sp23  added to  Group009-SP23  with permission  admin 
    -Group Repo:  Group009-SP23  successfuly created!
    -Repo URL: https://github.com/COGS118A/Group009-SP23
    -
    -Repo  Group010-SP23  Created... Wait for 3 sec to updates
    -File Successfully Renamed from   Checkpoint_groupXXX.ipynb   to  Checkpoint_Group010-SP23.ipynb 
    -File Successfully Renamed from   FinalProject_groupXXX.ipynb   to  FinalProject_Group010-SP23.ipynb 
    -File Successfully Renamed from   Proposal_groupXXX.ipynb   to  Proposal_Group010-SP23.ipynb 
    -Added Collaborator:  strawhatwilson23  to:  Group010-SP23  with permission:  write 
    -Added Collaborator:  tjamalcodes  to:  Group010-SP23  with permission:  write 
    -Added Collaborator:  jaysunl  to:  Group010-SP23  with permission:  write 
    -Added Collaborator:  idereknguyen  to:  Group010-SP23  with permission:  write 
    -Team  Instructors_Sp23  added to  Group010-SP23  with permission  admin 
    -Group Repo:  Group010-SP23  successfuly created!
    -Repo URL: https://github.com/COGS118A/Group010-SP23
    -
    -Repo  Group011-SP23  Created... Wait for 3 sec to updates
    -File Successfully Renamed from   Checkpoint_groupXXX.ipynb   to  Checkpoint_Group011-SP23.ipynb 
    -File Successfully Renamed from   FinalProject_groupXXX.ipynb   to  FinalProject_Group011-SP23.ipynb 
    -File Successfully Renamed from   Proposal_groupXXX.ipynb   to  Proposal_Group011-SP23.ipynb 
    -Added Collaborator:  khchuang12  to:  Group011-SP23  with permission:  write 
    -Added Collaborator:  Emdavis02  to:  Group011-SP23  with permission:  write 
    -Added Collaborator:  jennifer-jiang  to:  Group011-SP23  with permission:  write 
    -Added Collaborator:  nrejai  to:  Group011-SP23  with permission:  write 
    -Team  Instructors_Sp23  added to  Group011-SP23  with permission  admin 
    -Group Repo:  Group011-SP23  successfuly created!
    -Repo URL: https://github.com/COGS118A/Group011-SP23
    -
    -Repo  Group012-SP23  Created... Wait for 3 sec to updates
    -File Successfully Renamed from   Checkpoint_groupXXX.ipynb   to  Checkpoint_Group012-SP23.ipynb 
    -File Successfully Renamed from   FinalProject_groupXXX.ipynb   to  FinalProject_Group012-SP23.ipynb 
    -File Successfully Renamed from   Proposal_groupXXX.ipynb   to  Proposal_Group012-SP23.ipynb 
    -Added Collaborator:  afleschner  to:  Group012-SP23  with permission:  write 
    -Added Collaborator:  githubharsono  to:  Group012-SP23  with permission:  write 
    -Added Collaborator:  JJSanchez23  to:  Group012-SP23  with permission:  write 
    -Added Collaborator:  antarasengupta26  to:  Group012-SP23  with permission:  write 
    -Team  Instructors_Sp23  added to  Group012-SP23  with permission  admin 
    -Group Repo:  Group012-SP23  successfuly created!
    -Repo URL: https://github.com/COGS118A/Group012-SP23
    -
    -kehu's GitHub Username not found
    -Repo  Group013-SP23  Created... Wait for 3 sec to updates
    -File Successfully Renamed from   Checkpoint_groupXXX.ipynb   to  Checkpoint_Group013-SP23.ipynb 
    -File Successfully Renamed from   FinalProject_groupXXX.ipynb   to  FinalProject_Group013-SP23.ipynb 
    -File Successfully Renamed from   Proposal_groupXXX.ipynb   to  Proposal_Group013-SP23.ipynb 
    -Added Collaborator:  jnhuang02  to:  Group013-SP23  with permission:  write 
    -Added Collaborator:  Sean1572  to:  Group013-SP23  with permission:  write 
    -Added Collaborator:  valenciaaalberto  to:  Group013-SP23  with permission:  write 
    -Team  Instructors_Sp23  added to  Group013-SP23  with permission  admin 
    -Group Repo:  Group013-SP23  successfuly created!
    -Repo URL: https://github.com/COGS118A/Group013-SP23
    -
    -Repo  Group014-SP23  Created... Wait for 3 sec to updates
    -File Successfully Renamed from   Checkpoint_groupXXX.ipynb   to  Checkpoint_Group014-SP23.ipynb 
    -File Successfully Renamed from   FinalProject_groupXXX.ipynb   to  FinalProject_Group014-SP23.ipynb 
    -File Successfully Renamed from   Proposal_groupXXX.ipynb   to  Proposal_Group014-SP23.ipynb 
    -Added Collaborator:  empire-penguin  to:  Group014-SP23  with permission:  write 
    -Added Collaborator:  villafun  to:  Group014-SP23  with permission:  write 
    -Added Collaborator:  50ShadesOfShawn  to:  Group014-SP23  with permission:  write 
    -Added Collaborator:  ericzyl  to:  Group014-SP23  with permission:  write 
    -Team  Instructors_Sp23  added to  Group014-SP23  with permission  admin 
    -Group Repo:  Group014-SP23  successfuly created!
    -Repo URL: https://github.com/COGS118A/Group014-SP23
    -
    -Repo  Group015-SP23  Created... Wait for 3 sec to updates
    -File Successfully Renamed from   Checkpoint_groupXXX.ipynb   to  Checkpoint_Group015-SP23.ipynb 
    -File Successfully Renamed from   FinalProject_groupXXX.ipynb   to  FinalProject_Group015-SP23.ipynb 
    -File Successfully Renamed from   Proposal_groupXXX.ipynb   to  Proposal_Group015-SP23.ipynb 
    -Added Collaborator:  arjunanna  to:  Group015-SP23  with permission:  write 
    -Added Collaborator:  sdsilva1  to:  Group015-SP23  with permission:  write 
    -Added Collaborator:  abi2020  to:  Group015-SP23  with permission:  write 
    -Added Collaborator:  nikothomas  to:  Group015-SP23  with permission:  write 
    -Team  Instructors_Sp23  added to  Group015-SP23  with permission  admin 
    -Group Repo:  Group015-SP23  successfuly created!
    -Repo URL: https://github.com/COGS118A/Group015-SP23
    -
    -Repo  Group016-SP23  Created... Wait for 3 sec to updates
    -File Successfully Renamed from   Checkpoint_groupXXX.ipynb   to  Checkpoint_Group016-SP23.ipynb 
    -File Successfully Renamed from   FinalProject_groupXXX.ipynb   to  FinalProject_Group016-SP23.ipynb 
    -File Successfully Renamed from   Proposal_groupXXX.ipynb   to  Proposal_Group016-SP23.ipynb 
    -Added Collaborator:  shendu11  to:  Group016-SP23  with permission:  write 
    -Added Collaborator:  Y3GUO  to:  Group016-SP23  with permission:  write 
    -Added Collaborator:  EthanHu0  to:  Group016-SP23  with permission:  write 
    -Added Collaborator:  claireZHL  to:  Group016-SP23  with permission:  write 
    -Added Collaborator:  QilunLiu5216  to:  Group016-SP23  with permission:  write 
    -Team  Instructors_Sp23  added to  Group016-SP23  with permission  admin 
    -Group Repo:  Group016-SP23  successfuly created!
    -Repo URL: https://github.com/COGS118A/Group016-SP23
    -
    -
    -
    -
    Repo  Group017-SP23  Created... Wait for 3 sec to updates
    -File Successfully Renamed from   Checkpoint_groupXXX.ipynb   to  Checkpoint_Group017-SP23.ipynb 
    -File Successfully Renamed from   FinalProject_groupXXX.ipynb   to  FinalProject_Group017-SP23.ipynb 
    -File Successfully Renamed from   Proposal_groupXXX.ipynb   to  Proposal_Group017-SP23.ipynb 
    -Added Collaborator:  sreetama02  to:  Group017-SP23  with permission:  write 
    -Added Collaborator:  llennemann  to:  Group017-SP23  with permission:  write 
    -Added Collaborator:  pabbi5  to:  Group017-SP23  with permission:  write 
    -Added Collaborator:  AstuteFern  to:  Group017-SP23  with permission:  write 
    -Team  Instructors_Sp23  added to  Group017-SP23  with permission  admin 
    -Group Repo:  Group017-SP23  successfuly created!
    -Repo URL: https://github.com/COGS118A/Group017-SP23
    -
    -Repo  Group018-SP23  Created... Wait for 3 sec to updates
    -File Successfully Renamed from   Checkpoint_groupXXX.ipynb   to  Checkpoint_Group018-SP23.ipynb 
    -File Successfully Renamed from   FinalProject_groupXXX.ipynb   to  FinalProject_Group018-SP23.ipynb 
    -File Successfully Renamed from   Proposal_groupXXX.ipynb   to  Proposal_Group018-SP23.ipynb 
    -Added Collaborator:  ajcagle8  to:  Group018-SP23  with permission:  write 
    -Added Collaborator:  aHewig  to:  Group018-SP23  with permission:  write 
    -Added Collaborator:  notSaranshMalik  to:  Group018-SP23  with permission:  write 
    -Added Collaborator:  mnodini  to:  Group018-SP23  with permission:  write 
    -Added Collaborator:  maryamkusman  to:  Group018-SP23  with permission:  write 
    -Team  Instructors_Sp23  added to  Group018-SP23  with permission  admin 
    -Group Repo:  Group018-SP23  successfuly created!
    -Repo URL: https://github.com/COGS118A/Group018-SP23
    -
    -Repo  Group019-SP23  Created... Wait for 3 sec to updates
    -File Successfully Renamed from   Checkpoint_groupXXX.ipynb   to  Checkpoint_Group019-SP23.ipynb 
    -File Successfully Renamed from   FinalProject_groupXXX.ipynb   to  FinalProject_Group019-SP23.ipynb 
    -File Successfully Renamed from   Proposal_groupXXX.ipynb   to  Proposal_Group019-SP23.ipynb 
    -Added Collaborator:  SSingh44343  to:  Group019-SP23  with permission:  write 
    -Added Collaborator:  nathansit  to:  Group019-SP23  with permission:  write 
    -Added Collaborator:  DaimengSun  to:  Group019-SP23  with permission:  write 
    -Team  Instructors_Sp23  added to  Group019-SP23  with permission  admin 
    -Group Repo:  Group019-SP23  successfuly created!
    -Repo URL: https://github.com/COGS118A/Group019-SP23
    -
    -Repo  Group020-SP23  Created... Wait for 3 sec to updates
    -File Successfully Renamed from   Checkpoint_groupXXX.ipynb   to  Checkpoint_Group020-SP23.ipynb 
    -File Successfully Renamed from   FinalProject_groupXXX.ipynb   to  FinalProject_Group020-SP23.ipynb 
    -File Successfully Renamed from   Proposal_groupXXX.ipynb   to  Proposal_Group020-SP23.ipynb 
    -Added Collaborator:  ashesh8500  to:  Group020-SP23  with permission:  write 
    -
    -
    -
    -
    -

    Resent Invitations

    -

    GitHub collaboration invites will be expired automatically when the user did not accept the invite after a certain period of time. After all the group repositories are created, the command GitHubGroup.resent_invitations_team_repos will rescind all pending invitations and resent invitation to that collaborators.

    -

    This command is particularly useful when managing a large volume of repositories as it painlessly re-validated and re-sent all pending invitations of all repositories under a team. We ran this command daily to constantly remind student to accept their GitHub invitations, until all students have a valid permission to the target repository.

    -
    -
    ggroup.resent_invitations_team_repos(
    -    team_slug="Instructors_Sp23"
    -)
    -
    -
    Repository  AssignmentNotebooksSource_SP23 :
    -The list of pending invitation:
    -[]
    -Repository  AssignmentNotebooks_SP23 :
    -The list of pending invitation:
    -[]
    -Repository  DiscussionSectionNotebooks :
    -The list of pending invitation:
    -[]
    -Repository  Dockerfiles :
    -The list of pending invitation:
    -[]
    -Repository  Group001-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="demimao"),
    - NamedUser(login="xiw013"),
    - NamedUser(login="Ju-dyz"),
    - NamedUser(login="TaraaHe")]
    -demimao Invite Revoked 
    -Added Collaborator:  demimao  to:  Group001-SP23  with permission:  write 
    - Invite Resent to demimao 
    -xiw013 Invite Revoked 
    -Added Collaborator:  xiw013  to:  Group001-SP23  with permission:  write 
    - Invite Resent to xiw013 
    -Ju-dyz Invite Revoked 
    -Added Collaborator:  Ju-dyz  to:  Group001-SP23  with permission:  write 
    - Invite Resent to Ju-dyz 
    -TaraaHe Invite Revoked 
    -Added Collaborator:  TaraaHe  to:  Group001-SP23  with permission:  write 
    - Invite Resent to TaraaHe 
    -Repository  Group002-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="jup006"),
    - NamedUser(login="ssutharucsd"),
    - NamedUser(login="connormcmanigal"),
    - NamedUser(login="d3yu")]
    -jup006 Invite Revoked 
    -Added Collaborator:  jup006  to:  Group002-SP23  with permission:  write 
    - Invite Resent to jup006 
    -ssutharucsd Invite Revoked 
    -Added Collaborator:  ssutharucsd  to:  Group002-SP23  with permission:  write 
    - Invite Resent to ssutharucsd 
    -connormcmanigal Invite Revoked 
    -Added Collaborator:  connormcmanigal  to:  Group002-SP23  with permission:  write 
    - Invite Resent to connormcmanigal 
    -d3yu Invite Revoked 
    -Added Collaborator:  d3yu  to:  Group002-SP23  with permission:  write 
    - Invite Resent to d3yu 
    -Repository  Group003-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="scottieboyzhang"), NamedUser(login="Orang1s")]
    -scottieboyzhang Invite Revoked 
    -Added Collaborator:  scottieboyzhang  to:  Group003-SP23  with permission:  write 
    - Invite Resent to scottieboyzhang 
    -Orang1s Invite Revoked 
    -Added Collaborator:  Orang1s  to:  Group003-SP23  with permission:  write 
    - Invite Resent to Orang1s 
    -Repository  Group004-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="CDavid99"),
    - NamedUser(login="nilu0311"),
    - NamedUser(login="g6zhu")]
    -CDavid99 Invite Revoked 
    -Added Collaborator:  CDavid99  to:  Group004-SP23  with permission:  write 
    - Invite Resent to CDavid99 
    -nilu0311 Invite Revoked 
    -Added Collaborator:  nilu0311  to:  Group004-SP23  with permission:  write 
    - Invite Resent to nilu0311 
    -g6zhu Invite Revoked 
    -Added Collaborator:  g6zhu  to:  Group004-SP23  with permission:  write 
    - Invite Resent to g6zhu 
    -Repository  Group005-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="arth-shukla"),
    - NamedUser(login="kchen283"),
    - NamedUser(login="cdelira9")]
    -arth-shukla Invite Revoked 
    -Added Collaborator:  arth-shukla  to:  Group005-SP23  with permission:  write 
    - Invite Resent to arth-shukla 
    -kchen283 Invite Revoked 
    -Added Collaborator:  kchen283  to:  Group005-SP23  with permission:  write 
    - Invite Resent to kchen283 
    -cdelira9 Invite Revoked 
    -Added Collaborator:  cdelira9  to:  Group005-SP23  with permission:  write 
    - Invite Resent to cdelira9 
    -Repository  Group006-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="qdh-2002"), NamedUser(login="Chihhsinli")]
    -qdh-2002 Invite Revoked 
    -Added Collaborator:  qdh-2002  to:  Group006-SP23  with permission:  write 
    - Invite Resent to qdh-2002 
    -Chihhsinli Invite Revoked 
    -Added Collaborator:  Chihhsinli  to:  Group006-SP23  with permission:  write 
    - Invite Resent to Chihhsinli 
    -Repository  Group007-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="YunxiangChi"),
    - NamedUser(login="Andrina-iris"),
    - NamedUser(login="alien-invader")]
    -YunxiangChi Invite Revoked 
    -Added Collaborator:  YunxiangChi  to:  Group007-SP23  with permission:  write 
    - Invite Resent to YunxiangChi 
    -Andrina-iris Invite Revoked 
    -Added Collaborator:  Andrina-iris  to:  Group007-SP23  with permission:  write 
    - Invite Resent to Andrina-iris 
    -alien-invader Invite Revoked 
    -Added Collaborator:  alien-invader  to:  Group007-SP23  with permission:  write 
    - Invite Resent to alien-invader 
    -Repository  Group008-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="wwjasperww")]
    -wwjasperww Invite Revoked 
    -Added Collaborator:  wwjasperww  to:  Group008-SP23  with permission:  write 
    - Invite Resent to wwjasperww 
    -Repository  Group009-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="404EZRA"), NamedUser(login="vvishnus")]
    -404EZRA Invite Revoked 
    -Added Collaborator:  404EZRA  to:  Group009-SP23  with permission:  write 
    - Invite Resent to 404EZRA 
    -vvishnus Invite Revoked 
    -Added Collaborator:  vvishnus  to:  Group009-SP23  with permission:  write 
    - Invite Resent to vvishnus 
    -Repository  Group010-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="idereknguyen"),
    - NamedUser(login="jaysunl"),
    - NamedUser(login="tjamalcodes"),
    - NamedUser(login="strawhatwilson23")]
    -idereknguyen Invite Revoked 
    -Added Collaborator:  idereknguyen  to:  Group010-SP23  with permission:  write 
    - Invite Resent to idereknguyen 
    -jaysunl Invite Revoked 
    -Added Collaborator:  jaysunl  to:  Group010-SP23  with permission:  write 
    - Invite Resent to jaysunl 
    -tjamalcodes Invite Revoked 
    -Added Collaborator:  tjamalcodes  to:  Group010-SP23  with permission:  write 
    - Invite Resent to tjamalcodes 
    -strawhatwilson23 Invite Revoked 
    -Added Collaborator:  strawhatwilson23  to:  Group010-SP23  with permission:  write 
    - Invite Resent to strawhatwilson23 
    -Repository  Group011-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="khchuang12"),
    - NamedUser(login="nrejai"),
    - NamedUser(login="emdavis02")]
    -khchuang12 Invite Revoked 
    -Added Collaborator:  khchuang12  to:  Group011-SP23  with permission:  write 
    - Invite Resent to khchuang12 
    -nrejai Invite Revoked 
    -Added Collaborator:  nrejai  to:  Group011-SP23  with permission:  write 
    - Invite Resent to nrejai 
    -emdavis02 Invite Revoked 
    -Added Collaborator:  emdavis02  to:  Group011-SP23  with permission:  write 
    - Invite Resent to emdavis02 
    -Repository  Group012-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="githubharsono"),
    - NamedUser(login="JJSanchez23"),
    - NamedUser(login="antarasengupta26")]
    -githubharsono Invite Revoked 
    -Added Collaborator:  githubharsono  to:  Group012-SP23  with permission:  write 
    - Invite Resent to githubharsono 
    -
    -
    -
    JJSanchez23 Invite Revoked 
    -Added Collaborator:  JJSanchez23  to:  Group012-SP23  with permission:  write 
    - Invite Resent to JJSanchez23 
    -antarasengupta26 Invite Revoked 
    -Added Collaborator:  antarasengupta26  to:  Group012-SP23  with permission:  write 
    - Invite Resent to antarasengupta26 
    -Repository  Group013-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="Sean1572"), NamedUser(login="jnhuang02")]
    -Sean1572 Invite Revoked 
    -Added Collaborator:  Sean1572  to:  Group013-SP23  with permission:  write 
    - Invite Resent to Sean1572 
    -jnhuang02 Invite Revoked 
    -Added Collaborator:  jnhuang02  to:  Group013-SP23  with permission:  write 
    - Invite Resent to jnhuang02 
    -Repository  Group014-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="ericzyl"),
    - NamedUser(login="50ShadesOfShawn"),
    - NamedUser(login="villafun")]
    -ericzyl Invite Revoked 
    -Added Collaborator:  ericzyl  to:  Group014-SP23  with permission:  write 
    - Invite Resent to ericzyl 
    -50ShadesOfShawn Invite Revoked 
    -Added Collaborator:  50ShadesOfShawn  to:  Group014-SP23  with permission:  write 
    - Invite Resent to 50ShadesOfShawn 
    -villafun Invite Revoked 
    -Added Collaborator:  villafun  to:  Group014-SP23  with permission:  write 
    - Invite Resent to villafun 
    -Repository  Group015-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="nikothomas"),
    - NamedUser(login="abi2020"),
    - NamedUser(login="arjunanna"),
    - NamedUser(login="sdsilva1")]
    -nikothomas Invite Revoked 
    -Added Collaborator:  nikothomas  to:  Group015-SP23  with permission:  write 
    - Invite Resent to nikothomas 
    -abi2020 Invite Revoked 
    -Added Collaborator:  abi2020  to:  Group015-SP23  with permission:  write 
    - Invite Resent to abi2020 
    -arjunanna Invite Revoked 
    -Added Collaborator:  arjunanna  to:  Group015-SP23  with permission:  write 
    - Invite Resent to arjunanna 
    -sdsilva1 Invite Revoked 
    -Added Collaborator:  sdsilva1  to:  Group015-SP23  with permission:  write 
    - Invite Resent to sdsilva1 
    -Repository  Group016-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="shendu11"),
    - NamedUser(login="EthanHu0"),
    - NamedUser(login="QilunLiu5216"),
    - NamedUser(login="Y3GUO"),
    - NamedUser(login="claireZHL")]
    -shendu11 Invite Revoked 
    -Added Collaborator:  shendu11  to:  Group016-SP23  with permission:  write 
    - Invite Resent to shendu11 
    -EthanHu0 Invite Revoked 
    -Added Collaborator:  EthanHu0  to:  Group016-SP23  with permission:  write 
    - Invite Resent to EthanHu0 
    -QilunLiu5216 Invite Revoked 
    -Added Collaborator:  QilunLiu5216  to:  Group016-SP23  with permission:  write 
    - Invite Resent to QilunLiu5216 
    -Y3GUO Invite Revoked 
    -Added Collaborator:  Y3GUO  to:  Group016-SP23  with permission:  write 
    - Invite Resent to Y3GUO 
    -claireZHL Invite Revoked 
    -Added Collaborator:  claireZHL  to:  Group016-SP23  with permission:  write 
    - Invite Resent to claireZHL 
    -Repository  Group017-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="AstuteFern"),
    - NamedUser(login="sreetama02"),
    - NamedUser(login="pabbi5")]
    -AstuteFern Invite Revoked 
    -Added Collaborator:  AstuteFern  to:  Group017-SP23  with permission:  write 
    - Invite Resent to AstuteFern 
    -sreetama02 Invite Revoked 
    -Added Collaborator:  sreetama02  to:  Group017-SP23  with permission:  write 
    - Invite Resent to sreetama02 
    -pabbi5 Invite Revoked 
    -Added Collaborator:  pabbi5  to:  Group017-SP23  with permission:  write 
    - Invite Resent to pabbi5 
    -Repository  Group018-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="notSaranshMalik"),
    - NamedUser(login="mnodini"),
    - NamedUser(login="Maryamkusman"),
    - NamedUser(login="ajcagle8")]
    -notSaranshMalik Invite Revoked 
    -Added Collaborator:  notSaranshMalik  to:  Group018-SP23  with permission:  write 
    - Invite Resent to notSaranshMalik 
    -mnodini Invite Revoked 
    -Added Collaborator:  mnodini  to:  Group018-SP23  with permission:  write 
    - Invite Resent to mnodini 
    -Maryamkusman Invite Revoked 
    -Added Collaborator:  Maryamkusman  to:  Group018-SP23  with permission:  write 
    - Invite Resent to Maryamkusman 
    -ajcagle8 Invite Revoked 
    -Added Collaborator:  ajcagle8  to:  Group018-SP23  with permission:  write 
    - Invite Resent to ajcagle8 
    -Repository  Group019-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="nathansit"),
    - NamedUser(login="SSingh44343"),
    - NamedUser(login="DaimengSun")]
    -nathansit Invite Revoked 
    -Added Collaborator:  nathansit  to:  Group019-SP23  with permission:  write 
    - Invite Resent to nathansit 
    -SSingh44343 Invite Revoked 
    -Added Collaborator:  SSingh44343  to:  Group019-SP23  with permission:  write 
    - Invite Resent to SSingh44343 
    -DaimengSun Invite Revoked 
    -Added Collaborator:  DaimengSun  to:  Group019-SP23  with permission:  write 
    - Invite Resent to DaimengSun 
    -Repository  Group020-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="ashesh8500"),
    - NamedUser(login="ATrapenard"),
    - NamedUser(login="meghapareek2003")]
    -ashesh8500 Invite Revoked 
    -Added Collaborator:  ashesh8500  to:  Group020-SP23  with permission:  write 
    - Invite Resent to ashesh8500 
    -ATrapenard Invite Revoked 
    -Added Collaborator:  ATrapenard  to:  Group020-SP23  with permission:  write 
    - Invite Resent to ATrapenard 
    -meghapareek2003 Invite Revoked 
    -Added Collaborator:  meghapareek2003  to:  Group020-SP23  with permission:  write 
    - Invite Resent to meghapareek2003 
    -Repository  Group021-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="JasonKrentsel"),
    - NamedUser(login="shantellemeganserafin"),
    - NamedUser(login="orangejustin"),
    - NamedUser(login="jmlai08")]
    -JasonKrentsel Invite Revoked 
    -Added Collaborator:  JasonKrentsel  to:  Group021-SP23  with permission:  write 
    - Invite Resent to JasonKrentsel 
    -shantellemeganserafin Invite Revoked 
    -Added Collaborator:  shantellemeganserafin  to:  Group021-SP23  with permission:  write 
    - Invite Resent to shantellemeganserafin 
    -orangejustin Invite Revoked 
    -Added Collaborator:  orangejustin  to:  Group021-SP23  with permission:  write 
    - Invite Resent to orangejustin 
    -jmlai08 Invite Revoked 
    -Added Collaborator:  jmlai08  to:  Group021-SP23  with permission:  write 
    - Invite Resent to jmlai08 
    -Repository  Group022-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="anchen31"), NamedUser(login="nggalen")]
    -anchen31 Invite Revoked 
    -Added Collaborator:  anchen31  to:  Group022-SP23  with permission:  write 
    - Invite Resent to anchen31 
    -
    -
    -
    nggalen Invite Revoked 
    -Added Collaborator:  nggalen  to:  Group022-SP23  with permission:  write 
    - Invite Resent to nggalen 
    -Repository  Group023-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="VigneshJ14"),
    - NamedUser(login="helclp"),
    - NamedUser(login="rioak"),
    - NamedUser(login="chrishrochez"),
    - NamedUser(login="kpstern")]
    -VigneshJ14 Invite Revoked 
    -Added Collaborator:  VigneshJ14  to:  Group023-SP23  with permission:  write 
    - Invite Resent to VigneshJ14 
    -helclp Invite Revoked 
    -Added Collaborator:  helclp  to:  Group023-SP23  with permission:  write 
    - Invite Resent to helclp 
    -rioak Invite Revoked 
    -Added Collaborator:  rioak  to:  Group023-SP23  with permission:  write 
    - Invite Resent to rioak 
    -chrishrochez Invite Revoked 
    -Added Collaborator:  chrishrochez  to:  Group023-SP23  with permission:  write 
    - Invite Resent to chrishrochez 
    -kpstern Invite Revoked 
    -Added Collaborator:  kpstern  to:  Group023-SP23  with permission:  write 
    - Invite Resent to kpstern 
    -Repository  Group024-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="MoMo339610"),
    - NamedUser(login="ccaban6"),
    - NamedUser(login="Nakshatra120"),
    - NamedUser(login="sim-anna")]
    -MoMo339610 Invite Revoked 
    -Added Collaborator:  MoMo339610  to:  Group024-SP23  with permission:  write 
    - Invite Resent to MoMo339610 
    -ccaban6 Invite Revoked 
    -Added Collaborator:  ccaban6  to:  Group024-SP23  with permission:  write 
    - Invite Resent to ccaban6 
    -Nakshatra120 Invite Revoked 
    -Added Collaborator:  Nakshatra120  to:  Group024-SP23  with permission:  write 
    - Invite Resent to Nakshatra120 
    -sim-anna Invite Revoked 
    -Added Collaborator:  sim-anna  to:  Group024-SP23  with permission:  write 
    - Invite Resent to sim-anna 
    -Repository  Group025-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="Shayfe"),
    - NamedUser(login="jenniferwong1808"),
    - NamedUser(login="belindayan1000"),
    - NamedUser(login="hibask")]
    -Shayfe Invite Revoked 
    -Added Collaborator:  Shayfe  to:  Group025-SP23  with permission:  write 
    - Invite Resent to Shayfe 
    -jenniferwong1808 Invite Revoked 
    -Added Collaborator:  jenniferwong1808  to:  Group025-SP23  with permission:  write 
    - Invite Resent to jenniferwong1808 
    -belindayan1000 Invite Revoked 
    -Added Collaborator:  belindayan1000  to:  Group025-SP23  with permission:  write 
    - Invite Resent to belindayan1000 
    -hibask Invite Revoked 
    -Added Collaborator:  hibask  to:  Group025-SP23  with permission:  write 
    - Invite Resent to hibask 
    -Repository  Group026-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="e81786"),
    - NamedUser(login="hyun04p"),
    - NamedUser(login="Yuanzhen-Zhu"),
    - NamedUser(login="aaolivas")]
    -e81786 Invite Revoked 
    -Added Collaborator:  e81786  to:  Group026-SP23  with permission:  write 
    - Invite Resent to e81786 
    -hyun04p Invite Revoked 
    -Added Collaborator:  hyun04p  to:  Group026-SP23  with permission:  write 
    - Invite Resent to hyun04p 
    -Yuanzhen-Zhu Invite Revoked 
    -Added Collaborator:  Yuanzhen-Zhu  to:  Group026-SP23  with permission:  write 
    - Invite Resent to Yuanzhen-Zhu 
    -aaolivas Invite Revoked 
    -Added Collaborator:  aaolivas  to:  Group026-SP23  with permission:  write 
    - Invite Resent to aaolivas 
    -Repository  Group027-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="Jddeleon1981"),
    - NamedUser(login="natenyul"),
    - NamedUser(login="jifsus")]
    -Jddeleon1981 Invite Revoked 
    -Added Collaborator:  Jddeleon1981  to:  Group027-SP23  with permission:  write 
    - Invite Resent to Jddeleon1981 
    -natenyul Invite Revoked 
    -Added Collaborator:  natenyul  to:  Group027-SP23  with permission:  write 
    - Invite Resent to natenyul 
    -jifsus Invite Revoked 
    -Added Collaborator:  jifsus  to:  Group027-SP23  with permission:  write 
    - Invite Resent to jifsus 
    -Repository  Group028-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="mabdilahCSE"),
    - NamedUser(login="kylenakai"),
    - NamedUser(login="valar23"),
    - NamedUser(login="johnpaulonza")]
    -mabdilahCSE Invite Revoked 
    -Added Collaborator:  mabdilahCSE  to:  Group028-SP23  with permission:  write 
    - Invite Resent to mabdilahCSE 
    -kylenakai Invite Revoked 
    -Added Collaborator:  kylenakai  to:  Group028-SP23  with permission:  write 
    - Invite Resent to kylenakai 
    -valar23 Invite Revoked 
    -Added Collaborator:  valar23  to:  Group028-SP23  with permission:  write 
    - Invite Resent to valar23 
    -johnpaulonza Invite Revoked 
    -Added Collaborator:  johnpaulonza  to:  Group028-SP23  with permission:  write 
    - Invite Resent to johnpaulonza 
    -Repository  Group029-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="joshsalce"),
    - NamedUser(login="lmitbo"),
    - NamedUser(login="LukeSkerrett"),
    - NamedUser(login="jubamadu")]
    -joshsalce Invite Revoked 
    -Added Collaborator:  joshsalce  to:  Group029-SP23  with permission:  write 
    - Invite Resent to joshsalce 
    -lmitbo Invite Revoked 
    -Added Collaborator:  lmitbo  to:  Group029-SP23  with permission:  write 
    - Invite Resent to lmitbo 
    -LukeSkerrett Invite Revoked 
    -Added Collaborator:  LukeSkerrett  to:  Group029-SP23  with permission:  write 
    - Invite Resent to LukeSkerrett 
    -jubamadu Invite Revoked 
    -Added Collaborator:  jubamadu  to:  Group029-SP23  with permission:  write 
    - Invite Resent to jubamadu 
    -Repository  Group030-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="kmbnino"),
    - NamedUser(login="hiiminbush"),
    - NamedUser(login="RafferyChen"),
    - NamedUser(login="bonzonwin")]
    -kmbnino Invite Revoked 
    -Added Collaborator:  kmbnino  to:  Group030-SP23  with permission:  write 
    - Invite Resent to kmbnino 
    -hiiminbush Invite Revoked 
    -Added Collaborator:  hiiminbush  to:  Group030-SP23  with permission:  write 
    - Invite Resent to hiiminbush 
    -RafferyChen Invite Revoked 
    -Added Collaborator:  RafferyChen  to:  Group030-SP23  with permission:  write 
    - Invite Resent to RafferyChen 
    -bonzonwin Invite Revoked 
    -Added Collaborator:  bonzonwin  to:  Group030-SP23  with permission:  write 
    - Invite Resent to bonzonwin 
    -Repository  Group031-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="kendrick010"), NamedUser(login="getpakt")]
    -kendrick010 Invite Revoked 
    -Added Collaborator:  kendrick010  to:  Group031-SP23  with permission:  write 
    - Invite Resent to kendrick010 
    -getpakt Invite Revoked 
    -Added Collaborator:  getpakt  to:  Group031-SP23  with permission:  write 
    - Invite Resent to getpakt 
    -Repository  Group032-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="CharlesXu-Jingyue"),
    - NamedUser(login="hinyzee"),
    - NamedUser(login="Zachary-chao"),
    - NamedUser(login="smurase")]
    -
    -
    -
    CharlesXu-Jingyue Invite Revoked 
    -Added Collaborator:  CharlesXu-Jingyue  to:  Group032-SP23  with permission:  write 
    - Invite Resent to CharlesXu-Jingyue 
    -hinyzee Invite Revoked 
    -Added Collaborator:  hinyzee  to:  Group032-SP23  with permission:  write 
    - Invite Resent to hinyzee 
    -Zachary-chao Invite Revoked 
    -Added Collaborator:  Zachary-chao  to:  Group032-SP23  with permission:  write 
    - Invite Resent to Zachary-chao 
    -smurase Invite Revoked 
    -Added Collaborator:  smurase  to:  Group032-SP23  with permission:  write 
    - Invite Resent to smurase 
    -Repository  Group033-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="jason886595"),
    - NamedUser(login="cqrnik"),
    - NamedUser(login="AnyaBoo"),
    - NamedUser(login="areenlu"),
    - NamedUser(login="TydenRucker")]
    -jason886595 Invite Revoked 
    -Added Collaborator:  jason886595  to:  Group033-SP23  with permission:  write 
    - Invite Resent to jason886595 
    -cqrnik Invite Revoked 
    -Added Collaborator:  cqrnik  to:  Group033-SP23  with permission:  write 
    - Invite Resent to cqrnik 
    -AnyaBoo Invite Revoked 
    -Added Collaborator:  AnyaBoo  to:  Group033-SP23  with permission:  write 
    - Invite Resent to AnyaBoo 
    -areenlu Invite Revoked 
    -Added Collaborator:  areenlu  to:  Group033-SP23  with permission:  write 
    - Invite Resent to areenlu 
    -TydenRucker Invite Revoked 
    -Added Collaborator:  TydenRucker  to:  Group033-SP23  with permission:  write 
    - Invite Resent to TydenRucker 
    -Repository  Group034-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="vinaypillai"),
    - NamedUser(login="rchaklas"),
    - NamedUser(login="kavyaabalaji"),
    - NamedUser(login="Juan2002V")]
    -vinaypillai Invite Revoked 
    -Added Collaborator:  vinaypillai  to:  Group034-SP23  with permission:  write 
    - Invite Resent to vinaypillai 
    -rchaklas Invite Revoked 
    -Added Collaborator:  rchaklas  to:  Group034-SP23  with permission:  write 
    - Invite Resent to rchaklas 
    -kavyaabalaji Invite Revoked 
    -Added Collaborator:  kavyaabalaji  to:  Group034-SP23  with permission:  write 
    - Invite Resent to kavyaabalaji 
    -Juan2002V Invite Revoked 
    -Added Collaborator:  Juan2002V  to:  Group034-SP23  with permission:  write 
    - Invite Resent to Juan2002V 
    -Repository  Group035-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="hyperburn777"),
    - NamedUser(login="CristianAH"),
    - NamedUser(login="rihusedesign")]
    -hyperburn777 Invite Revoked 
    -Added Collaborator:  hyperburn777  to:  Group035-SP23  with permission:  write 
    - Invite Resent to hyperburn777 
    -CristianAH Invite Revoked 
    -Added Collaborator:  CristianAH  to:  Group035-SP23  with permission:  write 
    - Invite Resent to CristianAH 
    -rihusedesign Invite Revoked 
    -Added Collaborator:  rihusedesign  to:  Group035-SP23  with permission:  write 
    - Invite Resent to rihusedesign 
    -Repository  Group036-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="kohlir2020"),
    - NamedUser(login="dhavaljjani"),
    - NamedUser(login="Mkhlf"),
    - NamedUser(login="esti28")]
    -kohlir2020 Invite Revoked 
    -Added Collaborator:  kohlir2020  to:  Group036-SP23  with permission:  write 
    - Invite Resent to kohlir2020 
    -dhavaljjani Invite Revoked 
    -Added Collaborator:  dhavaljjani  to:  Group036-SP23  with permission:  write 
    - Invite Resent to dhavaljjani 
    -Mkhlf Invite Revoked 
    -Added Collaborator:  Mkhlf  to:  Group036-SP23  with permission:  write 
    - Invite Resent to Mkhlf 
    -esti28 Invite Revoked 
    -Added Collaborator:  esti28  to:  Group036-SP23  with permission:  write 
    - Invite Resent to esti28 
    -Repository  Group037-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="gyuj"),
    - NamedUser(login="NickAzp"),
    - NamedUser(login="kleumas")]
    -gyuj Invite Revoked 
    -Added Collaborator:  gyuj  to:  Group037-SP23  with permission:  write 
    - Invite Resent to gyuj 
    -NickAzp Invite Revoked 
    -Added Collaborator:  NickAzp  to:  Group037-SP23  with permission:  write 
    - Invite Resent to NickAzp 
    -kleumas Invite Revoked 
    -Added Collaborator:  kleumas  to:  Group037-SP23  with permission:  write 
    - Invite Resent to kleumas 
    -Repository  Group038-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="crickwang"),
    - NamedUser(login="the-bruz"),
    - NamedUser(login="m-sherrick")]
    -crickwang Invite Revoked 
    -Added Collaborator:  crickwang  to:  Group038-SP23  with permission:  write 
    - Invite Resent to crickwang 
    -the-bruz Invite Revoked 
    -Added Collaborator:  the-bruz  to:  Group038-SP23  with permission:  write 
    - Invite Resent to the-bruz 
    -m-sherrick Invite Revoked 
    -Added Collaborator:  m-sherrick  to:  Group038-SP23  with permission:  write 
    - Invite Resent to m-sherrick 
    -Repository  Group039-SP23 :
    -The list of pending invitation:
    -[NamedUser(login="dannyr742"),
    - NamedUser(login="clarissagtz"),
    - NamedUser(login="z8jiang"),
    - NamedUser(login="jtaolan")]
    -dannyr742 Invite Revoked 
    -Added Collaborator:  dannyr742  to:  Group039-SP23  with permission:  write 
    - Invite Resent to dannyr742 
    -clarissagtz Invite Revoked 
    -Added Collaborator:  clarissagtz  to:  Group039-SP23  with permission:  write 
    - Invite Resent to clarissagtz 
    -z8jiang Invite Revoked 
    -Added Collaborator:  z8jiang  to:  Group039-SP23  with permission:  write 
    - Invite Resent to z8jiang 
    -jtaolan Invite Revoked 
    -Added Collaborator:  jtaolan  to:  Group039-SP23  with permission:  write 
    - Invite Resent to jtaolan 
    -Repository  Lectures :
    -The list of pending invitation:
    -[]
    -Repository  Notebooks :
    -The list of pending invitation:
    -[]
    -
    -
    -
    -
    -

    The End of the Workflow

    -

    If you still have concerns, please reach out via GitHub Issue (on the RHS bar) or reach out me directly via email:

    - - -
    - -
    - -
    - - - - \ No newline at end of file diff --git a/_proc/_docs/tutorials/create template issues.html b/_proc/_docs/tutorials/create template issues.html deleted file mode 100644 index aa1f057..0000000 --- a/_proc/_docs/tutorials/create template issues.html +++ /dev/null @@ -1,481 +0,0 @@ - - - - - - - - - - -CanvasGroupy - Populate Grading Rubric Template on GitHub Repositories - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    - - -
    - -
    - - - - - -
    - -
    -
    -

    Populate Grading Rubric Template on GitHub Repositories

    -
    - -
    -
    - Create modifiable and gradable GitHub issues for project grading -
    -
    - - -
    - - - - -
    - - -
    - - -
    -
    import time
    -from CanvasGroupy import *
    -credentials_fp = "../credentials.json"
    -ghg = GitHubGroup(credentials_fp, org="COGS118A")
    -
    -
    Successfully Authenticated. GitHub account:  scott-yj-yang 
    -Target Organization Set:  COGS118A 
    -
    -
    -
    -
    for i in range(1, 40):
    -    repo_name = f"Group{i:03}-SP23"
    -    repo = ghg.get_repo(repo_name)
    -    ghg.create_issue_from_md(repo, "../CanvasGroupy/nbs/feedback_template/proposal_feedback.md")
    -    time.sleep(1)
    -
    -
    In the repo: Group021-SP23,
    -Issue Project Proposal Feedback Created!
    -In the repo: Group022-SP23,
    -Issue Project Proposal Feedback Created!
    -In the repo: Group023-SP23,
    -Issue Project Proposal Feedback Created!
    -In the repo: Group024-SP23,
    -Issue Project Proposal Feedback Created!
    -In the repo: Group025-SP23,
    -Issue Project Proposal Feedback Created!
    -In the repo: Group026-SP23,
    -Issue Project Proposal Feedback Created!
    -In the repo: Group027-SP23,
    -Issue Project Proposal Feedback Created!
    -In the repo: Group028-SP23,
    -Issue Project Proposal Feedback Created!
    -In the repo: Group029-SP23,
    -Issue Project Proposal Feedback Created!
    -In the repo: Group030-SP23,
    -Issue Project Proposal Feedback Created!
    -In the repo: Group031-SP23,
    -Issue Project Proposal Feedback Created!
    -In the repo: Group032-SP23,
    -Issue Project Proposal Feedback Created!
    -In the repo: Group033-SP23,
    -Issue Project Proposal Feedback Created!
    -In the repo: Group034-SP23,
    -Issue Project Proposal Feedback Created!
    -In the repo: Group035-SP23,
    -Issue Project Proposal Feedback Created!
    -In the repo: Group036-SP23,
    -Issue Project Proposal Feedback Created!
    -In the repo: Group037-SP23,
    -Issue Project Proposal Feedback Created!
    -In the repo: Group038-SP23,
    -Issue Project Proposal Feedback Created!
    -In the repo: Group039-SP23,
    -Issue Project Proposal Feedback Created!
    -
    -
    - - - -
    - -
    - - - - \ No newline at end of file diff --git a/_proc/_quarto.yml b/_proc/_quarto.yml deleted file mode 100644 index d69fe71..0000000 --- a/_proc/_quarto.yml +++ /dev/null @@ -1,28 +0,0 @@ -project: - type: website - -format: - html: - theme: cosmo - css: styles.css - toc: true - -website: - twitter-card: true - open-graph: true - repo-actions: [issue] - navbar: - background: primary - search: true - sidebar: - style: floating - contents: - - auto: "/index.ipynb" - - section: Tutorials - contents: tutorials/* - - section: API - contents: api/* - - section: Project Feedback Template - contents: feedback_template/* - -metadata-files: [nbdev.yml] \ No newline at end of file diff --git a/_proc/api/01_GroupEng_assign.ipynb b/_proc/api/01_GroupEng_assign.ipynb deleted file mode 100644 index 5805035..0000000 --- a/_proc/api/01_GroupEng_assign.ipynb +++ /dev/null @@ -1,403 +0,0 @@ -{ - "cells": [ - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "---\n", - "description: Invoke Package Group Eng to Assign Students in Groups\n", - "output-file: groupeng_assign.html\n", - "title: GroupEngAssign\n", - "\n", - "---\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/assign.py#L13){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### AssignGroup\n", - "\n", - "> AssignGroup (ghg:CanvasGroupy.github.GitHubGroup,\n", - "> cg:CanvasGroupy.canvas.CanvasGroup, groupeng_config='')\n", - "\n", - "Initializer for Assign Group\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| ghg | GitHubGroup | | authenticated GitHub object |\n", - "| cg | CanvasGroup | | authenticated canvas object |\n", - "| groupeng_config | str | | Directory for the GroupEng config yml file |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/assign.py#L13){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### AssignGroup\n", - "\n", - "> AssignGroup (ghg:CanvasGroupy.github.GitHubGroup,\n", - "> cg:CanvasGroupy.canvas.CanvasGroup, groupeng_config='')\n", - "\n", - "Initializer for Assign Group\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| ghg | GitHubGroup | | authenticated GitHub object |\n", - "| cg | CanvasGroup | | authenticated canvas object |\n", - "| groupeng_config | str | | Directory for the GroupEng config yml file |" - ] - }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(AssignGroup)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "collapsed": false - }, - "source": [ - "## API" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "ExecuteTime": { - "end_time": "2023-05-18T04:39:05.278308Z", - "start_time": "2023-05-18T04:39:05.206779Z" - }, - "collapsed": false, - "language": "python" - }, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/assign.py#L29){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### AssignGroup.assign_groups\n", - "\n", - "> AssignGroup.assign_groups (groupeng_config:str,\n", - "> assign_canvas_group=False,\n", - "> create_gh_repo=False, username_quiz_id=-1,\n", - "> in_group_category='', suffix='')\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| groupeng_config | str | | Directory for the GroupEng config yml file |\n", - "| assign_canvas_group | bool | False | directly assign canvas groups |\n", - "| create_gh_repo | bool | False | directly create GitHub repos |\n", - "| username_quiz_id | int | -1 | username quiz id from canvas course |\n", - "| in_group_category | str | | specify which group category the group belongs to |\n", - "| suffix | str | | suffix to the group name |\n", - "| **Returns** | **(, )** | | **Status and output directory of the compiled file.** |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/assign.py#L29){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### AssignGroup.assign_groups\n", - "\n", - "> AssignGroup.assign_groups (groupeng_config:str,\n", - "> assign_canvas_group=False,\n", - "> create_gh_repo=False, username_quiz_id=-1,\n", - "> in_group_category='', suffix='')\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| groupeng_config | str | | Directory for the GroupEng config yml file |\n", - "| assign_canvas_group | bool | False | directly assign canvas groups |\n", - "| create_gh_repo | bool | False | directly create GitHub repos |\n", - "| username_quiz_id | int | -1 | username quiz id from canvas course |\n", - "| in_group_category | str | | specify which group category the group belongs to |\n", - "| suffix | str | | suffix to the group name |\n", - "| **Returns** | **(, )** | | **Status and output directory of the compiled file.** |" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(AssignGroup.assign_groups)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": false, - "language": "python" - }, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/assign.py#L51){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### AssignGroup.create_canvas_group\n", - "\n", - "> AssignGroup.create_canvas_group (in_group_category='', suffix='')\n", - "\n", - "Create canvas groups based on the generated group configuration\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| in_group_category | str | | specify which group category the group belongs to |\n", - "| suffix | str | | suffix to the group name |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/assign.py#L51){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### AssignGroup.create_canvas_group\n", - "\n", - "> AssignGroup.create_canvas_group (in_group_category='', suffix='')\n", - "\n", - "Create canvas groups based on the generated group configuration\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| in_group_category | str | | specify which group category the group belongs to |\n", - "| suffix | str | | suffix to the group name |" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(AssignGroup.create_canvas_group)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": false, - "language": "python" - }, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/assign.py#L76){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### AssignGroup.create_github_group\n", - "\n", - "> AssignGroup.create_github_group (username_quiz_id:int)\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| username_quiz_id | int | username quiz id from canvas course |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/assign.py#L76){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### AssignGroup.create_github_group\n", - "\n", - "> AssignGroup.create_github_group (username_quiz_id:int)\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| username_quiz_id | int | username quiz id from canvas course |" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(AssignGroup.create_github_group)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Successfully Authenticated. GitHub account: scott-yj-yang \n", - "Target Organization Set: COGS118A \n", - "Authorization Successful!\n", - "Course Set: COGS 195 - Instructional Apprenticeship - Fleischer [SP23] \n", - "Getting List of Users... This might take a while...\n", - "Users Fetch Complete! The course has 5 students.\n" - ] - } - ], - "source": [ - "# Create authenticated objects\n", - "ghg = GitHubGroup(\"../../../credentials.json\",\n", - " \"COGS118A\"\n", - " )\n", - "cg = CanvasGroup(\"../../../credentials.json\",\n", - " course_id=45532,\n", - " )\n", - "# create assign group object\n", - "ag = AssignGroup(ghg, cg)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "data": { - "text/plain": "GroupCategory(_requester=, id=16456, name=Project 1, role=None, self_signup=None, group_limit=None, auto_leader=None, created_at=2023-05-17T20:38:56Z, created_at_date=2023-05-17 20:38:56+00:00, context_type=Course, course_id=45532, groups_count=0, unassigned_users_count=5, protected=False, allows_multiple_memberships=False, is_member=False)" - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# create a group category to hold students\n", - "cg.create_group_category({\"name\": \"Project 1\"})" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "['H', 'B', '-']\n", - "['B', 'H', '-']\n", - "['-', 'H', 'B']\n", - "['B', 'H', '-']\n", - "['B', '-', 'H']\n", - "['-', 'B', 'H']\n", - "[None, 3.9, 3.1]\n", - "[3.9, 3.1, None]\n", - "[3.4, 2.5, 2.1]\n", - "[3.9, None, 3.1]\n", - "[3.4, 2.1, 2.5]\n", - "[3.4, 2.1, 2.5]\n", - "In Group Set: Project 1,\n", - "Group Group1-SP23-Testing Created!\n", - "Member dol005 Joined group Group1-SP23-Testing\n", - "Member xiw013 Joined group Group1-SP23-Testing\n", - "In Group Set: Project 1,\n", - "Group Group2-SP23-Testing Created!\n", - "Member jiz088 Joined group Group2-SP23-Testing\n", - "Member jiz100 Joined group Group2-SP23-Testing\n", - "Member nmackler Joined group Group2-SP23-Testing\n", - "Quiz: GitHub Username fetch! \n", - "Generating Student Analaysis...\n", - "[====================] 100%\n", - "Report Generated!\n", - "The Question asked is 1399692: In plain text, what is your GitHub Username? Absolutely no typo, no extra space, no hyperlink please.. \n", - "Make sure this is the correct question where you asked student for their GitHub id.\n", - "If you need to change the index of columns, change the col_index argument of this call.\n", - "dol005's GitHub Username not found\n", - "xiw013's GitHub Username not found\n", - "Repo Group1-SP23-Testing Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group1-SP23-Testing.ipynb \n", - "File Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group1-SP23-Testing.ipynb \n", - "File Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group1-SP23-Testing.ipynb \n", - "Team Instructors_Sp23 added to Group1-SP23-Testing with permission admin \n", - "Group Repo: Group1-SP23-Testing successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group1-SP23-Testing\n", - "\n", - "jiz100's GitHub Username not found\n", - "jiz088's GitHub Username not found\n", - "Repo Group2-SP23-Testing Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group2-SP23-Testing.ipynb \n", - "File Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group2-SP23-Testing.ipynb \n", - "File Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group2-SP23-Testing.ipynb \n", - "Added Collaborator: nmackler to: Group2-SP23-Testing with permission: write \n", - "Team Instructors_Sp23 added to Group2-SP23-Testing with permission admin \n", - "Group Repo: Group2-SP23-Testing successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group2-SP23-Testing\n", - "\n" - ] - } - ], - "source": [ - "# assign, create both Canvas and GitHub Group in one call\n", - "status, out_dir = ag.assign_groups(\"../data/195_group_specification.groupeng\",\n", - " assign_canvas_group=True,\n", - " create_gh_repo=True,\n", - " username_quiz_id=139925,\n", - " in_group_category=\"Project 1\",\n", - " suffix=\"-SP23-Testing\"\n", - " )" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The false means that at least one requirement is not satisfied. We can take a look at the file that was generated." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "python3", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/_proc/api/02_github.ipynb b/_proc/api/02_github.ipynb deleted file mode 100644 index 769d070..0000000 --- a/_proc/api/02_github.ipynb +++ /dev/null @@ -1,1505 +0,0 @@ -{ - "cells": [ - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "---\n", - "description: Create GitHub group based on the provided group information\n", - "output-file: github.html\n", - "title: GitHub Group Creation\n", - "\n", - "---\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/github.py#L28){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup\n", - "\n", - "> GitHubGroup (credentials_fp='', org='', verbosity=1)\n", - "\n", - "Initialize self. See help(type(self)) for accurate signature.\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| credentials_fp | str | | the file path to the credential json |\n", - "| org | str | | the organization name |\n", - "| verbosity | int | 1 | Controls the verbosity: 0=silent, 1=print status |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/github.py#L28){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup\n", - "\n", - "> GitHubGroup (credentials_fp='', org='', verbosity=1)\n", - "\n", - "Initialize self. See help(type(self)) for accurate signature.\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| credentials_fp | str | | the file path to the credential json |\n", - "| org | str | | the organization name |\n", - "| verbosity | int | 1 | Controls the verbosity: 0=silent, 1=print status |" - ] - }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(GitHubGroup)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# GitHub Authentication\n", - "\n", - "View this [document](https://docs.google.com/document/d/1RvZnOX6nh0bXn6Zh4U-Yxazukuez5oGrtcP8mN-Roco/edit?usp=sharing) for how to set up your GitHub Personal Access Token. (TODO: be sure to specify scopes)\n", - "\n", - "Store the token information in a json file." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{\n", - " \"GitHub Token\": \"token\",\n", - " \"Canvas Token\": \"token\"\n", - "}\n", - "Successfully Authenticated. GitHub account: scott-yj-yang \n", - "Target Organization Set: COGS118A \n" - ] - } - ], - "source": [ - "credentials_fp = \"credentials.json\"\n", - "with open(credentials_fp, \"r\") as f:\n", - " # take a look at the token\n", - " print(f.read())\n", - "g = GitHubGroup(credentials_fp=credentials_fp, org=\"COGS118A\", verbosity=1)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Optionally, you can instansiate a GitHubGroup object and authenticate yourself by calling the following method." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/github.py#L43){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.auth_github\n", - "\n", - "> GitHubGroup.auth_github (credentials_fp:str)\n", - "\n", - "Authenticate GitHub account with the provided credentials\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| credentials_fp | str | the personal access token generated at GitHub Settings |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/github.py#L43){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.auth_github\n", - "\n", - "> GitHubGroup.auth_github (credentials_fp:str)\n", - "\n", - "Authenticate GitHub account with the provided credentials\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| credentials_fp | str | the personal access token generated at GitHub Settings |" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(GitHubGroup.auth_github)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Successfully Authenticated. GitHub account: scott-yj-yang \n" - ] - } - ], - "source": [ - "g = GitHubGroup(verbosity=1) # you can set verbosity to 1 to see the current progress\n", - "g.auth_github(credentials_fp)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# GitHub Organization Settings\n", - "\n", - "Usually, you want to create students repositories under a course GitHub organization. To set the target organization, you can call the following function." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/github.py#L56){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.set_org\n", - "\n", - "> GitHubGroup.set_org (org:str)\n", - "\n", - "Set the target organization for repo creation\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| org | str | the target organization name |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/github.py#L56){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.set_org\n", - "\n", - "> GitHubGroup.set_org (org:str)\n", - "\n", - "Set the target organization for repo creation\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| org | str | the target organization name |" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(GitHubGroup.set_org)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Target Organization Set: COGS118A \n" - ] - } - ], - "source": [ - "g.set_org(\"COGS118A\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Create GitHub Group in one Call" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/github.py#L258){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.create_group_repo\n", - "\n", - "> GitHubGroup.create_group_repo (repo_name:str,\n", - "> collaborators:[],\n", - "> permission:str, rename_files={},\n", - "> repo_template='', private=True,\n", - "> description='', team_slug='',\n", - "> team_permission='', feedback_dir=False,\n", - "> feedback_template_fp='')\n", - "\n", - "Create a Group Repository\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| repo_name | str | | group repository name |\n", - "| collaborators | [] | | list of collaborators GitHub id |\n", - "| permission | str | | the permission of collaborators. `pull`, `push` or `admin` |\n", - "| rename_files | dict | {} | dictionary of files renames {:} |\n", - "| repo_template | str | | If empty string, an empty repo will be created. Put in the format of \"/\" |\n", - "| private | bool | True | visibility of the created repository |\n", - "| description | str | | description for the GitHub repository |\n", - "| team_slug | str | | team slug, add to this repo |\n", - "| team_permission | str | | team permission to this repository `pull`, `push` or `admin` |\n", - "| feedback_dir | bool | False | whether to create a feedback directory for each repository created |\n", - "| feedback_template_fp | str | | the directory of the feedback template |\n", - "| **Returns** | **Repository** | | **created repository** |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/github.py#L258){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.create_group_repo\n", - "\n", - "> GitHubGroup.create_group_repo (repo_name:str,\n", - "> collaborators:[],\n", - "> permission:str, rename_files={},\n", - "> repo_template='', private=True,\n", - "> description='', team_slug='',\n", - "> team_permission='', feedback_dir=False,\n", - "> feedback_template_fp='')\n", - "\n", - "Create a Group Repository\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| repo_name | str | | group repository name |\n", - "| collaborators | [] | | list of collaborators GitHub id |\n", - "| permission | str | | the permission of collaborators. `pull`, `push` or `admin` |\n", - "| rename_files | dict | {} | dictionary of files renames {:} |\n", - "| repo_template | str | | If empty string, an empty repo will be created. Put in the format of \"/\" |\n", - "| private | bool | True | visibility of the created repository |\n", - "| description | str | | description for the GitHub repository |\n", - "| team_slug | str | | team slug, add to this repo |\n", - "| team_permission | str | | team permission to this repository `pull`, `push` or `admin` |\n", - "| feedback_dir | bool | False | whether to create a feedback directory for each repository created |\n", - "| feedback_template_fp | str | | the directory of the feedback template |\n", - "| **Returns** | **Repository** | | **created repository** |" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(GitHubGroup.create_group_repo)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Repo API_test_repo Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_group001.ipynb \n", - "File Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_group001.ipynb \n", - "File Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_group001.ipynb \n", - "Added Collaborator: jasongfleischer to: API_test_repo with permission: admin \n", - "Team Instructors_Sp23 added to API_test_repo with permission admin \n", - "Group Repo: API_test_repo successfuly created!\n" - ] - } - ], - "source": [ - "repo = g.create_group_repo(\n", - " repo_name=\"API_test_repo\",\n", - " collaborators=[\"jasongfleischer\"],\n", - " permission=\"admin\",\n", - " repo_template=\"COGS118A/group_template\",\n", - " rename_files={\n", - " \"Checkpoint_groupXXX.ipynb\": \"Checkpoint_group001.ipynb\",\n", - " \"FinalProject_groupXXX.ipynb\": \"FinalProject_group001.ipynb\",\n", - " \"Proposal_groupXXX.ipynb\": \"Proposal_group001.ipynb\"\n", - " },\n", - " private=False,\n", - " description=\"Test Creation of Group Repo for COGS118A final project group\",\n", - " team_slug=\"Instructors_Sp23\",\n", - " team_permission=\"admin\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Lower Level Methods\n", - "\n", - "Belows are the components that faciliate the [`GitHubGroup.create_group_repo`](https://FleischerResearchLab.github.io/CanvasGroupy/api/github.html#githubgroup.create_group_repo). If errors were prompted during group creation scripts, or simply you want more flexibility, you can call those components individually.\n", - "\n", - "## Create GitHub Repository\n", - "\n", - "_Note:_ `GtiHubGroup.create_group_repo` call this method to create a GitHub repository\n", - "\n", - "`personal_account` argument controls the location of the repository creation. If set to `False` (default), it will create repository in the target organization. If set to `True`, the new repository will be created in the personal GitHub account." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/github.py#L64){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.create_repo\n", - "\n", - "> GitHubGroup.create_repo (repo_name:str, repo_template='', private=True,\n", - "> description='', personal_account=False)\n", - "\n", - "Create a repository, either blank, or from a template\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| repo_name | str | | repository name |\n", - "| repo_template | str | | template repository that new repo will use. If empty string, an empty repo will be created. Put in the format of \"/\" |\n", - "| private | bool | True | visibility of the created repository |\n", - "| description | str | | description for the GitHub repository |\n", - "| personal_account | bool | False | create repos in personal GitHub account |\n", - "| **Returns** | **Repository** | | |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/github.py#L64){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.create_repo\n", - "\n", - "> GitHubGroup.create_repo (repo_name:str, repo_template='', private=True,\n", - "> description='', personal_account=False)\n", - "\n", - "Create a repository, either blank, or from a template\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| repo_name | str | | repository name |\n", - "| repo_template | str | | template repository that new repo will use. If empty string, an empty repo will be created. Put in the format of \"/\" |\n", - "| private | bool | True | visibility of the created repository |\n", - "| description | str | | description for the GitHub repository |\n", - "| personal_account | bool | False | create repos in personal GitHub account |\n", - "| **Returns** | **Repository** | | |" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(GitHubGroup.create_repo)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "_Note:_ `GtiHubGroup.create_group_repo` call this method to create a GitHub repository\n", - "\n", - "`personal_account` argument controls the location of the repository creation. If set to `False` (default), it will create repository in the target organization. If set to `True`, the new repository will be created in the personal GitHub account." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Repository(full_name=\"COGS118A/test-repo-organizational\")\n" - ] - } - ], - "source": [ - "# create a repo under the org\n", - "repo = g.create_repo(\n", - " \"test-repo-organizational\",\n", - " private=True\n", - ")\n", - "print(repo)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As you can see from the full name `COGS118A/test-repo`, it is created under the organization of `COGS118A`.\n", - "\n", - "Alternatively, I can also create a new repository under my personal account." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Repository(full_name=\"scott-yj-yang/test-repo-personal\")\n" - ] - } - ], - "source": [ - "# create a repo under the my personal account\n", - "repo = g.create_repo(\n", - " \"test-repo-personal\",\n", - " private=True,\n", - " personal_account=True\n", - ")\n", - "print(repo)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Repository(full_name=\"scott-yj-yang/test-repo-personal\")\n" - ] - } - ], - "source": [ - "# create a repo under the my personal account\n", - "repo = g.create_repo(\n", - " repo_name=\"test-repo-personal\",\n", - " private=True,\n", - " personal_account=True\n", - ")\n", - "print(repo)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create Repository from Template\n", - "\n", - "You can also create a repository with a template repository. To do that, specify the full name of the template repository to the `repo_template` parameter. From the output, we can see that the repository `test-repo-from-template` is created with the template files. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Repository(full_name=\"COGS118A/test-repo-from-template\")\n", - "\n", - "This Repository contains... \n", - "\n", - "[ContentFile(path=\".gitignore\"),\n", - " ContentFile(path=\"Checkpoint_groupXXX.ipynb\"),\n", - " ContentFile(path=\"FinalProject_groupXXX.ipynb\"),\n", - " ContentFile(path=\"Proposal_groupXXX.ipynb\"),\n", - " ContentFile(path=\"README.md\")]\n" - ] - } - ], - "source": [ - "# create a repo from a template\n", - "repo = g.create_repo(\n", - " repo_name=\"test-repo-from-template\",\n", - " repo_template=\"COGS118A/group_template\",\n", - " private=True\n", - ")\n", - "print(repo)\n", - "\n", - "# wait 3 sec for repository creation.\n", - "time.sleep(3)\n", - "\n", - "print(\"\\nThis Repository contains... \\n\")\n", - "pprint(repo.get_contents(\".\"))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Rename Files in the Repository\n", - "\n", - "Usually, the template file names are generics and is not specific to a group. Under the context of group project, we want to rename each notebook files with thier group numbers. For example, for Group 1, we want to rename the file `Checkpoint_groupXXX.ipynb` to `Checkpoint_group001.ipynb`. To do that, we can use the following method." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/github.py#L116){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.rename_files\n", - "\n", - "> GitHubGroup.rename_files (repo:github.Repository.Repository,\n", - "> og_filename:str, new_filename:str)\n", - "\n", - "Rename the file by delete the old file and commit the new file\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| repo | Repository | the repository that we want to rename file |\n", - "| og_filename | str | old file name |\n", - "| new_filename | str | new file name |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/github.py#L116){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.rename_files\n", - "\n", - "> GitHubGroup.rename_files (repo:github.Repository.Repository,\n", - "> og_filename:str, new_filename:str)\n", - "\n", - "Rename the file by delete the old file and commit the new file\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| repo | Repository | the repository that we want to rename file |\n", - "| og_filename | str | old file name |\n", - "| new_filename | str | new file name |" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(GitHubGroup.rename_files)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "_Note:_ This method simply delete the old files and create new files with the updated file name. Therefore, 2 commits for each file is expected. (1 for delete, 1 for re-upload). For example, if I want to rename 5 files, I will have 10 commits need to do in total." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "File Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_group001.ipynb \n", - "\n", - "This Repository contains... \n", - "\n", - "[ContentFile(path=\".gitignore\"),\n", - " ContentFile(path=\"Checkpoint_group001.ipynb\"),\n", - " ContentFile(path=\"FinalProject_groupXXX.ipynb\"),\n", - " ContentFile(path=\"Proposal_groupXXX.ipynb\"),\n", - " ContentFile(path=\"README.md\")]\n" - ] - } - ], - "source": [ - "g.rename_files(\n", - " repo=repo,\n", - " og_filename=\"Checkpoint_groupXXX.ipynb\",\n", - " new_filename=\"Checkpoint_group001.ipynb\"\n", - ")\n", - "# take a look at new files\n", - "print(\"\\nThis Repository contains... \\n\")\n", - "pprint(repo.get_contents(\".\"))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Notice that the files were renamed as expected." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Collaborators and Teams Access Repository\n", - "\n", - "Once the repository was created, we need to give the student team members proper permission to write to the repository and instructional team to be the admin of the repository. Those two functionalities are achieve by the following two methods." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/github.py#L130){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.add_collaborator\n", - "\n", - "> GitHubGroup.add_collaborator (repo:github.Repository.Repository,\n", - "> collaborator:str, permission:str)\n", - "\n", - "Add collaborator to the repository with specified permission\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| repo | Repository | target repository |\n", - "| collaborator | str | GitHub username of the collaborator |\n", - "| permission | str | `pull`, `push` or `admin` |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/github.py#L130){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.add_collaborator\n", - "\n", - "> GitHubGroup.add_collaborator (repo:github.Repository.Repository,\n", - "> collaborator:str, permission:str)\n", - "\n", - "Add collaborator to the repository with specified permission\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| repo | Repository | target repository |\n", - "| collaborator | str | GitHub username of the collaborator |\n", - "| permission | str | `pull`, `push` or `admin` |" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(GitHubGroup.add_collaborator)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added Collaborator: Andrina-iris to: test-repo-from-template with permission: write \n" - ] - } - ], - "source": [ - "# add collaborator to the repository with push permission\n", - "g.add_collaborator(\n", - " repo=repo,\n", - " collaborator=\"Andrina-iris\",\n", - " permission=\"write\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In almost every quarter, at least 10 students forgot to accept the invitation to join the group repository. Historially, our instructional team handle those student's GitHub account on a case-by-case basis. However, with this module, it is possible to resent all pending and expired invitations to those students with one call." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/github.py#L152){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.resend_invitations\n", - "\n", - "> GitHubGroup.resend_invitations (repo:github.Repository.Repository)\n", - "\n", - "Resent Invitation to invitee who did not accept the invitation\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| repo | Repository | target repository |\n", - "| **Returns** | **[]** | **list of re-invited user** |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/github.py#L152){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.resend_invitations\n", - "\n", - "> GitHubGroup.resend_invitations (repo:github.Repository.Repository)\n", - "\n", - "Resent Invitation to invitee who did not accept the invitation\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| repo | Repository | target repository |\n", - "| **Returns** | **[]** | **list of re-invited user** |" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(GitHubGroup.resend_invitations)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Students will receive an email from GitHub with the freshly made, unexpired invitation to their group repository." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The list of pending invitation:\n", - "[NamedUser(login=\"Andrina-iris\")]\n", - " Andrina-iris Invite Revoked Andrina-iris \n", - "Added Collaborator: Andrina-iris to: test-repo-from-template with permission: write \n", - " Invite Resent to Andrina-iris \n" - ] - }, - { - "data": { - "text/plain": [ - "[NamedUser(login=\"Andrina-iris\")]" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "g.resend_invitations(repo)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Additionally, course staffs should be in a team in the GitHub Organization in order to manage student repositories." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/github.py#L185){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.add_team\n", - "\n", - "> GitHubGroup.add_team (repo:github.Repository.Repository, team_slug:str,\n", - "> permission:str)\n", - "\n", - "Add team to the repository with specified permission\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| repo | Repository | target repository |\n", - "| team_slug | str | team slug (name) |\n", - "| permission | str | `pull`, `push` or `admin` |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/github.py#L185){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.add_team\n", - "\n", - "> GitHubGroup.add_team (repo:github.Repository.Repository, team_slug:str,\n", - "> permission:str)\n", - "\n", - "Add team to the repository with specified permission\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| repo | Repository | target repository |\n", - "| team_slug | str | team slug (name) |\n", - "| permission | str | `pull`, `push` or `admin` |" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(GitHubGroup.add_team)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Team Instructors_Sp23 added to test-repo-from-template with permission admin \n" - ] - } - ], - "source": [ - "g.add_team(\n", - " repo=repo,\n", - " team_slug=\"Instructors_Sp23\",\n", - " permission=\"admin\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If you have all the students repositories under the same team, you can use the following method to resent all pending invitations from all the repositories under that team." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/github.py#L170){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.resent_invitations_team_repos\n", - "\n", - "> GitHubGroup.resent_invitations_team_repos (team_slug:str)\n", - "\n", - "For all repository under that team, Resent invitation to invitee who did not accept the invitation\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| team_slug | str | team slug (name) under the org |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/github.py#L170){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.resent_invitations_team_repos\n", - "\n", - "> GitHubGroup.resent_invitations_team_repos (team_slug:str)\n", - "\n", - "For all repository under that team, Resent invitation to invitee who did not accept the invitation\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| team_slug | str | team slug (name) under the org |" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(GitHubGroup.resent_invitations_team_repos)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Repository AssignmentNotebooksSource_SP23 :\n", - "The list of pending invitation:\n", - "[]\n", - "Repository AssignmentNotebooks_SP23 :\n", - "The list of pending invitation:\n", - "[]\n", - "Repository DiscussionSectionNotebooks :\n", - "The list of pending invitation:\n", - "[]\n", - "Repository Dockerfiles :\n", - "The list of pending invitation:\n", - "[]\n", - "Repository Lectures :\n", - "The list of pending invitation:\n", - "[]\n", - "Repository Notebooks :\n", - "The list of pending invitation:\n", - "[]\n", - "Repository test-repo-from-template :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"Andrina-iris\")]\n", - " Andrina-iris Invite Revoked Andrina-iris \n", - "Added Collaborator: Andrina-iris to: test-repo-from-template with permission: write \n", - " Invite Resent to Andrina-iris \n" - ] - } - ], - "source": [ - "g.resent_invitations_team_repos(team_slug=\"Instructors_Sp23\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Project Feedback and GitHub Issues\n", - "\n", - "Thanks for the group nature of the created repository, we can also use the created GitHub repository to create group project feedback to students via GitHub issues." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can create a directory under the instructors' computer. Each directory will have a folder for each group repository with the template file. The template files are usually the rubrics for the project grading.\n", - "\n", - "TODO: add file superlink to the repo to see the examples." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/github.py#L199){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.create_feedback_dir\n", - "\n", - "> GitHubGroup.create_feedback_dir (repo:github.Repository.Repository,\n", - "> template_fp:str, destination='feedback')\n", - "\n", - "Create feedback directory on local machine\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| repo | Repository | | target repository |\n", - "| template_fp | str | | |\n", - "| destination | str | feedback | directory path of the template file. |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/github.py#L199){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.create_feedback_dir\n", - "\n", - "> GitHubGroup.create_feedback_dir (repo:github.Repository.Repository,\n", - "> template_fp:str, destination='feedback')\n", - "\n", - "Create feedback directory on local machine\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| repo | Repository | | target repository |\n", - "| template_fp | str | | |\n", - "| destination | str | feedback | directory path of the template file. |" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(GitHubGroup.create_feedback_dir)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "File checkpoint_feedback.md created at feedback/test-repo-from-template\n", - "File proposal_feedback.md created at feedback/test-repo-from-template\n", - "File final_project_feedback.md created at feedback/test-repo-from-template\n" - ] - }, - { - "data": { - "text/plain": [ - "['checkpoint_feedback.md', 'proposal_feedback.md', 'final_project_feedback.md']" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "g.create_feedback_dir(repo, \"feedback_template\")\n", - "# take a look at the generated tempalte files.\n", - "os.listdir(f\"feedback/{repo.name}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "After created the project feedback, instructional team can modify the markdown rubrics and provide feedback to students via GitHub issue." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/github.py#L219){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.create_issue\n", - "\n", - "> GitHubGroup.create_issue (repo:github.Repository.Repository, title:str,\n", - "> content:str)\n", - "\n", - "Create GitHub issue to the target repository\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| repo | Repository | target repository |\n", - "| title | str | title of the issue, |\n", - "| content | str | content of the issue |\n", - "| **Returns** | **Issue** | **open issue** |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/github.py#L219){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.create_issue\n", - "\n", - "> GitHubGroup.create_issue (repo:github.Repository.Repository, title:str,\n", - "> content:str)\n", - "\n", - "Create GitHub issue to the target repository\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| repo | Repository | target repository |\n", - "| title | str | title of the issue, |\n", - "| content | str | content of the issue |\n", - "| **Returns** | **Issue** | **open issue** |" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(GitHubGroup.create_issue)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "In the repo: test-repo-from-template,\n", - "Issue Test Issue Created!\n" - ] - }, - { - "data": { - "text/plain": [ - "Issue(title=\"Test Issue\", number=1)" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# create a test issue\n", - "issue = g.create_issue(repo, \"Test Issue\", \"This is just a test issue.\")\n", - "issue" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Alternatively, you can create issue from markdown files, where it contains all the comments and rubrics for this project. The first line of the markdown file will be the title of the github issue." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/github.py#L231){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.create_issue_from_md\n", - "\n", - "> GitHubGroup.create_issue_from_md (repo:github.Repository.Repository,\n", - "> md_fp:str)\n", - "\n", - "Create GitHub issue from markdown file.\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| repo | Repository | target repository, |\n", - "| md_fp | str | file path of the feedback markdown file |\n", - "| **Returns** | **Issue** | **open issue** |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/github.py#L231){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.create_issue_from_md\n", - "\n", - "> GitHubGroup.create_issue_from_md (repo:github.Repository.Repository,\n", - "> md_fp:str)\n", - "\n", - "Create GitHub issue from markdown file.\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| repo | Repository | target repository, |\n", - "| md_fp | str | file path of the feedback markdown file |\n", - "| **Returns** | **Issue** | **open issue** |" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(GitHubGroup.create_issue_from_md)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "In the repo: test-repo-from-template,\n", - "Issue Project Proposal Feedback Created!\n" - ] - }, - { - "data": { - "text/plain": [ - "Issue(title=\" Project Proposal Feedback\", number=2)" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "issue = g.create_issue_from_md(repo, \"feedback_template/proposal_feedback.md\")\n", - "issue" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Release Feedback in Batch\n", - "\n", - "During projct grading, we will handle numerous groups at once. Once the instructor team finish modifying the markdown file for each group, we can release feedback to each of the project repository, as long as they have the same file name. For example, we have finish grading the final project, in the file name of `feedback//final_project_feedback.md`, and they are ready to be publish, we can call the following function to create issue in batch." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/github.py#L243){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.release_feedback\n", - "\n", - "> GitHubGroup.release_feedback (md_filename:str, feedback_dir='feedback')\n", - "\n", - "Release feedback via GitHub issue from all the feedbacks in the feedback directory\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| md_filename | str | | feedback markdown file name |\n", - "| feedback_dir | str | feedback | feedback directory contains the markdown files |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/github.py#L243){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.release_feedback\n", - "\n", - "> GitHubGroup.release_feedback (md_filename:str, feedback_dir='feedback')\n", - "\n", - "Release feedback via GitHub issue from all the feedbacks in the feedback directory\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| md_filename | str | | feedback markdown file name |\n", - "| feedback_dir | str | feedback | feedback directory contains the markdown files |" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(GitHubGroup.release_feedback)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "In the repo: test-repo-from-template,\n", - "Issue Final Project Feedback Created!\n" - ] - } - ], - "source": [ - "g.release_feedback(\"final_project_feedback.md\")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "python3", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/_proc/api/03_canvas.ipynb b/_proc/api/03_canvas.ipynb deleted file mode 100644 index df1348d..0000000 --- a/_proc/api/03_canvas.ipynb +++ /dev/null @@ -1,631 +0,0 @@ -{ - "cells": [ - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "---\n", - "description: Create canvas group via Canvas API\n", - "output-file: canvas.html\n", - "title: Canvas Group Creation\n", - "\n", - "---\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L31){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup\n", - "\n", - "> CanvasGroup (credentials_fp='', API_URL='https://canvas.ucsd.edu',\n", - "> course_id='', group_category='', verbosity=1)\n", - "\n", - "Initialize Canvas Group within a Group Set and its appropriate memberships\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| credentials_fp | str | | credential file path. [Template of the credentials.json](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/nbs/credentials.json) |\n", - "| API_URL | str | https://canvas.ucsd.edu | the domain name of canvas |\n", - "| course_id | str | | Course ID, can be found in the course url |\n", - "| group_category | str | | target group category (set) of interests |\n", - "| verbosity | int | 1 | Controls the verbosity: 0 = Silent, 1 = print all messages |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L31){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup\n", - "\n", - "> CanvasGroup (credentials_fp='', API_URL='https://canvas.ucsd.edu',\n", - "> course_id='', group_category='', verbosity=1)\n", - "\n", - "Initialize Canvas Group within a Group Set and its appropriate memberships\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| credentials_fp | str | | credential file path. [Template of the credentials.json](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/nbs/credentials.json) |\n", - "| API_URL | str | https://canvas.ucsd.edu | the domain name of canvas |\n", - "| course_id | str | | Course ID, can be found in the course url |\n", - "| group_category | str | | target group category (set) of interests |\n", - "| verbosity | int | 1 | Controls the verbosity: 0 = Silent, 1 = print all messages |" - ] - }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(CanvasGroup)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Lower Level Methods" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Alternatively, you can manually set them after you created the [`CanvasGroup`](https://FleischerResearchLab.github.io/CanvasGroupy/api/canvas.html#canvasgroup) object" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L67){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.auth_canvas\n", - "\n", - "> CanvasGroup.auth_canvas (credentials_fp:str)\n", - "\n", - "Authorize the canvas module with API_KEY\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| credentials_fp | str | the Authenticator key generated from canvas |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L67){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.auth_canvas\n", - "\n", - "> CanvasGroup.auth_canvas (credentials_fp:str)\n", - "\n", - "Authorize the canvas module with API_KEY\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| credentials_fp | str | the Authenticator key generated from canvas |" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(CanvasGroup.auth_canvas)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L81){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.set_course\n", - "\n", - "> CanvasGroup.set_course (course_id:int)\n", - "\n", - "Set the target course by the course ID\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| course_id | int | the course id of the target course |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L81){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.set_course\n", - "\n", - "> CanvasGroup.set_course (course_id:int)\n", - "\n", - "Set the target course by the course ID\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| course_id | int | the course id of the target course |" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(CanvasGroup.set_course)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The following tutorial and examples demonstrates how to create and set a Group Category within a course context." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create / Set Target Group Category (Set)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L181){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.get_group_categories\n", - "\n", - "> CanvasGroup.get_group_categories ()\n", - "\n", - "Grab all existing group category (group set) in this course" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L181){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.get_group_categories\n", - "\n", - "> CanvasGroup.get_group_categories ()\n", - "\n", - "Grab all existing group category (group set) in this course" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(CanvasGroup.get_group_categories)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "data": { - "text/plain": [ - "['Final Project', 'Student Groups', 'Test']" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# list all current group category\n", - "list(cg.get_group_categories().keys())" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L187){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.create_group_category\n", - "\n", - "> CanvasGroup.create_group_category (params:dict)\n", - "\n", - "Create group category (group set) in this course\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| params | dict | the parameter of canvas group category API @ [this link](https://canvas.instructure.com/doc/api/group_categories.html#method.group_categories.create) |\n", - "| **Returns** | **GroupCategory** | **the generated group category object** |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L187){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.create_group_category\n", - "\n", - "> CanvasGroup.create_group_category (params:dict)\n", - "\n", - "Create group category (group set) in this course\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| params | dict | the parameter of canvas group category API @ [this link](https://canvas.instructure.com/doc/api/group_categories.html#method.group_categories.create) |\n", - "| **Returns** | **GroupCategory** | **the generated group category object** |" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(CanvasGroup.create_group_category)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [], - "source": [ - "params = {\n", - " \"name\": \"TEST-GroupProject\",\n", - " \"group_limit\": 5\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [], - "source": [ - "# create a new category\n", - "group_category = cg.create_group_category(params)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "data": { - "text/plain": [ - "['Final Project', 'Student Groups', 'Test', 'TEST-GroupProject']" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Check whether we successfully create a new group\n", - "list(cg.get_group_categories().keys())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When a group category is already created, we cannot create another group with the same name. To switch the group category destination of group creation, use the `set_group_category` methods." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L150){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.set_group_category\n", - "\n", - "> CanvasGroup.set_group_category (category_name:str)\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| category_name | str | the target group category |\n", - "| **Returns** | **GroupCategory** | **target group category object** |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L150){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.set_group_category\n", - "\n", - "> CanvasGroup.set_group_category (category_name:str)\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| category_name | str | the target group category |\n", - "| **Returns** | **GroupCategory** | **target group category object** |" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(CanvasGroup.set_group_category)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [], - "source": [ - "group_category = cg.set_group_category(\"TEST-GroupProject\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create a Group Inside the Target Group Category" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L194){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.create_group\n", - "\n", - "> CanvasGroup.create_group (params:dict)\n", - "\n", - "Create canvas group under the target group category\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| params | dict | the parameter of canvas group create API at [this link](https://canvas.instructure.com/doc/api/groups.html#method.groups.create) |\n", - "| **Returns** | **Group** | **the generated target group object** |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L194){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.create_group\n", - "\n", - "> CanvasGroup.create_group (params:dict)\n", - "\n", - "Create canvas group under the target group category\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| params | dict | the parameter of canvas group create API at [this link](https://canvas.instructure.com/doc/api/groups.html#method.groups.create) |\n", - "| **Returns** | **Group** | **the generated target group object** |" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(CanvasGroup.create_group)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "In Group Set: TEST-GroupProject,\n", - "Group TEST-GROUP1 Created!\n", - "TEST-GROUP1 (122854)\n" - ] - } - ], - "source": [ - "params = {\n", - " \"name\": \"TEST-GROUP1\",\n", - " \"join_level\": \"invitation_only\"\n", - "}\n", - "group1 = cg.create_group(params)\n", - "print(group1)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Assign Student to the Group" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L206){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.join_canvas_group\n", - "\n", - "> CanvasGroup.join_canvas_group (group:canvasapi.group.Group,\n", - "> group_members:[])\n", - "\n", - "Add membership access of each group member into the group\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| group | Group | the group that students will join |\n", - "| group_members | [] | list of group member's SIS Login (email prefix, before the @.) |\n", - "| **Returns** | **[]** | **list of unsuccessful join** |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L206){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.join_canvas_group\n", - "\n", - "> CanvasGroup.join_canvas_group (group:canvasapi.group.Group,\n", - "> group_members:[])\n", - "\n", - "Add membership access of each group member into the group\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| group | Group | the group that students will join |\n", - "| group_members | [] | list of group member's SIS Login (email prefix, before the @.) |\n", - "| **Returns** | **[]** | **list of unsuccessful join** |" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(CanvasGroup.join_canvas_group)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "member1 = \"email\"\n", - "\n", - "cg.join_canvas_group(group1, [member1])" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "python3", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/_proc/api/04_project_grading.ipynb b/_proc/api/04_project_grading.ipynb deleted file mode 100644 index 6c22dc0..0000000 --- a/_proc/api/04_project_grading.ipynb +++ /dev/null @@ -1,376 +0,0 @@ -{ - "cells": [ - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "---\n", - "description: Manage grading rubrics and grade posting on the groups grade.\n", - "output-file: project_grading.html\n", - "title: Project Grading\n", - "\n", - "---\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/grading.py#L26){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### Grading\n", - "\n", - "> Grading (ghg=None, cg=None)\n", - "\n", - "Initialize self. See help(type(self)) for accurate signature.\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| ghg | NoneType | None | authenticated GitHub object |\n", - "| cg | NoneType | None | authenticated canvas object |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/grading.py#L26){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### Grading\n", - "\n", - "> Grading (ghg=None, cg=None)\n", - "\n", - "Initialize self. See help(type(self)) for accurate signature.\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| ghg | NoneType | None | authenticated GitHub object |\n", - "| cg | NoneType | None | authenticated canvas object |" - ] - }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(Grading)" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": false, - "language": "python" - }, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "### Grading.create_issue_from_md\n", - "\n", - "> Grading.create_issue_from_md (repo:github.Repository.Repository,\n", - "> md_fp:str)\n", - "\n", - "Create GitHub issue from markdown file.\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| repo | Repository | target repository to create issue |\n", - "| md_fp | str | file path of the feedback markdown file |\n", - "| **Returns** | **Issue** | **open issue** |" - ], - "text/plain": [ - "---\n", - "\n", - "### Grading.create_issue_from_md\n", - "\n", - "> Grading.create_issue_from_md (repo:github.Repository.Repository,\n", - "> md_fp:str)\n", - "\n", - "Create GitHub issue from markdown file.\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| repo | Repository | target repository to create issue |\n", - "| md_fp | str | file path of the feedback markdown file |\n", - "| **Returns** | **Issue** | **open issue** |" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(Grading.create_issue_from_md)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": false, - "language": "python" - }, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/grading.py#L41){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### Grading.fetch_issue\n", - "\n", - "> Grading.fetch_issue (repo:github.Repository.Repository, component:str)\n", - "\n", - "Fetch the issue on GitHub repo and choose related one\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| repo | Repository | target repository to fetch issue |\n", - "| component | str | the component of the project grading, let it be proposal/checkpoint/final. Need to match the issue's title |\n", - "| **Returns** | **Issue** | |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/grading.py#L41){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### Grading.fetch_issue\n", - "\n", - "> Grading.fetch_issue (repo:github.Repository.Repository, component:str)\n", - "\n", - "Fetch the issue on GitHub repo and choose related one\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| repo | Repository | target repository to fetch issue |\n", - "| component | str | the component of the project grading, let it be proposal/checkpoint/final. Need to match the issue's title |\n", - "| **Returns** | **Issue** | |" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(Grading.fetch_issue)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": false, - "language": "python" - }, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/grading.py#L51){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### Grading.parse_score_from_issue\n", - "\n", - "> Grading.parse_score_from_issue (repo:github.Repository.Repository,\n", - "> component:str)\n", - "\n", - "parse score from the template issue\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| repo | Repository | target repository to create issue |\n", - "| component | str | the component of the project grading, let it be proposal/checkpoint/final. Need to match the issue's title |\n", - "| **Returns** | **int** | **the fetched score of that component** |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/grading.py#L51){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### Grading.parse_score_from_issue\n", - "\n", - "> Grading.parse_score_from_issue (repo:github.Repository.Repository,\n", - "> component:str)\n", - "\n", - "parse score from the template issue\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| repo | Repository | target repository to create issue |\n", - "| component | str | the component of the project grading, let it be proposal/checkpoint/final. Need to match the issue's title |\n", - "| **Returns** | **int** | **the fetched score of that component** |" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(Grading.parse_score_from_issue)" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "collapsed": false, - "language": "python" - }, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/grading.py#L66){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### Grading.update_canvas_score\n", - "\n", - "> Grading.update_canvas_score (group_name:str, assignment_id, score:float,\n", - "> post=False)\n", - "\n", - "Post score to canvas\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| group_name | str | | target group name on canvas group |\n", - "| assignment_id | | | assignment id of the related component |\n", - "| score | float | | score of that component |\n", - "| post | bool | False | whether to post score via api. for testing purposes |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/grading.py#L66){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### Grading.update_canvas_score\n", - "\n", - "> Grading.update_canvas_score (group_name:str, assignment_id, score:float,\n", - "> post=False)\n", - "\n", - "Post score to canvas\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| group_name | str | | target group name on canvas group |\n", - "| assignment_id | | | assignment id of the related component |\n", - "| score | float | | score of that component |\n", - "| post | bool | False | whether to post score via api. for testing purposes |" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(Grading.update_canvas_score)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "collapsed": false, - "language": "python" - }, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/grading.py#L87){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### Grading.grade_project\n", - "\n", - "> Grading.grade_project (repo:github.Repository.Repository, component:str,\n", - "> assignment_id:int, canvas_group_name=None,\n", - "> post=False)\n", - "\n", - "grade github project components\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| repo | Repository | | target repository to grade |\n", - "| component | str | | the component of the project grading, let it be proposal/checkpoint/final. Need to match the issue's title |\n", - "| assignment_id | int | | assignment id that link to that component of the project |\n", - "| canvas_group_name | NoneType | None | mapping from GitHub repo name to Group name. If not specified, the repository name will be used. |\n", - "| post | bool | False | whether to post score via api. For testing purposes |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/grading.py#L87){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### Grading.grade_project\n", - "\n", - "> Grading.grade_project (repo:github.Repository.Repository, component:str,\n", - "> assignment_id:int, canvas_group_name=None,\n", - "> post=False)\n", - "\n", - "grade github project components\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| repo | Repository | | target repository to grade |\n", - "| component | str | | the component of the project grading, let it be proposal/checkpoint/final. Need to match the issue's title |\n", - "| assignment_id | int | | assignment id that link to that component of the project |\n", - "| canvas_group_name | NoneType | None | mapping from GitHub repo name to Group name. If not specified, the repository name will be used. |\n", - "| post | bool | False | whether to post score via api. For testing purposes |" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| echo: false\n", - "#| output: asis\n", - "show_doc(Grading.grade_project)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "python3", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/_proc/api/github_username.csv b/_proc/api/github_username.csv deleted file mode 100644 index 997e626..0000000 --- a/_proc/api/github_username.csv +++ /dev/null @@ -1,2 +0,0 @@ -email,GitHub Username -nmackler,nmackler diff --git a/_proc/api/index.qmd b/_proc/api/index.qmd deleted file mode 100644 index 267272d..0000000 --- a/_proc/api/index.qmd +++ /dev/null @@ -1,11 +0,0 @@ ---- -order: 2 -title: api -listing: - fields: [title, description] - type: table - sort-ui: false - filter-ui: false ---- - -This section contains API details for each of CanvasGroupy python submodules. This reference documentation is mainly useful for people looking to customise or build on top of nbdev, or wanting detailed information about how this module works. diff --git a/_proc/credentials.json b/_proc/credentials.json deleted file mode 100644 index f8644d0..0000000 --- a/_proc/credentials.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "GitHub Token": "token", - "Canvas Token": "token" -} \ No newline at end of file diff --git a/_proc/data/195_class.csv b/_proc/data/195_class.csv deleted file mode 100644 index 0e45446..0000000 --- a/_proc/data/195_class.csv +++ /dev/null @@ -1,6 +0,0 @@ -ID,GPA,Gender,Ethnicity,Major,Math Skills,Programming Skills,Statistic Skills -dol005,3.9,M,-,COGS,5,5,3 -nmackler,3.4,F,B,COGS,2,3,2 -xiw013,3.1,M,-,DSC,3,1,5 -jiz100,2.5,M,H,MATH,2,2,1 -jiz088,2.1,F,-,DSC,4,4,4 \ No newline at end of file diff --git a/_proc/data/195_group_specification.groupeng b/_proc/data/195_group_specification.groupeng deleted file mode 100644 index 441948f..0000000 --- a/_proc/data/195_group_specification.groupeng +++ /dev/null @@ -1,23 +0,0 @@ -classlist : 195_class.csv - -student_identifier : ID - -group_size : 3- - -- cluster : Gender - values : F - -- cluster : Ethnicity - values : (B = H) - -- distribute : Major - values : COGS, DSC, MATH - -- distribute : Math Skills - values : 5, 4 -- distribute : Programming Skills - value : 5, 4 -- distribute : Statistic Skills - value : 5, 4 - -- balance : GPA \ No newline at end of file diff --git a/_proc/data/dir.txt b/_proc/data/dir.txt deleted file mode 100644 index 03c8d2e..0000000 --- a/_proc/data/dir.txt +++ /dev/null @@ -1 +0,0 @@ -/Users/scottyang/Desktop/SP23_Working/Group_Eng/CanvasGroupy/nbs/data/groups_195_group_specification_2023-05-11_15-49-41 \ No newline at end of file diff --git a/_proc/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_classlist.csv b/_proc/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_classlist.csv deleted file mode 100644 index 4cc795b..0000000 --- a/_proc/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_classlist.csv +++ /dev/null @@ -1,7 +0,0 @@ -ID,GPA,Gender,Ethnicity,Major,Math Skills,Programming Skills,Statistic Skills,Group Number -nmackler,3.4,F,B,COGS,2,3,2,1 -jiz100,2.5,M,H,MATH,2,2,1,1 -jiz088,2.1,F,-,DSC,4,4,4,1 -dol005,3.9,M,-,COGS,5,5,3,2 -xiw013,3.1,M,-,DSC,3,1,5,2 -phantom,None,None,None,None,None,None,None,2 diff --git a/_proc/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_details.csv b/_proc/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_details.csv deleted file mode 100644 index 4432d5c..0000000 --- a/_proc/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_details.csv +++ /dev/null @@ -1,9 +0,0 @@ -ID,GPA,Gender,Ethnicity,Major,Math Skills,Programming Skills,Statistic Skills,Group Number,,group GPA mean,Rules Broken -nmackler,3.4,F,B,COGS,2,3,2,1 -jiz100,2.5,M,H,MATH,2,2,1,1 -jiz088,2.1,F,-,DSC,4,4,4,1 -summary,,,,,,,,,,2.6666666666666665,Cluster: Ethnicity,Balance: GPA - -dol005,3.9,M,-,COGS,5,5,3,2 -xiw013,3.1,M,-,DSC,3,1,5,2 -phantom,None,None,None,None,None,None,None,2 diff --git a/_proc/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_groups.csv b/_proc/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_groups.csv deleted file mode 100644 index 31e9161..0000000 --- a/_proc/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_groups.csv +++ /dev/null @@ -1,2 +0,0 @@ -Group 1, jiz088, jiz100, nmackler -Group 2, dol005, xiw013 diff --git a/_proc/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_groups.txt b/_proc/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_groups.txt deleted file mode 100644 index 3b83a96..0000000 --- a/_proc/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_groups.txt +++ /dev/null @@ -1,7 +0,0 @@ -Group 1 -jiz088 -jiz100 -nmackler -Group 2 -dol005 -xiw013 diff --git a/_proc/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_statistics.txt b/_proc/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_statistics.txt deleted file mode 100644 index 2115a6e..0000000 --- a/_proc/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_statistics.txt +++ /dev/null @@ -1,23 +0,0 @@ -Ran GroupEng on: ../data/195_group_specification.groupeng with students from 195_class.csv - -Made 2 groups - -0 groups failed: - -1 groups failed: - -0 groups failed: - -0 groups failed: - -0 groups failed: - -0 groups failed: - -2 groups failed:: Class GPA Mean: 3.00, Class GPA Std Dev: 0.64, Std Dev of Group GPA Means: 0.42 - -Group Summaries ---------------- -Group 1: Failed , Failed -Group 2: Failed - diff --git a/_proc/dir.txt b/_proc/dir.txt deleted file mode 100644 index c604f64..0000000 --- a/_proc/dir.txt +++ /dev/null @@ -1 +0,0 @@ -groups_example_2023-04-18_13-28-02 \ No newline at end of file diff --git a/_proc/feedback_template/checkpoint_feedback.md b/_proc/feedback_template/checkpoint_feedback.md deleted file mode 100644 index 2a56fc0..0000000 --- a/_proc/feedback_template/checkpoint_feedback.md +++ /dev/null @@ -1,40 +0,0 @@ -# Project Checkpoint Feedback - -Total: - -## Feedback - -| Category | Full Point | Your Score | Comment | -|---------------------|------------|------------|---------| -| Abstract | 0.5 | | | -| Background | 0.5 | | | -| Problem Statement | 0.5 | | | -| Data | 1 | | | -| Proposed Solution | 1 | | | -| Metrics | 1 | | | -| Preliminary Results | 1 | | | -| Ethics & Privacy | 1 | | | -| Team expectations | 0.25 | | | -| Timeline | 0.25 | | | - -## Rubric - - - -| Category | Full Point | Explanation | -|---------------------|------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Abstract | 0.5 | Abstract is informative, succinct, and clear. It offers specific details about the educational issue, variables (data), context, proposed methods, and measurement of performance/success of the study. | -| Background | 0.5 | Use a minimum of 2 or 3 citations Include a general introduction to your topic Narrative integrates critical and logical details from the peer-reviewed theoretical and research literature. Each key research component is grounded to the literature. Attention is given to different perspectives, threats to validity, and opinion vs. evidence. | -| Problem Statement | 0.5 | Presents a well-defined and significant research problem Include at least one ML-relevant potential solution. Articulates clear, reasonable research questions given the purpose, design, and methods of the project. All variables and controls have been appropriately defined. Proposals are clearly supported from the research and theoretical literature. All elements are mutually supportive. | -| Data | 1 | Multiple data sources for each aspect of the project. All data sources are fully described and referenced. Data is appropriate to the question/goal and large enough data points >1k observations and >5 variable The details of the descriptions also make it clear how they support the needs of the project. | -| Proposed Solution | 1 | The elements of the process were described succinctly and with clarity about how they are connected to each other Included description how the solution will be tested. | -| Metrics | 1 | The metrics are described clearly and succinctly. Their appropriateness for addressing the research problem is clearly described. Provided the mathematical representations of metrics | -| Preliminary Results | 1 | Analyzing the suitability of a dataset or algorithm for prediction/solving your problem Performing feature selection or hand-designing features from the raw data. Describe the features available/created and/or show the code for selection/creation Dataset actually clean and usable after feature selection is carried out Showing the performance of a base model/hyper-parameter setting. Solve the task with one "default" algorithm and characterize the performance level of that base model. At least one of the three: Learning curves or validation curves for a particular model Tables/graphs showing the performance of different models/hyper-parameters | -| Ethics & Privacy | 1 | Thoughtful discussion of ethical concerns included. Ethical concerns consider the whole data science process (question asked, data collected, data being used, the bias in data, analysis, post-analysis, etc.). How your group handled bias/ethical concerns clearly described | -| Team expectations | 0.25 | The list clearly was the subject of a thoughtful approach and already indicates a well-working team | -| Timeline | 0.25 | The timeline was clearly the subject of a thoughtful approach and indicates that the team has a detailed plan that seems appropriate and completable in the allotted time. | - - - -## Comments - diff --git a/_proc/feedback_template/final_project_feedback.md b/_proc/feedback_template/final_project_feedback.md deleted file mode 100644 index d832f0a..0000000 --- a/_proc/feedback_template/final_project_feedback.md +++ /dev/null @@ -1,37 +0,0 @@ -# Final Project Feedback - -## Feedback - -| Category | Full Point | Your Score | Comment | -|-------------------------|------------|------------|---------| -| Title & Abstract | 1 | | | -| Background | 1 | | | -| Problem Statement | 1 | | | -| Data | 1 | | | -| Proposed Solution | 1.25 | | | -| Metrics | 1.25 | | | -| Results | 1.25 | | | -| Interpreting the result | 1.25 | | | -| Limitations | 1.25 | | | -| Ethics & Privacy | 0.75 | | | -| Conclusion | 1 | | | - -## Rubric - -| Category | Full Point | Expectation | -|-------------------------|------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Title & Abstract | 1 | Title & abstract is informative, succinct, and clear.
    It offers specific details about the educational issue, variables (data), context, proposed methods, and measurement of performance/success of the study. | -| Background | 1 | Use a minimum of 2 or 3 citations
    Include a general introduction to your topic
    Narrative integrates critical and logical details from the peer-reviewed theoretical and research literature.
    Each key research component is grounded to the literature. Attention is given to different perspectives, threats to validity, and opinion vs. evidence. | -| Problem Statement | 1 | Presents a well-defined and significant research problem
    Articulates clear, reasonable research questions given the purpose, design, and methods of the project.
    All variables and controls have been appropriately defined. Proposals are clearly supported from the research and theoretical literature. All elements are mutually supportive. | -| Data | 1 | Multiple data sources for each aspect of the project. All data sources are fully described and referenced.
    Data is appropriate to the question/goal and large enough data points >1k observations and >5 variable
    The details of the descriptions of the data also make it clear how they support the needs of the project.
    Details of data storage and cleaning | -| Proposed Solution | 1.25 | The elements of the process were described succinctly and with clarity about how they are connected to each other
    Included description how the solution will be tested. (0.5 pt) | -| Metrics | 1.25 | The metrics are described clearly and succinctly.
    Their appropriateness for addressing the research problem is clearly described. | -| Results | 1.25 | Does a good model/hyper-parameter selection using more than one model and hyperparameter in hyperparameter search.
    Include the detailed code and analysis results of the main points
    Performs multiple secondary analysis such as learning curves, heat maps looking at where in the parameter space things are good/bad, uses statistical testing | -| Interpreting the result | 1.25 | Think clearly about the results and obtain one main point and 2-4 secondary points (2-5 sentences per point).
    Highlight HOW the results support those points. Understand what they are doing in the previous “Results” section | -| Limitations | 1.25 | Has a sense of what to do next, and has good explorations of the limitations. | -| Ethics & Privacy | 0.75 | Thoughtful discussion of ethical concerns included.
    Ethical concerns consider the whole data science process (question asked, data collected, data being used, the bias in data, analysis, post-analysis, etc.).
    How your group handled bias/ethical concerns clearly described | -| Conclusion | 1 | Clearly recapitulates the results and provides context, perhaps including the literature of the field. | - -## Comments - - diff --git a/_proc/feedback_template/proposal_feedback.md b/_proc/feedback_template/proposal_feedback.md deleted file mode 100644 index 1725eb8..0000000 --- a/_proc/feedback_template/proposal_feedback.md +++ /dev/null @@ -1,45 +0,0 @@ -# Project Proposal Feedback - -## Score (out of 9) - -[comment]: # (Please put the score in the following line. For example, score of 8 should be `Score = 8` strictly) -Score = ... - -## Feedback: - -| | **Quality** | **Reasons** | -|-----------------------|-------------|-------------| -| **Abstract** | | | -| **Research question** | | | -| **Background** | | | -| **Hypothesis** | | | -| **Data** | | | -| **Ethics** | | | -| **Team expectations** | | | -| **Timeline** | | | - - -## Rubric - -| | Unsatisfactory | Developing | Proficient | Excellent | -|-----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| **Abstract** | Abstract is confusing or fails to offer important details about the issue, variables, context, or methods of the project. | Abstract lacks relevance or fails to offer appropriate details about the issue, variables, context, or methods of the project. | Abstract is relevant, offering details about the research project. | Abstract is informative, succinct, and clear. It offers specific details about the educational issue, variables, context, and proposed methods of the study. | -| **Research question** | Research issue remains unclear. The research purpose, questions, hypotheses, definitions or variables and controls are still largely undefined, or when they are are poorly formed, ambiguous, or not logically connected to the description of the problem. Unclear connections to the literature. | Research issue is identified, but statement is too broad or fails to establish the importance of the problem. The research purpose, questions, hypotheses, definitions or variables and controls are poorly formed, ambiguous, or not logically connected to the description of the problem. Unclear connections to the literature. | Identifies a relevant research issue. Research questions are succinctly stated, connected to the research issue, and supported by the literature. Variables and controls have been identified and described. Connections are established with the literature. | Presents a significant research problem. Articulates clear, reasonable research questions given the purpose, design, and methods of the project. All variables and controls have been appropriately defined. Proposals are clearly supported from the research and theoretical literature. All elements are mutually supportive. | -| **Background** | Did not have at least 2 reliable and relevant sources. Or relevant sources were not used in relevant ways | A key component was not connected to the research literature. Selected literature was from unreliable sources. Literary supports were vague or ambiguous. | Key research components were connected to relevant, reliable theoretical and research literature. | Narrative integrates critical and logical details from the peer-reviewed theoretical and research literature. Each key research component is grounded to the literature. Attention is given to different perspectives, threats to validity, and opinion vs. evidence. | -| **Hypothesis** | Lacks most details; vague or interpretable in different ways. Or seems completely unrealistic. | A key detail to understand the hypothesis or the rationale behind it was not described well enough | The hypothesis is clear. All elements needed to understand the rationale were described in sufficient detail | The hypothesis and its rationale were described succinctly and with clarity about how they are connected to each other | -| **Data** | Did not have references to relevant data sources for this problem. Did not describe the data obtained at those sources | A key data source was not referenced or described in satisfactory level of detail | All relevant data sources were referenced and described in terms of their key variables and size | Multiple data sources for each aspect of the project, All data sources are fully described and referenced. The details of the descriptions also make it clear how they support the needs ot the project. | -| **Ethics** | No effort or just says we have no ethical concerns | Minimal ethical section; probably just talks about data privacy and no unintended consequences discussion. Ethical concerns raised seem irrelevant. | Ethical concerns described are appropriate and described sufficiently | Ethical concerns are described clearly and succinctly. This was clearly a thoughtful and nuanced approach to the issues | -| **Team expectations** | Lack of expectations | The list of expectations feels incomplete and perfunctory | It feels like the list of expectations is complete and seems appropriate | The list clearly was the subject of a thoughtful approach and already indicates a well-working team | -| **Timeline** | Lack of timeline. Or timeline is completely unrealistic | The timeline feels incomplete and perfunctory. The timeline feels either too fast or too slow for the progress you expect a group can make | It feels like the timeline is complete and appropriate. it can likely be completed as is in the available amount of time | The timeline was clearly the subject of a thoughtful approach and indicates that the team has a detailed plan that seems appropriate and completable in the allotted time. | - -Scoring: Out of 9 points - -- Each Developing => -0.75 pts -- Each Unsatisfactory/Missing => -1.5 pts - - until the score is 0 - -If students address the detailed feedback in a future checkpoint they will earn these points back - -## Comments - - diff --git a/_proc/index.ipynb b/_proc/index.ipynb deleted file mode 100644 index 926982c..0000000 --- a/_proc/index.ipynb +++ /dev/null @@ -1,142 +0,0 @@ -{ - "cells": [ - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "---\n", - "description: Canvas Grouping Python Script\n", - "output-file: index.html\n", - "title: CanvasGroupy\n", - "\n", - "---\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "View our documentation [at this link](https://fleischerresearchlab.github.io/CanvasGroupy/)\n", - "\n", - "\n", - "This module will use `GroupEng` to create canvas and github groups." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Install" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "```sh\n", - "pip install CanvasGroupy\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## How to use" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Please visit [GroupEng Official Website](https://groupeng.org/) to see the documnetation of how to use GroupEng." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "['-', '-', 'B', '-', '-']\n", - "['B', '-', 'H', '-', '-']\n", - "['H', '-', '-', '-', '-']\n", - "['-', 'H', '-', 'B', 'H']\n", - "['-', '-', '-', 'B', '-']\n", - "['-', '-', 'H', '-', '-']\n", - "['nanotech', 'renewable energy', 'nanotech', 'nanotech', 'nanotech']\n", - "['automotive', 'automotive', 'robotics', 'automotive', 'renewable energy']\n", - "['automotive', 'statistics', 'automotive', 'renewable energy', 'renewable energy']\n", - "['automotive', 'automotive', 'statistics', 'renewable energy', 'renewable energy']\n", - "['automotive', 'automotive', 'renewable energy', 'statistics', 'renewable energy']\n", - "['renewable energy', 'automotive', 'automotive', 'statistics', 'renewable energy']\n", - "['CS', 'EE', 'Mech E', 'CS', 'EE']\n", - "['CS', 'EE', 'Mech E', 'Mech E', 'EE']\n", - "['CS', 'Civ E', 'EE', 'Mech E', 'Mech E']\n", - "['EE', 'Civ E', 'Civ E', 'EE', 'Mech E']\n", - "['EE', 'Mech E', 'CS', 'EE', 'EE']\n", - "['Civ E', 'Mech E', 'CS', 'Mech E', 'EE']\n", - "['y', 'y', 'y', 'y', 'y']\n", - "['y', 'y', 'y', 'y', 'y']\n", - "['y', 'y', 'y', 'y', 'y']\n", - "['y', 'y', 'y', 'y', 'y']\n", - "['y', 'y', 'y', 'y', 'y']\n", - "['-', '-', 'y', '-', 'y']\n", - "['y', 'y', 'y', 'y', 'y']\n", - "['y', 'y', 'y', 'y', 'y']\n", - "['y', 'y', 'y', 'y', 'y']\n", - "['y', 'y', 'y', 'y', 'y']\n", - "['y', 'y', 'y', 'y', 'y']\n", - "['y', '-', '-', 'y', 'y']\n", - "['y', 'y', 'y', '-', '-']\n", - "['y', 'y', '-', 'y', '-']\n", - "['-', '-', '-', 'y', 'y']\n", - "['y', '-', 'y', '-', 'y']\n", - "['-', '-', 'y', '-', 'y']\n", - "[3.2579174234, 3.5995299693, 3.2756432963, 4.220160605, 2.5723254477]\n", - "[3.2579174234, 3.2756432963, 2.5723254477, 3.5995299693, 4.220160605]\n", - "[4.220160605, 3.5995299693, 3.2756432963, 3.2579174234, 2.5723254477]\n", - "[3.2756432963, 4.220160605, 3.2579174234, 2.5723254477, 3.5995299693]\n", - "[3.5995299693, 4.220160605, 3.2756432963, 3.2579174234, 2.5723254477]\n", - "[3.5995299693, 3.2756432963, 2.5723254477, 3.2579174234, 4.220160605]\n" - ] - }, - { - "data": { - "text/plain": [ - "(False, 'groups_example_2023-04-18_13-00-35')" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "assign_groups(\"example/sample_group_specification.groupeng\")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "python3", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/_proc/nbdev.yml b/_proc/nbdev.yml deleted file mode 100644 index 2c6d459..0000000 --- a/_proc/nbdev.yml +++ /dev/null @@ -1,9 +0,0 @@ -project: - output-dir: _docs - -website: - title: "CanvasGroupy" - site-url: "https://FleischerResearchLab.github.io/CanvasGroupy" - description: "Canvas Grouping Python Script" - repo-branch: main - repo-url: "https://github.com/FleischerResearchLab/CanvasGroupy" diff --git a/_proc/sidebar.yml b/_proc/sidebar.yml deleted file mode 100644 index ddab084..0000000 --- a/_proc/sidebar.yml +++ /dev/null @@ -1,14 +0,0 @@ -website: - sidebar: - contents: - - index.ipynb - - section: api - contents: - - api/index.qmd - - api/01_GroupEng_assign.ipynb - - api/02_gh_group_creation.ipynb - - api/03_canvas.ipynb - - section: tutorial - contents: - - tutorial/Authentications.ipynb - - tutorial/Create GitHub Group from Canvas Group.ipynb diff --git a/_proc/styles.css b/_proc/styles.css deleted file mode 100644 index 66ccc49..0000000 --- a/_proc/styles.css +++ /dev/null @@ -1,37 +0,0 @@ -.cell { - margin-bottom: 1rem; -} - -.cell > .sourceCode { - margin-bottom: 0; -} - -.cell-output > pre { - margin-bottom: 0; -} - -.cell-output > pre, .cell-output > .sourceCode > pre, .cell-output-stdout > pre { - margin-left: 0.8rem; - margin-top: 0; - background: none; - border-left: 2px solid lightsalmon; - border-top-left-radius: 0; - border-top-right-radius: 0; -} - -.cell-output > .sourceCode { - border: none; -} - -.cell-output > .sourceCode { - background: none; - margin-top: 0; -} - -div.description { - padding-left: 2px; - padding-top: 5px; - font-style: italic; - font-size: 135%; - opacity: 70%; -} diff --git a/_proc/tutorials/Authentications.ipynb b/_proc/tutorials/Authentications.ipynb deleted file mode 100644 index 636ff90..0000000 --- a/_proc/tutorials/Authentications.ipynb +++ /dev/null @@ -1,99 +0,0 @@ -{ - "cells": [ - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "---\n", - "description: In this example, we demonstrated the required credentials information\n", - " needed to execute this program.\n", - "order: 1\n", - "output-file: authentications.html\n", - "title: GitHub / Canvas Authentications\n", - "\n", - "---\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "markdown", - "id": "95230c79", - "metadata": {}, - "source": [ - "To use this software, which essentially is a wrapper package under a API wrapper, you need to provide the program with the appropriate credentials file. We will use the provided credential to authenticate toward both Canvas and GitHub, in order to fetch and manipulate appropriate information.\n", - "\n", - "**Note:** The program itself won't either store nor stole your credentials information. This program will stay on your personal computer and will only use your credentials when needed. For more information about the implementation details, please refer to the GitHub repository to view the source code." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d4619816", - "metadata": { - "language": "python" - }, - "outputs": [], - "source": [ - "from CanvasGroupy.canvas import CanvasGroup\n", - "from CanvasGroupy.github import GitHubGroup" - ] - }, - { - "cell_type": "markdown", - "id": "38fd8b05", - "metadata": {}, - "source": [ - "The credentials are stored in the following format. You can download a credential template at the following link. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b6e480e4", - "metadata": { - "language": "python" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'GitHub Token': 'token', 'Canvas Token': 'token'}\n" - ] - } - ], - "source": [ - "#| echo: false\n", - "with open(\"../credentials.json\", \"r\") as f:\n", - " credentials = json.load(f)\n", - "print(credentials)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e8d26432", - "metadata": { - "language": "python" - }, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "python3", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/_proc/tutorials/Create GitHub Group from Canvas Group.ipynb b/_proc/tutorials/Create GitHub Group from Canvas Group.ipynb deleted file mode 100644 index 4147ff0..0000000 --- a/_proc/tutorials/Create GitHub Group from Canvas Group.ipynb +++ /dev/null @@ -1,1277 +0,0 @@ -{ - "cells": [ - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "---\n", - "description: In this example, we created GitHub group repositories to student groups\n", - " based on the group information on canvas.\n", - "order: 2\n", - "output-file: create github group from canvas group.html\n", - "title: Example Workflow - Create GitHub Group from Canvas Group\n", - "\n", - "---\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "markdown", - "id": "19f57906", - "metadata": {}, - "source": [ - " This example workflow was used in Spring 2023, for COGS 118A at UC San Diego. his workflow demonstrated a component of the `CanvasGroupy`, where we already have group information based on the canvas group. In addition, students' GitHub usernames were collected via a canvas quiz, where we fetched, validated, and stored the GitHub Username directly. This workflow was run after the fact that all students were successfully assigned a group, and all students have _correctly_ completed their GitHub Username quiz." - ] - }, - { - "cell_type": "markdown", - "id": "98820a9d", - "metadata": {}, - "source": [ - "As usual, to execute those API calls, you will have to provide the system with necessary credentials. You can find more information based on the _???TODO_ tutorial." - ] - }, - { - "cell_type": "markdown", - "id": "855dfd7c", - "metadata": {}, - "source": [ - "**Note:** The output of some cells are long, and the output might affect your reading experience. I recommend you to use the hyperlink on the right hand side bar to skip to the next section if needed." - ] - }, - { - "cell_type": "markdown", - "id": "912d8cc8", - "metadata": {}, - "source": [ - "## Canvas Get Groups / GitHub Username\n", - "\n", - "We need to get the group member information at Canvas. This is achieved by pulling the people list on the group category page." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "13856869", - "metadata": { - "language": "python" - }, - "outputs": [], - "source": [ - "from CanvasGroupy.canvas import CanvasGroup\n", - "from CanvasGroupy.github import GitHubGroup" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "eafdcbc3", - "metadata": { - "language": "python" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Authorization Successful!\n", - "Course Set: COGS 118A - Supvr/Mach Learning Algorithms - Fleischer [SP23] \n", - "Getting List of Users... This might take a while...\n", - "Users Fetch Complete! The course has 161 students.\n" - ] - } - ], - "source": [ - "cg = CanvasGroup(\"Group_Eng/credentials.json\", course_id=45059)" - ] - }, - { - "cell_type": "markdown", - "id": "17468662", - "metadata": {}, - "source": [ - "### Fetch GitHub Username from Quiz\n", - "\n", - "See more info at [`CanvasGroup.fetch_username_from_quiz`](https://FleischerResearchLab.github.io/CanvasGroupy/api/canvas.html#canvasgroup.fetch_username_from_quiz)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c1103f9a", - "metadata": { - "language": "python" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Quiz: GitHub Username fetch! \n", - "Generating Student Analaysis...\n", - "[====================] 100%\n", - "Report Generated!\n", - "The Question asked is 1389031: What is your GitHub Username? Absolutely No Typo Please.. \n", - "Make sure this is the correct question where you asked student for their GitHub id.\n", - "If you need to change the index of columns, change the col_index argument of this call.\n" - ] - } - ], - "source": [ - "github_usernames = cg.fetch_username_from_quiz(quiz_id=139061)" - ] - }, - { - "cell_type": "markdown", - "id": "580ba3fc", - "metadata": {}, - "source": [ - "## Check GitHub Username Validity\n", - "\n", - "We use GitHub API to search for a target user. See more info at [`CanvasGroup.check_github_usernames`](https://FleischerResearchLab.github.io/CanvasGroupy/api/canvas.html#canvasgroup.check_github_usernames)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d34a0188", - "metadata": { - "language": "python" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Student ax008708 did not submit their github username.\n", - "Notification Sent!\n", - "Student a8chu did not submit their github username.\n", - "Notification Sent!\n", - "Student j3dong did not submit their github username.\n", - "Notification Sent!\n", - "Student n6garcia did not submit their github username.\n", - "Notification Sent!\n", - "Student kehu did not submit their github username.\n", - "Notification Sent!\n", - "Student qil016 did not submit their github username.\n", - "Notification Sent!\n", - "Student ttp007 did not submit their github username.\n", - "Notification Sent!\n", - "Student zshao did not submit their github username.\n", - "Notification Sent!\n" - ] - }, - { - "data": { - "text/plain": [ - "{}" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cg.check_github_usernames(github_usernames,\n", - " send_canvas_email=True,\n", - " send_undone_reminder=True,\n", - " quiz_url=\"https://canvas.ucsd.edu/courses/45059/quizzes/139061\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "6d90cf81", - "metadata": {}, - "source": [ - "## Get Group Member Information" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0a7889e9", - "metadata": { - "language": "python" - }, - "outputs": [ - { - "data": { - "text/plain": [ - "{'Group001-SP23': ['h5he', 'zmao', 'xiw013', 'j6wen', 'j5zhu'],\n", - " 'Group002-SP23': ['cmcmanig', 'jup006', 'ssuthar', 'd3yu'],\n", - " 'Group003-SP23': ['yic055', 'yuz191', 'xiz068'],\n", - " 'Group004-SP23': ['dac020', 'nilu', 'tyap', 'g6zhu'],\n", - " 'Group005-SP23': ['kechen', 'a8chu', 'cdelira', 'wolee', 'arshukla'],\n", - " 'Group006-SP23': ['ax008707', 'ax008724', 'ax008777'],\n", - " 'Group007-SP23': ['yuchi', 'j3dong', 'y3ge', 'x6he', 'xiz031'],\n", - " 'Group008-SP23': ['zifeng', 'ax008740', 'jul121', 'yuy047'],\n", - " 'Group009-SP23': ['ax008573', 'ckavanagh', 'v1lu', 'vvishnus'],\n", - " 'Group010-SP23': ['jwc002', 'tjamal', 'jsliang', 'tdn003'],\n", - " 'Group011-SP23': ['khchuang', 'emdavis', 'jejiang', 'nrejai'],\n", - " 'Group012-SP23': ['afleschn', 'rlharsono', 'jjsanchez', 'asengupt'],\n", - " 'Group013-SP23': ['kehu', 'jnhuang', 'shperry', 'alvalenc'],\n", - " 'Group014-SP23': ['gsroberts', 'cvillafa', 'shw089', 'yiz095'],\n", - " 'Group015-SP23': ['aanna', 'sdsilva', 'asivayog', 'nyanekch'],\n", - " 'Group016-SP23': ['yuche', 'y3guo', 'e1hu', 'zhl023', 'qil012'],\n", - " 'Group017-SP23': ['s3chowdhury', 'llennema', 'psodhi', 'svirk'],\n", - " 'Group018-SP23': ['ajcagle', 'ahewig', 's2malik', 'mnodini', 'musman'],\n", - " 'Group019-SP23': ['sasingh', 'nsit', 'dsun'],\n", - " 'Group020-SP23': ['akaji', 'm1manzan', 'j3mendez', 'mpareek', 'atrapena'],\n", - " 'Group021-SP23': ['jkrentse', 'jmlai', 'zel012', 'sserafin'],\n", - " 'Group022-SP23': ['ax008708', 'anc024', 'gng', 'reyang'],\n", - " 'Group023-SP23': ['raguinakang', 'phelcl', 'vjayanan', 'crochez', 'kpstern'],\n", - " 'Group024-SP23': ['nabansal', 'ccaban', 'mvfang', 'asim'],\n", - " 'Group025-SP23': ['nschaefe', 'hshaikh', 'jww001', 'bjyan'],\n", - " 'Group026-SP23': ['e1dong', 'aaolivas', 'h5park', 'yuz821'],\n", - " 'Group027-SP23': ['jddeleon', 'ndnguyen', 'ttp007', 'jzs002'],\n", - " 'Group028-SP23': ['mabdilah', 'yuh045', 'ktnakai', 'jonza'],\n", - " 'Group029-SP23': ['lmitbo', 'jsalce', 'lskerrett', 'jubamadu'],\n", - " 'Group030-SP23': ['ruc003', 'shh035', 'btn003', 'knino'],\n", - " 'Group031-SP23': ['shc007', 'n6garcia', 'ken010', 'mmpak'],\n", - " 'Group032-SP23': ['zachao', 'smurase', 'j1xu', 'z5zhang'],\n", - " 'Group033-SP23': ['sbodhisartha', 'ndeepak', 'arlu', 'trucker', 'jaxu'],\n", - " 'Group034-SP23': ['kabalaji', 'rchaklas', 'vspillai', 'jmvillal'],\n", - " 'Group035-SP23': ['cantoniohernandez', 'rpuranam', 'rsedano', 'zshao'],\n", - " 'Group036-SP23': ['malkhalifah', 'djjani', 'rkohli', 'smtrived'],\n", - " 'Group037-SP23': ['nazpeitia', 'g2hong', 'gkweon'],\n", - " 'Group038-SP23': ['qil016', 'msherrick', 'yiw085', 'r4zhou'],\n", - " 'Group039-SP23': ['cgutierrezgodoy', 'z8jiang', 'dar005', 'jtaolan']}" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "groups = cg.get_groups(\"Final Project\")\n", - "groups" - ] - }, - { - "cell_type": "markdown", - "id": "11be10d8", - "metadata": {}, - "source": [ - "## GitHub Repository Creation\n", - "\n", - "Given the gathered information about both group membership and students' GitHub Username, we are ready to create group repositories for them." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "50e54f49", - "metadata": { - "language": "python" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Successfully Authenticated. GitHub account: scott-yj-yang \n", - "Target Organization Set: COGS118A \n" - ] - } - ], - "source": [ - "ggroup = GitHubGroup(\"Group_Eng/credentials.json\", verbosity=1)\n", - "ggroup.set_org(\"COGS118A\")" - ] - }, - { - "cell_type": "markdown", - "id": "be22eaba", - "metadata": {}, - "source": [ - "In the following for loop, we create the group repositories via a series of [`GitHubGroup.create_group_repo`](https://FleischerResearchLab.github.io/CanvasGroupy/api/github.html#githubgroup.create_group_repo) command. This is the place where we can get personalized (or I shall say _groupalized_) repositories. Be sure to change the appropriate parameters." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "915d6dfa", - "metadata": { - "language": "python" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Repo Group001-SP23 Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group001-SP23.ipynb \n", - "File Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group001-SP23.ipynb \n", - "File Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group001-SP23.ipynb \n", - "Added Collaborator: TaraaHe to: Group001-SP23 with permission: write \n", - "Added Collaborator: demimao to: Group001-SP23 with permission: write \n", - "Added Collaborator: xiw013 to: Group001-SP23 with permission: write \n", - "Added Collaborator: willwen96 to: Group001-SP23 with permission: write \n", - "Added Collaborator: Ju-dyz to: Group001-SP23 with permission: write \n", - "Team Instructors_Sp23 added to Group001-SP23 with permission admin \n", - "Group Repo: Group001-SP23 successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group001-SP23\n", - "\n", - "Repo Group002-SP23 Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group002-SP23.ipynb \n", - "File Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group002-SP23.ipynb \n", - "File Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group002-SP23.ipynb \n", - "Added Collaborator: connormcmanigal to: Group002-SP23 with permission: write \n", - "Added Collaborator: jup006 to: Group002-SP23 with permission: write \n", - "Added Collaborator: ssutharucsd to: Group002-SP23 with permission: write \n", - "Added Collaborator: d3yu to: Group002-SP23 with permission: write \n", - "Team Instructors_Sp23 added to Group002-SP23 with permission admin \n", - "Group Repo: Group002-SP23 successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group002-SP23\n", - "\n", - "Repo Group003-SP23 Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group003-SP23.ipynb \n", - "File Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group003-SP23.ipynb \n", - "File Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group003-SP23.ipynb \n", - "Added Collaborator: Cyl200215 to: Group003-SP23 with permission: write \n", - "Added Collaborator: scottieboyzhang to: Group003-SP23 with permission: write \n", - "Added Collaborator: Orang1s to: Group003-SP23 with permission: write \n", - "Team Instructors_Sp23 added to Group003-SP23 with permission admin \n", - "Group Repo: Group003-SP23 successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group003-SP23\n", - "\n", - "Repo Group004-SP23 Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group004-SP23.ipynb \n", - "File Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group004-SP23.ipynb \n", - "File Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group004-SP23.ipynb \n", - "Added Collaborator: CDavid99 to: Group004-SP23 with permission: write \n", - "Added Collaborator: nilu0311 to: Group004-SP23 with permission: write \n", - "Added Collaborator: cookingoil88 to: Group004-SP23 with permission: write \n", - "Added Collaborator: g6zhu to: Group004-SP23 with permission: write \n", - "Team Instructors_Sp23 added to Group004-SP23 with permission admin \n", - "Group Repo: Group004-SP23 successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group004-SP23\n", - "\n", - "a8chu's GitHub Username not found\n", - "Repo Group005-SP23 Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group005-SP23.ipynb \n", - "File Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group005-SP23.ipynb \n", - "File Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group005-SP23.ipynb \n", - "Added Collaborator: kchen283 to: Group005-SP23 with permission: write \n", - "Added Collaborator: cdelira9 to: Group005-SP23 with permission: write \n", - "Added Collaborator: wj6801 to: Group005-SP23 with permission: write \n", - "Added Collaborator: arth-shukla to: Group005-SP23 with permission: write \n", - "Team Instructors_Sp23 added to Group005-SP23 with permission admin \n", - "Group Repo: Group005-SP23 successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group005-SP23\n", - "\n", - "Repo Group006-SP23 Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group006-SP23.ipynb \n", - "File Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group006-SP23.ipynb \n", - "File Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group006-SP23.ipynb \n", - "Added Collaborator: Leoooo333 to: Group006-SP23 with permission: write \n", - "Added Collaborator: qdh-2002 to: Group006-SP23 with permission: write \n", - "Added Collaborator: Chihhsinli to: Group006-SP23 with permission: write \n", - "Team Instructors_Sp23 added to Group006-SP23 with permission admin \n", - "Group Repo: Group006-SP23 successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group006-SP23\n", - "\n", - "j3dong's GitHub Username not found\n", - "Repo Group007-SP23 Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group007-SP23.ipynb \n", - "File Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group007-SP23.ipynb \n", - "File Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group007-SP23.ipynb \n", - "Added Collaborator: YunxiangChi to: Group007-SP23 with permission: write \n", - "Added Collaborator: alien-invader to: Group007-SP23 with permission: write \n", - "Added Collaborator: XiaoyanHe0713 to: Group007-SP23 with permission: write \n", - "Added Collaborator: Andrina-iris to: Group007-SP23 with permission: write \n", - "Team Instructors_Sp23 added to Group007-SP23 with permission admin \n", - "Group Repo: Group007-SP23 successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group007-SP23\n", - "\n", - "Repo Group008-SP23 Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group008-SP23.ipynb \n", - "File Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group008-SP23.ipynb \n", - "File Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group008-SP23.ipynb \n", - "Added Collaborator: wwjasperww to: Group008-SP23 with permission: write \n", - "Added Collaborator: ChengqinLi1206 to: Group008-SP23 with permission: write \n", - "Added Collaborator: junyuelin to: Group008-SP23 with permission: write \n", - "Added Collaborator: fergusyyang to: Group008-SP23 with permission: write \n", - "Team Instructors_Sp23 added to Group008-SP23 with permission admin \n", - "Group Repo: Group008-SP23 successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group008-SP23\n", - "\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Repo Group009-SP23 Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group009-SP23.ipynb \n", - "File Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group009-SP23.ipynb \n", - "File Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group009-SP23.ipynb \n", - "Added Collaborator: thaiscodafond to: Group009-SP23 with permission: write \n", - "Added Collaborator: ckavanagh21 to: Group009-SP23 with permission: write \n", - "Added Collaborator: 404EZRA to: Group009-SP23 with permission: write \n", - "Added Collaborator: vvishnus to: Group009-SP23 with permission: write \n", - "Team Instructors_Sp23 added to Group009-SP23 with permission admin \n", - "Group Repo: Group009-SP23 successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group009-SP23\n", - "\n", - "Repo Group010-SP23 Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group010-SP23.ipynb \n", - "File Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group010-SP23.ipynb \n", - "File Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group010-SP23.ipynb \n", - "Added Collaborator: strawhatwilson23 to: Group010-SP23 with permission: write \n", - "Added Collaborator: tjamalcodes to: Group010-SP23 with permission: write \n", - "Added Collaborator: jaysunl to: Group010-SP23 with permission: write \n", - "Added Collaborator: idereknguyen to: Group010-SP23 with permission: write \n", - "Team Instructors_Sp23 added to Group010-SP23 with permission admin \n", - "Group Repo: Group010-SP23 successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group010-SP23\n", - "\n", - "Repo Group011-SP23 Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group011-SP23.ipynb \n", - "File Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group011-SP23.ipynb \n", - "File Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group011-SP23.ipynb \n", - "Added Collaborator: khchuang12 to: Group011-SP23 with permission: write \n", - "Added Collaborator: Emdavis02 to: Group011-SP23 with permission: write \n", - "Added Collaborator: jennifer-jiang to: Group011-SP23 with permission: write \n", - "Added Collaborator: nrejai to: Group011-SP23 with permission: write \n", - "Team Instructors_Sp23 added to Group011-SP23 with permission admin \n", - "Group Repo: Group011-SP23 successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group011-SP23\n", - "\n", - "Repo Group012-SP23 Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group012-SP23.ipynb \n", - "File Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group012-SP23.ipynb \n", - "File Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group012-SP23.ipynb \n", - "Added Collaborator: afleschner to: Group012-SP23 with permission: write \n", - "Added Collaborator: githubharsono to: Group012-SP23 with permission: write \n", - "Added Collaborator: JJSanchez23 to: Group012-SP23 with permission: write \n", - "Added Collaborator: antarasengupta26 to: Group012-SP23 with permission: write \n", - "Team Instructors_Sp23 added to Group012-SP23 with permission admin \n", - "Group Repo: Group012-SP23 successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group012-SP23\n", - "\n", - "kehu's GitHub Username not found\n", - "Repo Group013-SP23 Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group013-SP23.ipynb \n", - "File Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group013-SP23.ipynb \n", - "File Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group013-SP23.ipynb \n", - "Added Collaborator: jnhuang02 to: Group013-SP23 with permission: write \n", - "Added Collaborator: Sean1572 to: Group013-SP23 with permission: write \n", - "Added Collaborator: valenciaaalberto to: Group013-SP23 with permission: write \n", - "Team Instructors_Sp23 added to Group013-SP23 with permission admin \n", - "Group Repo: Group013-SP23 successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group013-SP23\n", - "\n", - "Repo Group014-SP23 Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group014-SP23.ipynb \n", - "File Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group014-SP23.ipynb \n", - "File Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group014-SP23.ipynb \n", - "Added Collaborator: empire-penguin to: Group014-SP23 with permission: write \n", - "Added Collaborator: villafun to: Group014-SP23 with permission: write \n", - "Added Collaborator: 50ShadesOfShawn to: Group014-SP23 with permission: write \n", - "Added Collaborator: ericzyl to: Group014-SP23 with permission: write \n", - "Team Instructors_Sp23 added to Group014-SP23 with permission admin \n", - "Group Repo: Group014-SP23 successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group014-SP23\n", - "\n", - "Repo Group015-SP23 Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group015-SP23.ipynb \n", - "File Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group015-SP23.ipynb \n", - "File Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group015-SP23.ipynb \n", - "Added Collaborator: arjunanna to: Group015-SP23 with permission: write \n", - "Added Collaborator: sdsilva1 to: Group015-SP23 with permission: write \n", - "Added Collaborator: abi2020 to: Group015-SP23 with permission: write \n", - "Added Collaborator: nikothomas to: Group015-SP23 with permission: write \n", - "Team Instructors_Sp23 added to Group015-SP23 with permission admin \n", - "Group Repo: Group015-SP23 successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group015-SP23\n", - "\n", - "Repo Group016-SP23 Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group016-SP23.ipynb \n", - "File Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group016-SP23.ipynb \n", - "File Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group016-SP23.ipynb \n", - "Added Collaborator: shendu11 to: Group016-SP23 with permission: write \n", - "Added Collaborator: Y3GUO to: Group016-SP23 with permission: write \n", - "Added Collaborator: EthanHu0 to: Group016-SP23 with permission: write \n", - "Added Collaborator: claireZHL to: Group016-SP23 with permission: write \n", - "Added Collaborator: QilunLiu5216 to: Group016-SP23 with permission: write \n", - "Team Instructors_Sp23 added to Group016-SP23 with permission admin \n", - "Group Repo: Group016-SP23 successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group016-SP23\n", - "\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Repo Group017-SP23 Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group017-SP23.ipynb \n", - "File Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group017-SP23.ipynb \n", - "File Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group017-SP23.ipynb \n", - "Added Collaborator: sreetama02 to: Group017-SP23 with permission: write \n", - "Added Collaborator: llennemann to: Group017-SP23 with permission: write \n", - "Added Collaborator: pabbi5 to: Group017-SP23 with permission: write \n", - "Added Collaborator: AstuteFern to: Group017-SP23 with permission: write \n", - "Team Instructors_Sp23 added to Group017-SP23 with permission admin \n", - "Group Repo: Group017-SP23 successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group017-SP23\n", - "\n", - "Repo Group018-SP23 Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group018-SP23.ipynb \n", - "File Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group018-SP23.ipynb \n", - "File Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group018-SP23.ipynb \n", - "Added Collaborator: ajcagle8 to: Group018-SP23 with permission: write \n", - "Added Collaborator: aHewig to: Group018-SP23 with permission: write \n", - "Added Collaborator: notSaranshMalik to: Group018-SP23 with permission: write \n", - "Added Collaborator: mnodini to: Group018-SP23 with permission: write \n", - "Added Collaborator: maryamkusman to: Group018-SP23 with permission: write \n", - "Team Instructors_Sp23 added to Group018-SP23 with permission admin \n", - "Group Repo: Group018-SP23 successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group018-SP23\n", - "\n", - "Repo Group019-SP23 Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group019-SP23.ipynb \n", - "File Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group019-SP23.ipynb \n", - "File Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group019-SP23.ipynb \n", - "Added Collaborator: SSingh44343 to: Group019-SP23 with permission: write \n", - "Added Collaborator: nathansit to: Group019-SP23 with permission: write \n", - "Added Collaborator: DaimengSun to: Group019-SP23 with permission: write \n", - "Team Instructors_Sp23 added to Group019-SP23 with permission admin \n", - "Group Repo: Group019-SP23 successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group019-SP23\n", - "\n", - "Repo Group020-SP23 Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from Checkpoint_groupXXX.ipynb to Checkpoint_Group020-SP23.ipynb \n", - "File Successfully Renamed from FinalProject_groupXXX.ipynb to FinalProject_Group020-SP23.ipynb \n", - "File Successfully Renamed from Proposal_groupXXX.ipynb to Proposal_Group020-SP23.ipynb \n", - "Added Collaborator: ashesh8500 to: Group020-SP23 with permission: write \n" - ] - } - ], - "source": [ - "repos = []\n", - "for group_name, members in groups.items():\n", - " group_git_usernames = []\n", - " for email in members:\n", - " try:\n", - " # try to get the git username for each student.\n", - " # not all students completed their quiz.\n", - " group_git_usernames.append(github_usernames[email])\n", - " except KeyError:\n", - " print(f\"{email}'s GitHub Username not found\")\n", - " repo = ggroup.create_group_repo(\n", - " repo_name=group_name,\n", - " collaborators=group_git_usernames,\n", - " permission=\"write\",\n", - " repo_template=\"COGS118A/group_template\",\n", - " rename_files={\n", - " \"Checkpoint_groupXXX.ipynb\": f\"Checkpoint_{group_name}.ipynb\",\n", - " \"FinalProject_groupXXX.ipynb\": f\"FinalProject_{group_name}.ipynb\",\n", - " \"Proposal_groupXXX.ipynb\": f\"Proposal_{group_name}.ipynb\"\n", - " },\n", - " private=False,\n", - " description=f\"COGS118A Final Project {group_name} Repository\",\n", - " team_slug=\"Instructors_Sp23\",\n", - " team_permission=\"admin\"\n", - " )\n", - " print(\"\")\n", - " repos.append(repo)" - ] - }, - { - "cell_type": "markdown", - "id": "573ddd71", - "metadata": {}, - "source": [ - "## Resent Invitations\n", - "\n", - "GitHub collaboration invites will be [expired automatically](https://docs.github.com/en/organizations/managing-membership-in-your-organization/inviting-users-to-join-your-organization#retrying-or-canceling-expired-invitations) when the user did not accept the invite after a certain period of time. After all the group repositories are created, the command [`GitHubGroup.resent_invitations_team_repos`](https://FleischerResearchLab.github.io/CanvasGroupy/api/github.html#githubgroup.resent_invitations_team_repos) will rescind all pending invitations and resent invitation to that collaborators. \n", - "\n", - "This command is particularly useful when managing a large volume of repositories as it painlessly re-validated and re-sent all pending invitations of all repositories under a team. We ran this command daily to constantly remind student to accept their GitHub invitations, until all students have a valid permission to the target repository." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6acb7b7d", - "metadata": { - "language": "python" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Repository AssignmentNotebooksSource_SP23 :\n", - "The list of pending invitation:\n", - "[]\n", - "Repository AssignmentNotebooks_SP23 :\n", - "The list of pending invitation:\n", - "[]\n", - "Repository DiscussionSectionNotebooks :\n", - "The list of pending invitation:\n", - "[]\n", - "Repository Dockerfiles :\n", - "The list of pending invitation:\n", - "[]\n", - "Repository Group001-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"demimao\"),\n", - " NamedUser(login=\"xiw013\"),\n", - " NamedUser(login=\"Ju-dyz\"),\n", - " NamedUser(login=\"TaraaHe\")]\n", - "demimao Invite Revoked \n", - "Added Collaborator: demimao to: Group001-SP23 with permission: write \n", - " Invite Resent to demimao \n", - "xiw013 Invite Revoked \n", - "Added Collaborator: xiw013 to: Group001-SP23 with permission: write \n", - " Invite Resent to xiw013 \n", - "Ju-dyz Invite Revoked \n", - "Added Collaborator: Ju-dyz to: Group001-SP23 with permission: write \n", - " Invite Resent to Ju-dyz \n", - "TaraaHe Invite Revoked \n", - "Added Collaborator: TaraaHe to: Group001-SP23 with permission: write \n", - " Invite Resent to TaraaHe \n", - "Repository Group002-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"jup006\"),\n", - " NamedUser(login=\"ssutharucsd\"),\n", - " NamedUser(login=\"connormcmanigal\"),\n", - " NamedUser(login=\"d3yu\")]\n", - "jup006 Invite Revoked \n", - "Added Collaborator: jup006 to: Group002-SP23 with permission: write \n", - " Invite Resent to jup006 \n", - "ssutharucsd Invite Revoked \n", - "Added Collaborator: ssutharucsd to: Group002-SP23 with permission: write \n", - " Invite Resent to ssutharucsd \n", - "connormcmanigal Invite Revoked \n", - "Added Collaborator: connormcmanigal to: Group002-SP23 with permission: write \n", - " Invite Resent to connormcmanigal \n", - "d3yu Invite Revoked \n", - "Added Collaborator: d3yu to: Group002-SP23 with permission: write \n", - " Invite Resent to d3yu \n", - "Repository Group003-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"scottieboyzhang\"), NamedUser(login=\"Orang1s\")]\n", - "scottieboyzhang Invite Revoked \n", - "Added Collaborator: scottieboyzhang to: Group003-SP23 with permission: write \n", - " Invite Resent to scottieboyzhang \n", - "Orang1s Invite Revoked \n", - "Added Collaborator: Orang1s to: Group003-SP23 with permission: write \n", - " Invite Resent to Orang1s \n", - "Repository Group004-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"CDavid99\"),\n", - " NamedUser(login=\"nilu0311\"),\n", - " NamedUser(login=\"g6zhu\")]\n", - "CDavid99 Invite Revoked \n", - "Added Collaborator: CDavid99 to: Group004-SP23 with permission: write \n", - " Invite Resent to CDavid99 \n", - "nilu0311 Invite Revoked \n", - "Added Collaborator: nilu0311 to: Group004-SP23 with permission: write \n", - " Invite Resent to nilu0311 \n", - "g6zhu Invite Revoked \n", - "Added Collaborator: g6zhu to: Group004-SP23 with permission: write \n", - " Invite Resent to g6zhu \n", - "Repository Group005-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"arth-shukla\"),\n", - " NamedUser(login=\"kchen283\"),\n", - " NamedUser(login=\"cdelira9\")]\n", - "arth-shukla Invite Revoked \n", - "Added Collaborator: arth-shukla to: Group005-SP23 with permission: write \n", - " Invite Resent to arth-shukla \n", - "kchen283 Invite Revoked \n", - "Added Collaborator: kchen283 to: Group005-SP23 with permission: write \n", - " Invite Resent to kchen283 \n", - "cdelira9 Invite Revoked \n", - "Added Collaborator: cdelira9 to: Group005-SP23 with permission: write \n", - " Invite Resent to cdelira9 \n", - "Repository Group006-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"qdh-2002\"), NamedUser(login=\"Chihhsinli\")]\n", - "qdh-2002 Invite Revoked \n", - "Added Collaborator: qdh-2002 to: Group006-SP23 with permission: write \n", - " Invite Resent to qdh-2002 \n", - "Chihhsinli Invite Revoked \n", - "Added Collaborator: Chihhsinli to: Group006-SP23 with permission: write \n", - " Invite Resent to Chihhsinli \n", - "Repository Group007-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"YunxiangChi\"),\n", - " NamedUser(login=\"Andrina-iris\"),\n", - " NamedUser(login=\"alien-invader\")]\n", - "YunxiangChi Invite Revoked \n", - "Added Collaborator: YunxiangChi to: Group007-SP23 with permission: write \n", - " Invite Resent to YunxiangChi \n", - "Andrina-iris Invite Revoked \n", - "Added Collaborator: Andrina-iris to: Group007-SP23 with permission: write \n", - " Invite Resent to Andrina-iris \n", - "alien-invader Invite Revoked \n", - "Added Collaborator: alien-invader to: Group007-SP23 with permission: write \n", - " Invite Resent to alien-invader \n", - "Repository Group008-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"wwjasperww\")]\n", - "wwjasperww Invite Revoked \n", - "Added Collaborator: wwjasperww to: Group008-SP23 with permission: write \n", - " Invite Resent to wwjasperww \n", - "Repository Group009-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"404EZRA\"), NamedUser(login=\"vvishnus\")]\n", - "404EZRA Invite Revoked \n", - "Added Collaborator: 404EZRA to: Group009-SP23 with permission: write \n", - " Invite Resent to 404EZRA \n", - "vvishnus Invite Revoked \n", - "Added Collaborator: vvishnus to: Group009-SP23 with permission: write \n", - " Invite Resent to vvishnus \n", - "Repository Group010-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"idereknguyen\"),\n", - " NamedUser(login=\"jaysunl\"),\n", - " NamedUser(login=\"tjamalcodes\"),\n", - " NamedUser(login=\"strawhatwilson23\")]\n", - "idereknguyen Invite Revoked \n", - "Added Collaborator: idereknguyen to: Group010-SP23 with permission: write \n", - " Invite Resent to idereknguyen \n", - "jaysunl Invite Revoked \n", - "Added Collaborator: jaysunl to: Group010-SP23 with permission: write \n", - " Invite Resent to jaysunl \n", - "tjamalcodes Invite Revoked \n", - "Added Collaborator: tjamalcodes to: Group010-SP23 with permission: write \n", - " Invite Resent to tjamalcodes \n", - "strawhatwilson23 Invite Revoked \n", - "Added Collaborator: strawhatwilson23 to: Group010-SP23 with permission: write \n", - " Invite Resent to strawhatwilson23 \n", - "Repository Group011-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"khchuang12\"),\n", - " NamedUser(login=\"nrejai\"),\n", - " NamedUser(login=\"emdavis02\")]\n", - "khchuang12 Invite Revoked \n", - "Added Collaborator: khchuang12 to: Group011-SP23 with permission: write \n", - " Invite Resent to khchuang12 \n", - "nrejai Invite Revoked \n", - "Added Collaborator: nrejai to: Group011-SP23 with permission: write \n", - " Invite Resent to nrejai \n", - "emdavis02 Invite Revoked \n", - "Added Collaborator: emdavis02 to: Group011-SP23 with permission: write \n", - " Invite Resent to emdavis02 \n", - "Repository Group012-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"githubharsono\"),\n", - " NamedUser(login=\"JJSanchez23\"),\n", - " NamedUser(login=\"antarasengupta26\")]\n", - "githubharsono Invite Revoked \n", - "Added Collaborator: githubharsono to: Group012-SP23 with permission: write \n", - " Invite Resent to githubharsono \n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "JJSanchez23 Invite Revoked \n", - "Added Collaborator: JJSanchez23 to: Group012-SP23 with permission: write \n", - " Invite Resent to JJSanchez23 \n", - "antarasengupta26 Invite Revoked \n", - "Added Collaborator: antarasengupta26 to: Group012-SP23 with permission: write \n", - " Invite Resent to antarasengupta26 \n", - "Repository Group013-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"Sean1572\"), NamedUser(login=\"jnhuang02\")]\n", - "Sean1572 Invite Revoked \n", - "Added Collaborator: Sean1572 to: Group013-SP23 with permission: write \n", - " Invite Resent to Sean1572 \n", - "jnhuang02 Invite Revoked \n", - "Added Collaborator: jnhuang02 to: Group013-SP23 with permission: write \n", - " Invite Resent to jnhuang02 \n", - "Repository Group014-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"ericzyl\"),\n", - " NamedUser(login=\"50ShadesOfShawn\"),\n", - " NamedUser(login=\"villafun\")]\n", - "ericzyl Invite Revoked \n", - "Added Collaborator: ericzyl to: Group014-SP23 with permission: write \n", - " Invite Resent to ericzyl \n", - "50ShadesOfShawn Invite Revoked \n", - "Added Collaborator: 50ShadesOfShawn to: Group014-SP23 with permission: write \n", - " Invite Resent to 50ShadesOfShawn \n", - "villafun Invite Revoked \n", - "Added Collaborator: villafun to: Group014-SP23 with permission: write \n", - " Invite Resent to villafun \n", - "Repository Group015-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"nikothomas\"),\n", - " NamedUser(login=\"abi2020\"),\n", - " NamedUser(login=\"arjunanna\"),\n", - " NamedUser(login=\"sdsilva1\")]\n", - "nikothomas Invite Revoked \n", - "Added Collaborator: nikothomas to: Group015-SP23 with permission: write \n", - " Invite Resent to nikothomas \n", - "abi2020 Invite Revoked \n", - "Added Collaborator: abi2020 to: Group015-SP23 with permission: write \n", - " Invite Resent to abi2020 \n", - "arjunanna Invite Revoked \n", - "Added Collaborator: arjunanna to: Group015-SP23 with permission: write \n", - " Invite Resent to arjunanna \n", - "sdsilva1 Invite Revoked \n", - "Added Collaborator: sdsilva1 to: Group015-SP23 with permission: write \n", - " Invite Resent to sdsilva1 \n", - "Repository Group016-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"shendu11\"),\n", - " NamedUser(login=\"EthanHu0\"),\n", - " NamedUser(login=\"QilunLiu5216\"),\n", - " NamedUser(login=\"Y3GUO\"),\n", - " NamedUser(login=\"claireZHL\")]\n", - "shendu11 Invite Revoked \n", - "Added Collaborator: shendu11 to: Group016-SP23 with permission: write \n", - " Invite Resent to shendu11 \n", - "EthanHu0 Invite Revoked \n", - "Added Collaborator: EthanHu0 to: Group016-SP23 with permission: write \n", - " Invite Resent to EthanHu0 \n", - "QilunLiu5216 Invite Revoked \n", - "Added Collaborator: QilunLiu5216 to: Group016-SP23 with permission: write \n", - " Invite Resent to QilunLiu5216 \n", - "Y3GUO Invite Revoked \n", - "Added Collaborator: Y3GUO to: Group016-SP23 with permission: write \n", - " Invite Resent to Y3GUO \n", - "claireZHL Invite Revoked \n", - "Added Collaborator: claireZHL to: Group016-SP23 with permission: write \n", - " Invite Resent to claireZHL \n", - "Repository Group017-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"AstuteFern\"),\n", - " NamedUser(login=\"sreetama02\"),\n", - " NamedUser(login=\"pabbi5\")]\n", - "AstuteFern Invite Revoked \n", - "Added Collaborator: AstuteFern to: Group017-SP23 with permission: write \n", - " Invite Resent to AstuteFern \n", - "sreetama02 Invite Revoked \n", - "Added Collaborator: sreetama02 to: Group017-SP23 with permission: write \n", - " Invite Resent to sreetama02 \n", - "pabbi5 Invite Revoked \n", - "Added Collaborator: pabbi5 to: Group017-SP23 with permission: write \n", - " Invite Resent to pabbi5 \n", - "Repository Group018-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"notSaranshMalik\"),\n", - " NamedUser(login=\"mnodini\"),\n", - " NamedUser(login=\"Maryamkusman\"),\n", - " NamedUser(login=\"ajcagle8\")]\n", - "notSaranshMalik Invite Revoked \n", - "Added Collaborator: notSaranshMalik to: Group018-SP23 with permission: write \n", - " Invite Resent to notSaranshMalik \n", - "mnodini Invite Revoked \n", - "Added Collaborator: mnodini to: Group018-SP23 with permission: write \n", - " Invite Resent to mnodini \n", - "Maryamkusman Invite Revoked \n", - "Added Collaborator: Maryamkusman to: Group018-SP23 with permission: write \n", - " Invite Resent to Maryamkusman \n", - "ajcagle8 Invite Revoked \n", - "Added Collaborator: ajcagle8 to: Group018-SP23 with permission: write \n", - " Invite Resent to ajcagle8 \n", - "Repository Group019-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"nathansit\"),\n", - " NamedUser(login=\"SSingh44343\"),\n", - " NamedUser(login=\"DaimengSun\")]\n", - "nathansit Invite Revoked \n", - "Added Collaborator: nathansit to: Group019-SP23 with permission: write \n", - " Invite Resent to nathansit \n", - "SSingh44343 Invite Revoked \n", - "Added Collaborator: SSingh44343 to: Group019-SP23 with permission: write \n", - " Invite Resent to SSingh44343 \n", - "DaimengSun Invite Revoked \n", - "Added Collaborator: DaimengSun to: Group019-SP23 with permission: write \n", - " Invite Resent to DaimengSun \n", - "Repository Group020-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"ashesh8500\"),\n", - " NamedUser(login=\"ATrapenard\"),\n", - " NamedUser(login=\"meghapareek2003\")]\n", - "ashesh8500 Invite Revoked \n", - "Added Collaborator: ashesh8500 to: Group020-SP23 with permission: write \n", - " Invite Resent to ashesh8500 \n", - "ATrapenard Invite Revoked \n", - "Added Collaborator: ATrapenard to: Group020-SP23 with permission: write \n", - " Invite Resent to ATrapenard \n", - "meghapareek2003 Invite Revoked \n", - "Added Collaborator: meghapareek2003 to: Group020-SP23 with permission: write \n", - " Invite Resent to meghapareek2003 \n", - "Repository Group021-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"JasonKrentsel\"),\n", - " NamedUser(login=\"shantellemeganserafin\"),\n", - " NamedUser(login=\"orangejustin\"),\n", - " NamedUser(login=\"jmlai08\")]\n", - "JasonKrentsel Invite Revoked \n", - "Added Collaborator: JasonKrentsel to: Group021-SP23 with permission: write \n", - " Invite Resent to JasonKrentsel \n", - "shantellemeganserafin Invite Revoked \n", - "Added Collaborator: shantellemeganserafin to: Group021-SP23 with permission: write \n", - " Invite Resent to shantellemeganserafin \n", - "orangejustin Invite Revoked \n", - "Added Collaborator: orangejustin to: Group021-SP23 with permission: write \n", - " Invite Resent to orangejustin \n", - "jmlai08 Invite Revoked \n", - "Added Collaborator: jmlai08 to: Group021-SP23 with permission: write \n", - " Invite Resent to jmlai08 \n", - "Repository Group022-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"anchen31\"), NamedUser(login=\"nggalen\")]\n", - "anchen31 Invite Revoked \n", - "Added Collaborator: anchen31 to: Group022-SP23 with permission: write \n", - " Invite Resent to anchen31 \n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "nggalen Invite Revoked \n", - "Added Collaborator: nggalen to: Group022-SP23 with permission: write \n", - " Invite Resent to nggalen \n", - "Repository Group023-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"VigneshJ14\"),\n", - " NamedUser(login=\"helclp\"),\n", - " NamedUser(login=\"rioak\"),\n", - " NamedUser(login=\"chrishrochez\"),\n", - " NamedUser(login=\"kpstern\")]\n", - "VigneshJ14 Invite Revoked \n", - "Added Collaborator: VigneshJ14 to: Group023-SP23 with permission: write \n", - " Invite Resent to VigneshJ14 \n", - "helclp Invite Revoked \n", - "Added Collaborator: helclp to: Group023-SP23 with permission: write \n", - " Invite Resent to helclp \n", - "rioak Invite Revoked \n", - "Added Collaborator: rioak to: Group023-SP23 with permission: write \n", - " Invite Resent to rioak \n", - "chrishrochez Invite Revoked \n", - "Added Collaborator: chrishrochez to: Group023-SP23 with permission: write \n", - " Invite Resent to chrishrochez \n", - "kpstern Invite Revoked \n", - "Added Collaborator: kpstern to: Group023-SP23 with permission: write \n", - " Invite Resent to kpstern \n", - "Repository Group024-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"MoMo339610\"),\n", - " NamedUser(login=\"ccaban6\"),\n", - " NamedUser(login=\"Nakshatra120\"),\n", - " NamedUser(login=\"sim-anna\")]\n", - "MoMo339610 Invite Revoked \n", - "Added Collaborator: MoMo339610 to: Group024-SP23 with permission: write \n", - " Invite Resent to MoMo339610 \n", - "ccaban6 Invite Revoked \n", - "Added Collaborator: ccaban6 to: Group024-SP23 with permission: write \n", - " Invite Resent to ccaban6 \n", - "Nakshatra120 Invite Revoked \n", - "Added Collaborator: Nakshatra120 to: Group024-SP23 with permission: write \n", - " Invite Resent to Nakshatra120 \n", - "sim-anna Invite Revoked \n", - "Added Collaborator: sim-anna to: Group024-SP23 with permission: write \n", - " Invite Resent to sim-anna \n", - "Repository Group025-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"Shayfe\"),\n", - " NamedUser(login=\"jenniferwong1808\"),\n", - " NamedUser(login=\"belindayan1000\"),\n", - " NamedUser(login=\"hibask\")]\n", - "Shayfe Invite Revoked \n", - "Added Collaborator: Shayfe to: Group025-SP23 with permission: write \n", - " Invite Resent to Shayfe \n", - "jenniferwong1808 Invite Revoked \n", - "Added Collaborator: jenniferwong1808 to: Group025-SP23 with permission: write \n", - " Invite Resent to jenniferwong1808 \n", - "belindayan1000 Invite Revoked \n", - "Added Collaborator: belindayan1000 to: Group025-SP23 with permission: write \n", - " Invite Resent to belindayan1000 \n", - "hibask Invite Revoked \n", - "Added Collaborator: hibask to: Group025-SP23 with permission: write \n", - " Invite Resent to hibask \n", - "Repository Group026-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"e81786\"),\n", - " NamedUser(login=\"hyun04p\"),\n", - " NamedUser(login=\"Yuanzhen-Zhu\"),\n", - " NamedUser(login=\"aaolivas\")]\n", - "e81786 Invite Revoked \n", - "Added Collaborator: e81786 to: Group026-SP23 with permission: write \n", - " Invite Resent to e81786 \n", - "hyun04p Invite Revoked \n", - "Added Collaborator: hyun04p to: Group026-SP23 with permission: write \n", - " Invite Resent to hyun04p \n", - "Yuanzhen-Zhu Invite Revoked \n", - "Added Collaborator: Yuanzhen-Zhu to: Group026-SP23 with permission: write \n", - " Invite Resent to Yuanzhen-Zhu \n", - "aaolivas Invite Revoked \n", - "Added Collaborator: aaolivas to: Group026-SP23 with permission: write \n", - " Invite Resent to aaolivas \n", - "Repository Group027-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"Jddeleon1981\"),\n", - " NamedUser(login=\"natenyul\"),\n", - " NamedUser(login=\"jifsus\")]\n", - "Jddeleon1981 Invite Revoked \n", - "Added Collaborator: Jddeleon1981 to: Group027-SP23 with permission: write \n", - " Invite Resent to Jddeleon1981 \n", - "natenyul Invite Revoked \n", - "Added Collaborator: natenyul to: Group027-SP23 with permission: write \n", - " Invite Resent to natenyul \n", - "jifsus Invite Revoked \n", - "Added Collaborator: jifsus to: Group027-SP23 with permission: write \n", - " Invite Resent to jifsus \n", - "Repository Group028-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"mabdilahCSE\"),\n", - " NamedUser(login=\"kylenakai\"),\n", - " NamedUser(login=\"valar23\"),\n", - " NamedUser(login=\"johnpaulonza\")]\n", - "mabdilahCSE Invite Revoked \n", - "Added Collaborator: mabdilahCSE to: Group028-SP23 with permission: write \n", - " Invite Resent to mabdilahCSE \n", - "kylenakai Invite Revoked \n", - "Added Collaborator: kylenakai to: Group028-SP23 with permission: write \n", - " Invite Resent to kylenakai \n", - "valar23 Invite Revoked \n", - "Added Collaborator: valar23 to: Group028-SP23 with permission: write \n", - " Invite Resent to valar23 \n", - "johnpaulonza Invite Revoked \n", - "Added Collaborator: johnpaulonza to: Group028-SP23 with permission: write \n", - " Invite Resent to johnpaulonza \n", - "Repository Group029-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"joshsalce\"),\n", - " NamedUser(login=\"lmitbo\"),\n", - " NamedUser(login=\"LukeSkerrett\"),\n", - " NamedUser(login=\"jubamadu\")]\n", - "joshsalce Invite Revoked \n", - "Added Collaborator: joshsalce to: Group029-SP23 with permission: write \n", - " Invite Resent to joshsalce \n", - "lmitbo Invite Revoked \n", - "Added Collaborator: lmitbo to: Group029-SP23 with permission: write \n", - " Invite Resent to lmitbo \n", - "LukeSkerrett Invite Revoked \n", - "Added Collaborator: LukeSkerrett to: Group029-SP23 with permission: write \n", - " Invite Resent to LukeSkerrett \n", - "jubamadu Invite Revoked \n", - "Added Collaborator: jubamadu to: Group029-SP23 with permission: write \n", - " Invite Resent to jubamadu \n", - "Repository Group030-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"kmbnino\"),\n", - " NamedUser(login=\"hiiminbush\"),\n", - " NamedUser(login=\"RafferyChen\"),\n", - " NamedUser(login=\"bonzonwin\")]\n", - "kmbnino Invite Revoked \n", - "Added Collaborator: kmbnino to: Group030-SP23 with permission: write \n", - " Invite Resent to kmbnino \n", - "hiiminbush Invite Revoked \n", - "Added Collaborator: hiiminbush to: Group030-SP23 with permission: write \n", - " Invite Resent to hiiminbush \n", - "RafferyChen Invite Revoked \n", - "Added Collaborator: RafferyChen to: Group030-SP23 with permission: write \n", - " Invite Resent to RafferyChen \n", - "bonzonwin Invite Revoked \n", - "Added Collaborator: bonzonwin to: Group030-SP23 with permission: write \n", - " Invite Resent to bonzonwin \n", - "Repository Group031-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"kendrick010\"), NamedUser(login=\"getpakt\")]\n", - "kendrick010 Invite Revoked \n", - "Added Collaborator: kendrick010 to: Group031-SP23 with permission: write \n", - " Invite Resent to kendrick010 \n", - "getpakt Invite Revoked \n", - "Added Collaborator: getpakt to: Group031-SP23 with permission: write \n", - " Invite Resent to getpakt \n", - "Repository Group032-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"CharlesXu-Jingyue\"),\n", - " NamedUser(login=\"hinyzee\"),\n", - " NamedUser(login=\"Zachary-chao\"),\n", - " NamedUser(login=\"smurase\")]\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CharlesXu-Jingyue Invite Revoked \n", - "Added Collaborator: CharlesXu-Jingyue to: Group032-SP23 with permission: write \n", - " Invite Resent to CharlesXu-Jingyue \n", - "hinyzee Invite Revoked \n", - "Added Collaborator: hinyzee to: Group032-SP23 with permission: write \n", - " Invite Resent to hinyzee \n", - "Zachary-chao Invite Revoked \n", - "Added Collaborator: Zachary-chao to: Group032-SP23 with permission: write \n", - " Invite Resent to Zachary-chao \n", - "smurase Invite Revoked \n", - "Added Collaborator: smurase to: Group032-SP23 with permission: write \n", - " Invite Resent to smurase \n", - "Repository Group033-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"jason886595\"),\n", - " NamedUser(login=\"cqrnik\"),\n", - " NamedUser(login=\"AnyaBoo\"),\n", - " NamedUser(login=\"areenlu\"),\n", - " NamedUser(login=\"TydenRucker\")]\n", - "jason886595 Invite Revoked \n", - "Added Collaborator: jason886595 to: Group033-SP23 with permission: write \n", - " Invite Resent to jason886595 \n", - "cqrnik Invite Revoked \n", - "Added Collaborator: cqrnik to: Group033-SP23 with permission: write \n", - " Invite Resent to cqrnik \n", - "AnyaBoo Invite Revoked \n", - "Added Collaborator: AnyaBoo to: Group033-SP23 with permission: write \n", - " Invite Resent to AnyaBoo \n", - "areenlu Invite Revoked \n", - "Added Collaborator: areenlu to: Group033-SP23 with permission: write \n", - " Invite Resent to areenlu \n", - "TydenRucker Invite Revoked \n", - "Added Collaborator: TydenRucker to: Group033-SP23 with permission: write \n", - " Invite Resent to TydenRucker \n", - "Repository Group034-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"vinaypillai\"),\n", - " NamedUser(login=\"rchaklas\"),\n", - " NamedUser(login=\"kavyaabalaji\"),\n", - " NamedUser(login=\"Juan2002V\")]\n", - "vinaypillai Invite Revoked \n", - "Added Collaborator: vinaypillai to: Group034-SP23 with permission: write \n", - " Invite Resent to vinaypillai \n", - "rchaklas Invite Revoked \n", - "Added Collaborator: rchaklas to: Group034-SP23 with permission: write \n", - " Invite Resent to rchaklas \n", - "kavyaabalaji Invite Revoked \n", - "Added Collaborator: kavyaabalaji to: Group034-SP23 with permission: write \n", - " Invite Resent to kavyaabalaji \n", - "Juan2002V Invite Revoked \n", - "Added Collaborator: Juan2002V to: Group034-SP23 with permission: write \n", - " Invite Resent to Juan2002V \n", - "Repository Group035-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"hyperburn777\"),\n", - " NamedUser(login=\"CristianAH\"),\n", - " NamedUser(login=\"rihusedesign\")]\n", - "hyperburn777 Invite Revoked \n", - "Added Collaborator: hyperburn777 to: Group035-SP23 with permission: write \n", - " Invite Resent to hyperburn777 \n", - "CristianAH Invite Revoked \n", - "Added Collaborator: CristianAH to: Group035-SP23 with permission: write \n", - " Invite Resent to CristianAH \n", - "rihusedesign Invite Revoked \n", - "Added Collaborator: rihusedesign to: Group035-SP23 with permission: write \n", - " Invite Resent to rihusedesign \n", - "Repository Group036-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"kohlir2020\"),\n", - " NamedUser(login=\"dhavaljjani\"),\n", - " NamedUser(login=\"Mkhlf\"),\n", - " NamedUser(login=\"esti28\")]\n", - "kohlir2020 Invite Revoked \n", - "Added Collaborator: kohlir2020 to: Group036-SP23 with permission: write \n", - " Invite Resent to kohlir2020 \n", - "dhavaljjani Invite Revoked \n", - "Added Collaborator: dhavaljjani to: Group036-SP23 with permission: write \n", - " Invite Resent to dhavaljjani \n", - "Mkhlf Invite Revoked \n", - "Added Collaborator: Mkhlf to: Group036-SP23 with permission: write \n", - " Invite Resent to Mkhlf \n", - "esti28 Invite Revoked \n", - "Added Collaborator: esti28 to: Group036-SP23 with permission: write \n", - " Invite Resent to esti28 \n", - "Repository Group037-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"gyuj\"),\n", - " NamedUser(login=\"NickAzp\"),\n", - " NamedUser(login=\"kleumas\")]\n", - "gyuj Invite Revoked \n", - "Added Collaborator: gyuj to: Group037-SP23 with permission: write \n", - " Invite Resent to gyuj \n", - "NickAzp Invite Revoked \n", - "Added Collaborator: NickAzp to: Group037-SP23 with permission: write \n", - " Invite Resent to NickAzp \n", - "kleumas Invite Revoked \n", - "Added Collaborator: kleumas to: Group037-SP23 with permission: write \n", - " Invite Resent to kleumas \n", - "Repository Group038-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"crickwang\"),\n", - " NamedUser(login=\"the-bruz\"),\n", - " NamedUser(login=\"m-sherrick\")]\n", - "crickwang Invite Revoked \n", - "Added Collaborator: crickwang to: Group038-SP23 with permission: write \n", - " Invite Resent to crickwang \n", - "the-bruz Invite Revoked \n", - "Added Collaborator: the-bruz to: Group038-SP23 with permission: write \n", - " Invite Resent to the-bruz \n", - "m-sherrick Invite Revoked \n", - "Added Collaborator: m-sherrick to: Group038-SP23 with permission: write \n", - " Invite Resent to m-sherrick \n", - "Repository Group039-SP23 :\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"dannyr742\"),\n", - " NamedUser(login=\"clarissagtz\"),\n", - " NamedUser(login=\"z8jiang\"),\n", - " NamedUser(login=\"jtaolan\")]\n", - "dannyr742 Invite Revoked \n", - "Added Collaborator: dannyr742 to: Group039-SP23 with permission: write \n", - " Invite Resent to dannyr742 \n", - "clarissagtz Invite Revoked \n", - "Added Collaborator: clarissagtz to: Group039-SP23 with permission: write \n", - " Invite Resent to clarissagtz \n", - "z8jiang Invite Revoked \n", - "Added Collaborator: z8jiang to: Group039-SP23 with permission: write \n", - " Invite Resent to z8jiang \n", - "jtaolan Invite Revoked \n", - "Added Collaborator: jtaolan to: Group039-SP23 with permission: write \n", - " Invite Resent to jtaolan \n", - "Repository Lectures :\n", - "The list of pending invitation:\n", - "[]\n", - "Repository Notebooks :\n", - "The list of pending invitation:\n", - "[]\n" - ] - } - ], - "source": [ - "ggroup.resent_invitations_team_repos(\n", - " team_slug=\"Instructors_Sp23\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "6cd203d4", - "metadata": {}, - "source": [ - "## The End of the Workflow\n", - "\n", - "If you still have concerns, please reach out via GitHub Issue (on the RHS bar) or reach out me directly via email: " - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "python3", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/_proc/tutorials/create template issues.ipynb b/_proc/tutorials/create template issues.ipynb deleted file mode 100644 index fe29725..0000000 --- a/_proc/tutorials/create template issues.ipynb +++ /dev/null @@ -1,125 +0,0 @@ -{ - "cells": [ - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "---\n", - "description: Create modifiable and gradable GitHub issues for project grading\n", - "output-file: create template issues.html\n", - "title: Populate Grading Rubric Template on GitHub Repositories\n", - "\n", - "---\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Successfully Authenticated. GitHub account: scott-yj-yang \n", - "Target Organization Set: COGS118A \n" - ] - } - ], - "source": [ - "import time\n", - "from CanvasGroupy import *\n", - "credentials_fp = \"../credentials.json\"\n", - "ghg = GitHubGroup(credentials_fp, org=\"COGS118A\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "In the repo: Group021-SP23,\n", - "Issue Project Proposal Feedback Created!\n", - "In the repo: Group022-SP23,\n", - "Issue Project Proposal Feedback Created!\n", - "In the repo: Group023-SP23,\n", - "Issue Project Proposal Feedback Created!\n", - "In the repo: Group024-SP23,\n", - "Issue Project Proposal Feedback Created!\n", - "In the repo: Group025-SP23,\n", - "Issue Project Proposal Feedback Created!\n", - "In the repo: Group026-SP23,\n", - "Issue Project Proposal Feedback Created!\n", - "In the repo: Group027-SP23,\n", - "Issue Project Proposal Feedback Created!\n", - "In the repo: Group028-SP23,\n", - "Issue Project Proposal Feedback Created!\n", - "In the repo: Group029-SP23,\n", - "Issue Project Proposal Feedback Created!\n", - "In the repo: Group030-SP23,\n", - "Issue Project Proposal Feedback Created!\n", - "In the repo: Group031-SP23,\n", - "Issue Project Proposal Feedback Created!\n", - "In the repo: Group032-SP23,\n", - "Issue Project Proposal Feedback Created!\n", - "In the repo: Group033-SP23,\n", - "Issue Project Proposal Feedback Created!\n", - "In the repo: Group034-SP23,\n", - "Issue Project Proposal Feedback Created!\n", - "In the repo: Group035-SP23,\n", - "Issue Project Proposal Feedback Created!\n", - "In the repo: Group036-SP23,\n", - "Issue Project Proposal Feedback Created!\n", - "In the repo: Group037-SP23,\n", - "Issue Project Proposal Feedback Created!\n", - "In the repo: Group038-SP23,\n", - "Issue Project Proposal Feedback Created!\n", - "In the repo: Group039-SP23,\n", - "Issue Project Proposal Feedback Created!\n" - ] - } - ], - "source": [ - "for i in range(1, 40):\n", - " repo_name = f\"Group{i:03}-SP23\"\n", - " repo = ghg.get_repo(repo_name)\n", - " ghg.create_issue_from_md(repo, \"../CanvasGroupy/nbs/feedback_template/proposal_feedback.md\")\n", - " time.sleep(1)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "language": "python" - }, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "python3", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/environment.yml b/environment.yml deleted file mode 100644 index cda4d70..0000000 --- a/environment.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: CanvasGroupy - -channels: - - anaconda - - conda-forge - - defaults - -dependencies: - - python=3.10.9 - - numpy - - toolz - - matplotlib - - dill - - pandas - - jupyterlab - - partd - - nbdev - - pip - - pip: - - git+https://github.com/tdimiduk/groupeng.git - - PyGithub - - canvasapi diff --git a/nbs/_quarto.yml b/nbs/_quarto.yml index d69fe71..8254f0a 100644 --- a/nbs/_quarto.yml +++ b/nbs/_quarto.yml @@ -1,5 +1,6 @@ project: type: website + output-dir: _docs format: html: @@ -8,6 +9,11 @@ format: toc: true website: + title: "CanvasGroupy" + site-url: "https://FleischerResearchLab.github.io/CanvasGroupy" + description: "Canvas Grouping Python Script" + repo-branch: main + repo-url: "https://github.com/FleischerResearchLab/CanvasGroupy" twitter-card: true open-graph: true repo-actions: [issue] @@ -24,5 +30,3 @@ website: contents: api/* - section: Project Feedback Template contents: feedback_template/* - -metadata-files: [nbdev.yml] \ No newline at end of file diff --git a/nbs/api/01_GroupEng_assign.ipynb b/nbs/api/01_GroupEng_assign.ipynb index 6faf0c8..f9a5ecb 100644 --- a/nbs/api/01_GroupEng_assign.ipynb +++ b/nbs/api/01_GroupEng_assign.ipynb @@ -15,17 +15,6 @@ "metadata": {}, "outputs": [], "source": [ - "#| default_exp assign" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#| hide\n", - "from nbdev.showdoc import *\n", "import os\n", "import pandas as pd\n", "import shutil" @@ -37,7 +26,6 @@ "metadata": {}, "outputs": [], "source": [ - "#| export\n", "import GroupEng\n", "import canvasapi\n", "import github\n", @@ -50,7 +38,6 @@ "metadata": {}, "outputs": [], "source": [ - "#| export\n", "class AssignGroup:\n", " def __init__(self,\n", " ghg: GitHubGroup, # authenticated GitHub object\n", @@ -158,43 +145,6 @@ "## API" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/markdown": "---\n\n[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/assign.py#L29){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n\n### AssignGroup.assign_groups\n\n> AssignGroup.assign_groups (groupeng_config:str,\n> assign_canvas_group=False,\n> create_gh_repo=False, username_quiz_id=-1,\n> in_group_category='', suffix='')\n\n| | **Type** | **Default** | **Details** |\n| -- | -------- | ----------- | ----------- |\n| groupeng_config | str | | Directory for the GroupEng config yml file |\n| assign_canvas_group | bool | False | directly assign canvas groups |\n| create_gh_repo | bool | False | directly create GitHub repos |\n| username_quiz_id | int | -1 | username quiz id from canvas course |\n| in_group_category | str | | specify which group category the group belongs to |\n| suffix | str | | suffix to the group name |\n| **Returns** | **(, )** | | **Status and output directory of the compiled file.** |", - "text/plain": "---\n\n[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/assign.py#L29){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n\n### AssignGroup.assign_groups\n\n> AssignGroup.assign_groups (groupeng_config:str,\n> assign_canvas_group=False,\n> create_gh_repo=False, username_quiz_id=-1,\n> in_group_category='', suffix='')\n\n| | **Type** | **Default** | **Details** |\n| -- | -------- | ----------- | ----------- |\n| groupeng_config | str | | Directory for the GroupEng config yml file |\n| assign_canvas_group | bool | False | directly assign canvas groups |\n| create_gh_repo | bool | False | directly create GitHub repos |\n| username_quiz_id | int | -1 | username quiz id from canvas course |\n| in_group_category | str | | specify which group category the group belongs to |\n| suffix | str | | suffix to the group name |\n| **Returns** | **(, )** | | **Status and output directory of the compiled file.** |" - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "show_doc(AssignGroup.assign_groups)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "show_doc(AssignGroup.create_canvas_group)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "show_doc(AssignGroup.create_github_group)" - ] - }, { "cell_type": "code", "execution_count": null, @@ -322,16 +272,6 @@ "source": [ "The false means that at least one requirement is not satisfied. We can take a look at the file that was generated." ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#| hide\n", - "import nbdev; nbdev.nbdev_export()" - ] } ], "metadata": { diff --git a/nbs/api/02_github.ipynb b/nbs/api/02_github.ipynb index 6834590..b2437a4 100644 --- a/nbs/api/02_github.ipynb +++ b/nbs/api/02_github.ipynb @@ -15,27 +15,6 @@ "metadata": {}, "outputs": [], "source": [ - "#| default_exp github" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#| hide\n", - "from nbdev.showdoc import *\n", - "from fastcore.test import *" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#| export\n", "from github import Github\n", "import github\n", "import json\n", @@ -51,8 +30,6 @@ "metadata": {}, "outputs": [], "source": [ - "#| export\n", - "#| hide\n", "class bcolors:\n", " HEADER = '\\033[95m'\n", " OKBLUE = '\\033[94m'\n", @@ -71,7 +48,6 @@ "metadata": {}, "outputs": [], "source": [ - "#| export\n", "class GitHubGroup:\n", " def __init__(self,\n", " credentials_fp=\"\", # the file path to the credential json\n", @@ -381,49 +357,6 @@ "Optionally, you can instansiate a GitHubGroup object and authenticate yourself by calling the following method." ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "### GitHubGroup.auth_github\n", - "\n", - "> GitHubGroup.auth_github (credentials_fp:str)\n", - "\n", - "Authenticate GitHub account with the provided credentials\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| credentials_fp | str | the personal access token generated at GitHub Settings |" - ], - "text/plain": [ - "---\n", - "\n", - "### GitHubGroup.auth_github\n", - "\n", - "> GitHubGroup.auth_github (credentials_fp:str)\n", - "\n", - "Authenticate GitHub account with the provided credentials\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| credentials_fp | str | the personal access token generated at GitHub Settings |" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "show_doc(GitHubGroup.auth_github)" - ] - }, { "cell_type": "code", "execution_count": null, @@ -451,53 +384,6 @@ "Usually, you want to create students repositories under a course GitHub organization. To set the target organization, you can call the following function." ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/gh_group.py#L55){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.set_org\n", - "\n", - "> GitHubGroup.set_org (org:str)\n", - "\n", - "Set the target organization for repo creation\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| org | str | the target organization name |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/gh_group.py#L55){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.set_org\n", - "\n", - "> GitHubGroup.set_org (org:str)\n", - "\n", - "Set the target organization for repo creation\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| org | str | the target organization name |" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "show_doc(GitHubGroup.set_org)" - ] - }, { "cell_type": "code", "execution_count": null, @@ -522,87 +408,6 @@ "# Create GitHub Group in one Call" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/gh_group.py#L247){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.create_group_repo\n", - "\n", - "> GitHubGroup.create_group_repo (repo_name:str,\n", - "> collaborators:[],\n", - "> permission:str, rename_files={},\n", - "> repo_template='', private=True,\n", - "> description='', team_slug='',\n", - "> team_permission='', feedback_dir=False,\n", - "> feedback_template_fp='')\n", - "\n", - "Create a Group Repository\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| repo_name | str | | group repository name |\n", - "| collaborators | [] | | list of collaborators GitHub id |\n", - "| permission | str | | the permission of collaborators. `pull`, `push` or `admin` |\n", - "| rename_files | dict | {} | dictionary of files renames {:} |\n", - "| repo_template | str | | If empty string, an empty repo will be created. Put in the format of \"/\" |\n", - "| private | bool | True | visibility of the created repository |\n", - "| description | str | | description for the GitHub repository |\n", - "| team_slug | str | | team slug, add to this repo |\n", - "| team_permission | str | | team permission to this repository `pull`, `push` or `admin` |\n", - "| feedback_dir | bool | False | whether to create a feedback directory for each repository created |\n", - "| feedback_template_fp | str | | the directory of the feedback template |\n", - "| **Returns** | **Repository** | | **created repository** |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/gh_group.py#L247){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.create_group_repo\n", - "\n", - "> GitHubGroup.create_group_repo (repo_name:str,\n", - "> collaborators:[],\n", - "> permission:str, rename_files={},\n", - "> repo_template='', private=True,\n", - "> description='', team_slug='',\n", - "> team_permission='', feedback_dir=False,\n", - "> feedback_template_fp='')\n", - "\n", - "Create a Group Repository\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| repo_name | str | | group repository name |\n", - "| collaborators | [] | | list of collaborators GitHub id |\n", - "| permission | str | | the permission of collaborators. `pull`, `push` or `admin` |\n", - "| rename_files | dict | {} | dictionary of files renames {:} |\n", - "| repo_template | str | | If empty string, an empty repo will be created. Put in the format of \"/\" |\n", - "| private | bool | True | visibility of the created repository |\n", - "| description | str | | description for the GitHub repository |\n", - "| team_slug | str | | team slug, add to this repo |\n", - "| team_permission | str | | team permission to this repository `pull`, `push` or `admin` |\n", - "| feedback_dir | bool | False | whether to create a feedback directory for each repository created |\n", - "| feedback_template_fp | str | | the directory of the feedback template |\n", - "| **Returns** | **Repository** | | **created repository** |" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "show_doc(GitHubGroup.create_group_repo)" - ] - }, { "cell_type": "code", "execution_count": null, @@ -646,7 +451,6 @@ "metadata": {}, "outputs": [], "source": [ - "#| hide\n", "# take down the repo that we just created\n", "repo.delete()" ] @@ -666,65 +470,6 @@ "`personal_account` argument controls the location of the repository creation. If set to `False` (default), it will create repository in the target organization. If set to `True`, the new repository will be created in the personal GitHub account." ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/gh_group.py#L63){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.create_repo\n", - "\n", - "> GitHubGroup.create_repo (repo_name:str, repo_template='', private=True,\n", - "> description='', personal_account=False)\n", - "\n", - "Create a repository, either blank, or from a template\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| repo_name | str | | repository name |\n", - "| repo_template | str | | template repository that new repo will use. If empty string, an empty repo will be created. Put in the format of \"/\" |\n", - "| private | bool | True | visibility of the created repository |\n", - "| description | str | | description for the GitHub repository |\n", - "| personal_account | bool | False | create repos in personal GitHub account |\n", - "| **Returns** | **Repository** | | |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/gh_group.py#L63){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.create_repo\n", - "\n", - "> GitHubGroup.create_repo (repo_name:str, repo_template='', private=True,\n", - "> description='', personal_account=False)\n", - "\n", - "Create a repository, either blank, or from a template\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| repo_name | str | | repository name |\n", - "| repo_template | str | | template repository that new repo will use. If empty string, an empty repo will be created. Put in the format of \"/\" |\n", - "| private | bool | True | visibility of the created repository |\n", - "| description | str | | description for the GitHub repository |\n", - "| personal_account | bool | False | create repos in personal GitHub account |\n", - "| **Returns** | **Repository** | | |" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "show_doc(GitHubGroup.create_repo)" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -872,59 +617,6 @@ "Usually, the template file names are generics and is not specific to a group. Under the context of group project, we want to rename each notebook files with thier group numbers. For example, for Group 1, we want to rename the file `Checkpoint_groupXXX.ipynb` to `Checkpoint_group001.ipynb`. To do that, we can use the following method." ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/gh_group.py#L112){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.rename_files\n", - "\n", - "> GitHubGroup.rename_files (repo:github.Repository.Repository,\n", - "> og_filename:str, new_filename:str)\n", - "\n", - "Rename the file by delete the old file and commit the new file\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| repo | Repository | the repository that we want to rename file |\n", - "| og_filename | str | old file name |\n", - "| new_filename | str | new file name |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/gh_group.py#L112){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.rename_files\n", - "\n", - "> GitHubGroup.rename_files (repo:github.Repository.Repository,\n", - "> og_filename:str, new_filename:str)\n", - "\n", - "Rename the file by delete the old file and commit the new file\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| repo | Repository | the repository that we want to rename file |\n", - "| og_filename | str | old file name |\n", - "| new_filename | str | new file name |" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "show_doc(GitHubGroup.rename_files)" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -980,59 +672,6 @@ "Once the repository was created, we need to give the student team members proper permission to write to the repository and instructional team to be the admin of the repository. Those two functionalities are achieve by the following two methods." ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/gh_group.py#L126){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.add_collaborator\n", - "\n", - "> GitHubGroup.add_collaborator (repo:github.Repository.Repository,\n", - "> collaborator:str, permission:str)\n", - "\n", - "Add collaborator to the repository with specified permission\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| repo | Repository | target repository |\n", - "| collaborator | str | GitHub username of the collaborator |\n", - "| permission | str | `pull`, `push` or `admin` |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/gh_group.py#L126){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.add_collaborator\n", - "\n", - "> GitHubGroup.add_collaborator (repo:github.Repository.Repository,\n", - "> collaborator:str, permission:str)\n", - "\n", - "Add collaborator to the repository with specified permission\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| repo | Repository | target repository |\n", - "| collaborator | str | GitHub username of the collaborator |\n", - "| permission | str | `pull`, `push` or `admin` |" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "show_doc(GitHubGroup.add_collaborator)" - ] - }, { "cell_type": "code", "execution_count": null, @@ -1062,55 +701,6 @@ "In almost every quarter, at least 10 students forgot to accept the invitation to join the group repository. Historially, our instructional team handle those student's GitHub account on a case-by-case basis. However, with this module, it is possible to resent all pending and expired invitations to those students with one call." ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/gh_group.py#L145){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.resend_invitations\n", - "\n", - "> GitHubGroup.resend_invitations (repo:github.Repository.Repository)\n", - "\n", - "Resent Invitation to invitee who did not accept the invitation\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| repo | Repository | target repository |\n", - "| **Returns** | **[]** | **list of re-invited user** |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/gh_group.py#L145){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.resend_invitations\n", - "\n", - "> GitHubGroup.resend_invitations (repo:github.Repository.Repository)\n", - "\n", - "Resent Invitation to invitee who did not accept the invitation\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| repo | Repository | target repository |\n", - "| **Returns** | **[]** | **list of re-invited user** |" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "show_doc(GitHubGroup.resend_invitations)" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -1156,59 +746,6 @@ "Additionally, course staffs should be in a team in the GitHub Organization in order to manage student repositories." ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/gh_group.py#L174){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.add_team\n", - "\n", - "> GitHubGroup.add_team (repo:github.Repository.Repository, team_slug:str,\n", - "> permission:str)\n", - "\n", - "Add team to the repository with specified permission\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| repo | Repository | target repository |\n", - "| team_slug | str | team slug (name) |\n", - "| permission | str | `pull`, `push` or `admin` |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/gh_group.py#L174){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.add_team\n", - "\n", - "> GitHubGroup.add_team (repo:github.Repository.Repository, team_slug:str,\n", - "> permission:str)\n", - "\n", - "Add team to the repository with specified permission\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| repo | Repository | target repository |\n", - "| team_slug | str | team slug (name) |\n", - "| permission | str | `pull`, `push` or `admin` |" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "show_doc(GitHubGroup.add_team)" - ] - }, { "cell_type": "code", "execution_count": null, @@ -1237,53 +774,6 @@ "If you have all the students repositories under the same team, you can use the following method to resent all pending invitations from all the repositories under that team." ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/gh_group.py#L163){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.resent_invitations_team_repos\n", - "\n", - "> GitHubGroup.resent_invitations_team_repos (team_slug:str)\n", - "\n", - "For all repository under that team, Resent invitation to invitee who did not accept the inivtation\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| team_slug | str | team slug (name) under the org |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/gh_group.py#L163){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.resent_invitations_team_repos\n", - "\n", - "> GitHubGroup.resent_invitations_team_repos (team_slug:str)\n", - "\n", - "For all repository under that team, Resent invitation to invitee who did not accept the inivtation\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| team_slug | str | team slug (name) under the org |" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "show_doc(GitHubGroup.resent_invitations_team_repos)" - ] - }, { "cell_type": "code", "execution_count": null, @@ -1342,59 +832,6 @@ "TODO: add file superlink to the repo to see the examples." ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/gh_group.py#L188){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.create_feedback_dir\n", - "\n", - "> GitHubGroup.create_feedback_dir (repo:github.Repository.Repository,\n", - "> template_fp:str, destination='feedback')\n", - "\n", - "Create feedback direcotry on local machine\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| repo | Repository | | target repository |\n", - "| template_fp | str | | |\n", - "| destination | str | feedback | directory path of the template file. |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/gh_group.py#L188){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.create_feedback_dir\n", - "\n", - "> GitHubGroup.create_feedback_dir (repo:github.Repository.Repository,\n", - "> template_fp:str, destination='feedback')\n", - "\n", - "Create feedback direcotry on local machine\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| repo | Repository | | target repository |\n", - "| template_fp | str | | |\n", - "| destination | str | feedback | directory path of the template file. |" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "show_doc(GitHubGroup.create_feedback_dir)" - ] - }, { "cell_type": "code", "execution_count": null, @@ -1433,61 +870,6 @@ "After created the project feedback, instructional team can modify the markdown rubrics and provide feedback to students via GitHub issue." ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/gh_group.py#L208){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.create_issue\n", - "\n", - "> GitHubGroup.create_issue (repo:github.Repository.Repository, title:str,\n", - "> content:str)\n", - "\n", - "Create GitHub issue to the target repository\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| repo | Repository | target repository |\n", - "| title | str | title of the issue, |\n", - "| content | str | content of the issue |\n", - "| **Returns** | **Issue** | **open issue** |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/gh_group.py#L208){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.create_issue\n", - "\n", - "> GitHubGroup.create_issue (repo:github.Repository.Repository, title:str,\n", - "> content:str)\n", - "\n", - "Create GitHub issue to the target repository\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| repo | Repository | target repository |\n", - "| title | str | title of the issue, |\n", - "| content | str | content of the issue |\n", - "| **Returns** | **Issue** | **open issue** |" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "show_doc(GitHubGroup.create_issue)" - ] - }, { "cell_type": "code", "execution_count": null, @@ -1525,59 +907,6 @@ "Alternatively, you can create issue from markdown files, where it contains all the comments and rubrics for this project. The first line of the markdown file will be the title of the github issue." ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/gh_group.py#L220){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.create_issue_from_md\n", - "\n", - "> GitHubGroup.create_issue_from_md (repo:github.Repository.Repository,\n", - "> md_fp:str)\n", - "\n", - "Create GitHub issue from markdown file.\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| repo | Repository | target repository, |\n", - "| md_fp | str | file path of the feedback markdown file |\n", - "| **Returns** | **Issue** | **open issue** |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/gh_group.py#L220){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.create_issue_from_md\n", - "\n", - "> GitHubGroup.create_issue_from_md (repo:github.Repository.Repository,\n", - "> md_fp:str)\n", - "\n", - "Create GitHub issue from markdown file.\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| repo | Repository | target repository, |\n", - "| md_fp | str | file path of the feedback markdown file |\n", - "| **Returns** | **Issue** | **open issue** |" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "show_doc(GitHubGroup.create_issue_from_md)" - ] - }, { "cell_type": "code", "execution_count": null, @@ -1616,55 +945,6 @@ "During projct grading, we will handle numerous groups at once. Once the instructor team finish modifying the markdown file for each group, we can release feedback to each of the project repository, as long as they have the same file name. For example, we have finish grading the final project, in the file name of `feedback//final_project_feedback.md`, and they are ready to be publish, we can call the following function to create issue in batch." ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/gh_group.py#L232){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.release_feedback\n", - "\n", - "> GitHubGroup.release_feedback (md_filename:str, feedback_dir='feedback')\n", - "\n", - "Release feedback via GitHub issue from all the feedbacks in the feedback directory\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| md_filename | str | | feedback markdown file name |\n", - "| feedback_dir | str | feedback | feedback directory contains the markdown files |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/gh_group.py#L232){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### GitHubGroup.release_feedback\n", - "\n", - "> GitHubGroup.release_feedback (md_filename:str, feedback_dir='feedback')\n", - "\n", - "Release feedback via GitHub issue from all the feedbacks in the feedback directory\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| md_filename | str | | feedback markdown file name |\n", - "| feedback_dir | str | feedback | feedback directory contains the markdown files |" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "show_doc(GitHubGroup.release_feedback)" - ] - }, { "cell_type": "code", "execution_count": null, @@ -1689,19 +969,8 @@ "metadata": {}, "outputs": [], "source": [ - "#| hide\n", "repo.delete()" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#| hide\n", - "import nbdev; nbdev.nbdev_export()" - ] } ], "metadata": { diff --git a/nbs/api/03_canvas.ipynb b/nbs/api/03_canvas.ipynb index 2d6452b..e2941e2 100644 --- a/nbs/api/03_canvas.ipynb +++ b/nbs/api/03_canvas.ipynb @@ -15,27 +15,6 @@ "metadata": {}, "outputs": [], "source": [ - "#| default_exp canvas" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#| hide\n", - "from nbdev.showdoc import *\n", - "from fastcore.test import *" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#| export\n", "from canvasapi import Canvas\n", "from github import Github\n", "import canvasapi\n", @@ -54,8 +33,6 @@ "metadata": {}, "outputs": [], "source": [ - "#| export\n", - "#| hide\n", "class bcolors:\n", " HEADER = '\\033[95m'\n", " OKBLUE = '\\033[94m'\n", @@ -74,8 +51,6 @@ "metadata": {}, "outputs": [], "source": [ - "#| export\n", - "\n", "class CanvasGroup():\n", " def __init__(self,\n", " credentials_fp = \"\", # credential file path. [Template of the credentials.json](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/nbs/credentials.json)\n", @@ -432,107 +407,12 @@ "Alternatively, you can manually set them after you created the `CanvasGroup` object" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L56){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.auth_canvas\n", - "\n", - "> CanvasGroup.auth_canvas (credentials_fp:str)\n", - "\n", - "Authorize the canvas module with API_KEY\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| credentials_fp | str | the Authenticator key generated from canvas |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L56){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.auth_canvas\n", - "\n", - "> CanvasGroup.auth_canvas (credentials_fp:str)\n", - "\n", - "Authorize the canvas module with API_KEY\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| credentials_fp | str | the Authenticator key generated from canvas |" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "show_doc(CanvasGroup.auth_canvas)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L70){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.set_course\n", - "\n", - "> CanvasGroup.set_course (course_id:int)\n", - "\n", - "Set the target course by the course ID\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| course_id | int | the course id of the target course |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L70){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.set_course\n", - "\n", - "> CanvasGroup.set_course (course_id:int)\n", - "\n", - "Set the target course by the course ID\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| course_id | int | the course id of the target course |" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "show_doc(CanvasGroup.set_course)" - ] - }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "#| hide\n", "test_eq(course_id, cg.course.id)\n", "assert isinstance(cg.canvas, canvasapi.canvas.Canvas)" ] @@ -551,45 +431,6 @@ "## Create / Set Target Group Category (Set)" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L107){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.get_group_categories\n", - "\n", - "> CanvasGroup.get_group_categories ()\n", - "\n", - "Grab all existing group category (group set) in this course" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L107){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.get_group_categories\n", - "\n", - "> CanvasGroup.get_group_categories ()\n", - "\n", - "Grab all existing group category (group set) in this course" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "show_doc(CanvasGroup.get_group_categories)" - ] - }, { "cell_type": "code", "execution_count": null, @@ -611,55 +452,6 @@ "list(cg.get_group_categories().keys())" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L113){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.create_group_category\n", - "\n", - "> CanvasGroup.create_group_category (params:dict)\n", - "\n", - "Create group category (group set) in this course\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| params | dict | the parameter of canvas group category API @ [this link](https://canvas.instructure.com/doc/api/group_categories.html#method.group_categories.create) |\n", - "| **Returns** | **GroupCategory** | **the generated group category object** |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L113){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.create_group_category\n", - "\n", - "> CanvasGroup.create_group_category (params:dict)\n", - "\n", - "Create group category (group set) in this course\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| params | dict | the parameter of canvas group category API @ [this link](https://canvas.instructure.com/doc/api/group_categories.html#method.group_categories.create) |\n", - "| **Returns** | **GroupCategory** | **the generated group category object** |" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "show_doc(CanvasGroup.create_group_category)" - ] - }, { "cell_type": "code", "execution_count": null, @@ -710,51 +502,6 @@ "When a group category is already created, we cannot create another group with the same name. To switch the group category destination of group creation, use the `set_group_category` methods." ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L93){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.set_group_category\n", - "\n", - "> CanvasGroup.set_group_category (category_name:str)\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| category_name | str | the target group category |\n", - "| **Returns** | **GroupCategory** | **target group category object** |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L93){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.set_group_category\n", - "\n", - "> CanvasGroup.set_group_category (category_name:str)\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| category_name | str | the target group category |\n", - "| **Returns** | **GroupCategory** | **target group category object** |" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "show_doc(CanvasGroup.set_group_category)" - ] - }, { "cell_type": "code", "execution_count": null, @@ -771,55 +518,6 @@ "## Create a Group Inside the Target Group Category" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L120){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.create_group\n", - "\n", - "> CanvasGroup.create_group (params:dict)\n", - "\n", - "Create canvas group under the target group category\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| params | dict | the parameter of canvas group create API at [this link](https://canvas.instructure.com/doc/api/groups.html#method.groups.create) |\n", - "| **Returns** | **Group** | **the generated target group object** |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L120){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.create_group\n", - "\n", - "> CanvasGroup.create_group (params:dict)\n", - "\n", - "Create canvas group under the target group category\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| params | dict | the parameter of canvas group create API at [this link](https://canvas.instructure.com/doc/api/groups.html#method.groups.create) |\n", - "| **Returns** | **Group** | **the generated target group object** |" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "show_doc(CanvasGroup.create_group)" - ] - }, { "cell_type": "code", "execution_count": null, @@ -829,8 +527,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "In Group Set: \u001B[94mTEST-GroupProject\u001B[0m,\n", - "Group \u001B[92mTEST-GROUP1\u001B[0m Created!\n", + "In Group Set: \u001b[94mTEST-GroupProject\u001b[0m,\n", + "Group \u001b[92mTEST-GROUP1\u001b[0m Created!\n", "TEST-GROUP1 (122854)\n" ] } @@ -851,59 +549,6 @@ "## Assign Student to the Group" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L134){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.join_canvas_group\n", - "\n", - "> CanvasGroup.join_canvas_group (group:canvasapi.group.Group,\n", - "> group_members:[])\n", - "\n", - "Add membership access of each group member into the group\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| group | Group | the group that students will join |\n", - "| group_members | [] | list of group member's SIS Login (email prefix, before the @.) |\n", - "| **Returns** | **[]** | **list of unsuccessful join** |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L134){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.join_canvas_group\n", - "\n", - "> CanvasGroup.join_canvas_group (group:canvasapi.group.Group,\n", - "> group_members:[])\n", - "\n", - "Add membership access of each group member into the group\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| group | Group | the group that students will join |\n", - "| group_members | [] | list of group member's SIS Login (email prefix, before the @.) |\n", - "| **Returns** | **[]** | **list of unsuccessful join** |" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "show_doc(CanvasGroup.join_canvas_group)" - ] - }, { "cell_type": "code", "execution_count": null, @@ -955,20 +600,9 @@ } ], "source": [ - "#| hide\n", "# tear down the example project\n", "cg.get_group_categories()[\"TEST-GroupProject\"].delete()" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#| hide\n", - "import nbdev; nbdev.nbdev_export()" - ] } ], "metadata": { diff --git a/nbs/api/04_project_grading.ipynb b/nbs/api/04_project_grading.ipynb index 0749371..bbea9d6 100644 --- a/nbs/api/04_project_grading.ipynb +++ b/nbs/api/04_project_grading.ipynb @@ -10,26 +10,6 @@ "> Manage grading rubrics and grade posting on the groups grade." ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#| default_exp grading" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#| hide\n", - "from nbdev.showdoc import *\n", - "from fastcore.test import *" - ] - }, { "cell_type": "code", "execution_count": null, @@ -37,7 +17,6 @@ "metadata": {}, "outputs": [], "source": [ - "#| export\n", "from CanvasGroupy import *\n", "import github\n", "import canvasapi\n", @@ -50,8 +29,6 @@ "metadata": {}, "outputs": [], "source": [ - "#| export\n", - "#| hide\n", "class bcolors:\n", " HEADER = '\\033[95m'\n", " OKBLUE = '\\033[94m'\n", @@ -70,7 +47,6 @@ "metadata": {}, "outputs": [], "source": [ - "#| export\n", "class Grading:\n", " def __init__(self,\n", " ghg:GitHubGroup=None, # authenticated GitHub object\n", @@ -176,51 +152,6 @@ " self.update_canvas_score(group_name, assignment_id, score, issue, post)\n", "\n" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "show_doc(Grading.create_issue_from_md)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "show_doc(Grading.fetch_issue)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "show_doc(Grading.parse_score_from_issue)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "show_doc(Grading.update_canvas_score)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "show_doc(Grading.grade_project)" - ] } ], "metadata": { diff --git a/nbs/api/index.qmd b/nbs/api/index.qmd index 267272d..b5f8c1e 100644 --- a/nbs/api/index.qmd +++ b/nbs/api/index.qmd @@ -8,4 +8,4 @@ listing: filter-ui: false --- -This section contains API details for each of CanvasGroupy python submodules. This reference documentation is mainly useful for people looking to customise or build on top of nbdev, or wanting detailed information about how this module works. +This section contains API details for each of CanvasGroupy python submodules. This reference documentation is mainly useful for people looking to customise or build on top of CanvasGroupy, or wanting detailed information about how this module works. diff --git a/nbs/nbdev.yml b/nbs/nbdev.yml deleted file mode 100644 index 2c6d459..0000000 --- a/nbs/nbdev.yml +++ /dev/null @@ -1,9 +0,0 @@ -project: - output-dir: _docs - -website: - title: "CanvasGroupy" - site-url: "https://FleischerResearchLab.github.io/CanvasGroupy" - description: "Canvas Grouping Python Script" - repo-branch: main - repo-url: "https://github.com/FleischerResearchLab/CanvasGroupy" diff --git a/nbs/sidebar.yml b/nbs/sidebar.yml deleted file mode 100644 index ddab084..0000000 --- a/nbs/sidebar.yml +++ /dev/null @@ -1,14 +0,0 @@ -website: - sidebar: - contents: - - index.ipynb - - section: api - contents: - - api/index.qmd - - api/01_GroupEng_assign.ipynb - - api/02_gh_group_creation.ipynb - - api/03_canvas.ipynb - - section: tutorial - contents: - - tutorial/Authentications.ipynb - - tutorial/Create GitHub Group from Canvas Group.ipynb diff --git a/nbs/tutorials/Authentications.ipynb b/nbs/tutorials/Authentications.ipynb index 3d12034..1f914a0 100644 --- a/nbs/tutorials/Authentications.ipynb +++ b/nbs/tutorials/Authentications.ipynb @@ -7,8 +7,6 @@ "metadata": {}, "outputs": [], "source": [ - "#| hide\n", - "from nbdev.doclinks import NbdevLookup\n", "import json" ] }, @@ -72,14 +70,6 @@ " credentials = json.load(f)\n", "print(credentials)" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e8d26432", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..61d0b76 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,45 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "CanvasGroupy" +version = "0.0.3" +description = "Canvas Grouping Python Script" +readme = "README.md" +license = "Apache-2.0" +requires-python = ">=3.9" +authors = [ + { name = "scottyang", email = "yuy004@ucsd.edu" }, +] +keywords = ["canvas", "github", "grouping", "education"] +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Natural Language :: English", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "License :: OSI Approved :: Apache Software License", +] +dependencies = [ + "pandas", + "numpy", + "PyGithub", + "canvasapi", + "groupeng @ git+https://github.com/scott-yj-yang/groupeng.git@dev", +] + +[project.optional-dependencies] +dev = [ + "pytest", +] + +[tool.hatch.metadata] +allow-direct-references = true + +[project.urls] +Homepage = "https://github.com/FleischerResearchLab/CanvasGroupy" +Documentation = "https://FleischerResearchLab.github.io/CanvasGroupy" +Repository = "https://github.com/FleischerResearchLab/CanvasGroupy" diff --git a/settings.ini b/settings.ini deleted file mode 100644 index 0cde2c1..0000000 --- a/settings.ini +++ /dev/null @@ -1,44 +0,0 @@ -[DEFAULT] -# All sections below are required unless otherwise specified. -# See https://github.com/fastai/nbdev/blob/master/settings.ini for examples. - -### Python library ### -repo = CanvasGroupy -lib_name = %(repo)s -version = 0.0.3 -min_python = 3.7 -license = apache2 -black_formatting = False - -### nbdev ### -doc_path = _docs -lib_path = CanvasGroupy -nbs_path = nbs -recursive = True -tst_flags = notest -put_version_in_init = True - -### Docs ### -branch = main -custom_sidebar = True -doc_host = https://%(user)s.github.io -doc_baseurl = /%(repo)s -git_url = https://github.com/%(user)s/%(repo)s -title = %(lib_name)s - -### PyPI ### -audience = Developers -author = scottyang -author_email = yuy004@ucsd.edu -copyright = 2023 onwards, %(author)s -description = Canvas Grouping Python Script -keywords = nbdev jupyter notebook python -language = English -status = 3 -user = FleischerResearchLab - -### Optional ### -requirements = fastcore pandas numpy PyGithub canvasapi -pip_requirements = 'groupeng @ git+https://github.com/scott-yj-yang/groupeng.git@dev' -# dev_requirements = -# console_scripts = diff --git a/setup.py b/setup.py deleted file mode 100644 index 34abbe8..0000000 --- a/setup.py +++ /dev/null @@ -1,57 +0,0 @@ -from pkg_resources import parse_version -from configparser import ConfigParser -import setuptools, shlex -assert parse_version(setuptools.__version__)>=parse_version('36.2') - -# note: all settings are in settings.ini; edit there, not here -config = ConfigParser(delimiters=['=']) -config.read('settings.ini') -cfg = config['DEFAULT'] - -cfg_keys = 'version description keywords author author_email'.split() -expected = cfg_keys + "lib_name user branch license status min_python audience language".split() -for o in expected: assert o in cfg, "missing expected setting: {}".format(o) -setup_cfg = {o:cfg[o] for o in cfg_keys} - -licenses = { - 'apache2': ('Apache Software License 2.0','OSI Approved :: Apache Software License'), - 'mit': ('MIT License', 'OSI Approved :: MIT License'), - 'gpl2': ('GNU General Public License v2', 'OSI Approved :: GNU General Public License v2 (GPLv2)'), - 'gpl3': ('GNU General Public License v3', 'OSI Approved :: GNU General Public License v3 (GPLv3)'), - 'bsd3': ('BSD License', 'OSI Approved :: BSD License'), -} -statuses = [ '1 - Planning', '2 - Pre-Alpha', '3 - Alpha', - '4 - Beta', '5 - Production/Stable', '6 - Mature', '7 - Inactive' ] -py_versions = '3.6 3.7 3.8 3.9 3.10'.split() - -requirements = shlex.split(cfg.get('requirements', '')) -if cfg.get('pip_requirements'): requirements += shlex.split(cfg.get('pip_requirements', '')) -min_python = cfg['min_python'] -lic = licenses.get(cfg['license'].lower(), (cfg['license'], None)) -dev_requirements = (cfg.get('dev_requirements') or '').split() - -setuptools.setup( - name = cfg['lib_name'], - license = lic[0], - classifiers = [ - 'Development Status :: ' + statuses[int(cfg['status'])], - 'Intended Audience :: ' + cfg['audience'].title(), - 'Natural Language :: ' + cfg['language'].title(), - ] + ['Programming Language :: Python :: '+o for o in py_versions[py_versions.index(min_python):]] + (['License :: ' + lic[1] ] if lic[1] else []), - url = cfg['git_url'], - packages = setuptools.find_packages(), - include_package_data = True, - install_requires = requirements, - extras_require={ 'dev': dev_requirements }, - dependency_links = cfg.get('dep_links','').split(), - python_requires = '>=' + cfg['min_python'], - long_description = open('README.md').read(), - long_description_content_type = 'text/markdown', - zip_safe = False, - entry_points = { - 'console_scripts': cfg.get('console_scripts','').split(), - 'nbdev': [f'{cfg.get("lib_path")}={cfg.get("lib_path")}._modidx:d'] - }, - **setup_cfg) - - diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..e44f778 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1050 @@ +version = 1 +revision = 3 +requires-python = ">=3.9" +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version < '3.10'", +] + +[[package]] +name = "arrow" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/33/032cdc44182491aa708d06a68b62434140d8c50820a087fac7af37703357/arrow-1.4.0.tar.gz", hash = "sha256:ed0cc050e98001b8779e84d461b0098c4ac597e88704a655582b21d116e526d7", size = 152931, upload-time = "2025-10-18T17:46:46.761Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl", hash = "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205", size = 68797, upload-time = "2025-10-18T17:46:45.663Z" }, +] + +[[package]] +name = "canvasapi" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "arrow" }, + { name = "pytz" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/e0/4fcbddff6dbc8497adf430166e36a85118738c38d260791b6776b83f5bab/canvasapi-3.4.0.tar.gz", hash = "sha256:efa3dca843fa0dd494c62fab66c0e09f7866b4f8007d0053e649779675209ece", size = 158021, upload-time = "2025-11-10T22:01:01.942Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/dc/f5bcb673038837776f6972de44683b63e26777a090fb3dd372b7fbb4f8e1/canvasapi-3.4.0-py3-none-any.whl", hash = "sha256:dafc2a7d2106f51782271e59b6e3b62373c7e0714dfd68bdc00f4df7711f7750", size = 116725, upload-time = "2025-11-10T22:01:00.651Z" }, +] + +[[package]] +name = "canvasgroupy" +version = "0.0.3" +source = { editable = "." } +dependencies = [ + { name = "canvasapi" }, + { name = "groupeng" }, + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "pandas", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pygithub" }, +] + +[package.optional-dependencies] +dev = [ + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] + +[package.metadata] +requires-dist = [ + { name = "canvasapi" }, + { name = "groupeng", git = "https://github.com/scott-yj-yang/groupeng.git?rev=dev" }, + { name = "numpy" }, + { name = "pandas" }, + { name = "pygithub" }, + { name = "pytest", marker = "extra == 'dev'" }, +] +provides-extras = ["dev"] + +[[package]] +name = "certifi" +version = "2026.2.25" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", version = "2.23", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' and implementation_name != 'PyPy'" }, + { name = "pycparser", version = "3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, + { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, + { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, + { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, + { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, + { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, + { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, + { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, + { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, + { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, + { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, + { url = "https://files.pythonhosted.org/packages/c0/cc/08ed5a43f2996a16b462f64a7055c6e962803534924b9b2f1371d8c00b7b/cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf", size = 184288, upload-time = "2025-09-08T23:23:48.404Z" }, + { url = "https://files.pythonhosted.org/packages/3d/de/38d9726324e127f727b4ecc376bc85e505bfe61ef130eaf3f290c6847dd4/cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7", size = 180509, upload-time = "2025-09-08T23:23:49.73Z" }, + { url = "https://files.pythonhosted.org/packages/9b/13/c92e36358fbcc39cf0962e83223c9522154ee8630e1df7c0b3a39a8124e2/cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c", size = 208813, upload-time = "2025-09-08T23:23:51.263Z" }, + { url = "https://files.pythonhosted.org/packages/15/12/a7a79bd0df4c3bff744b2d7e52cc1b68d5e7e427b384252c42366dc1ecbc/cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165", size = 216498, upload-time = "2025-09-08T23:23:52.494Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ad/5c51c1c7600bdd7ed9a24a203ec255dccdd0ebf4527f7b922a0bde2fb6ed/cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534", size = 203243, upload-time = "2025-09-08T23:23:53.836Z" }, + { url = "https://files.pythonhosted.org/packages/32/f2/81b63e288295928739d715d00952c8c6034cb6c6a516b17d37e0c8be5600/cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f", size = 203158, upload-time = "2025-09-08T23:23:55.169Z" }, + { url = "https://files.pythonhosted.org/packages/1f/74/cc4096ce66f5939042ae094e2e96f53426a979864aa1f96a621ad128be27/cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63", size = 216548, upload-time = "2025-09-08T23:23:56.506Z" }, + { url = "https://files.pythonhosted.org/packages/e8/be/f6424d1dc46b1091ffcc8964fa7c0ab0cd36839dd2761b49c90481a6ba1b/cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2", size = 218897, upload-time = "2025-09-08T23:23:57.825Z" }, + { url = "https://files.pythonhosted.org/packages/f7/e0/dda537c2309817edf60109e39265f24f24aa7f050767e22c98c53fe7f48b/cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65", size = 211249, upload-time = "2025-09-08T23:23:59.139Z" }, + { url = "https://files.pythonhosted.org/packages/2b/e7/7c769804eb75e4c4b35e658dba01de1640a351a9653c3d49ca89d16ccc91/cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322", size = 218041, upload-time = "2025-09-08T23:24:00.496Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d9/6218d78f920dcd7507fc16a766b5ef8f3b913cc7aa938e7fc80b9978d089/cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a", size = 172138, upload-time = "2025-09-08T23:24:01.7Z" }, + { url = "https://files.pythonhosted.org/packages/54/8f/a1e836f82d8e32a97e6b29cc8f641779181ac7363734f12df27db803ebda/cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9", size = 182794, upload-time = "2025-09-08T23:24:02.943Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709, upload-time = "2025-10-14T04:40:11.385Z" }, + { url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814, upload-time = "2025-10-14T04:40:13.135Z" }, + { url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467, upload-time = "2025-10-14T04:40:14.728Z" }, + { url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280, upload-time = "2025-10-14T04:40:16.14Z" }, + { url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454, upload-time = "2025-10-14T04:40:17.567Z" }, + { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609, upload-time = "2025-10-14T04:40:19.08Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849, upload-time = "2025-10-14T04:40:20.607Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586, upload-time = "2025-10-14T04:40:21.719Z" }, + { url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290, upload-time = "2025-10-14T04:40:23.069Z" }, + { url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663, upload-time = "2025-10-14T04:40:24.17Z" }, + { url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964, upload-time = "2025-10-14T04:40:25.368Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064, upload-time = "2025-10-14T04:40:26.806Z" }, + { url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015, upload-time = "2025-10-14T04:40:28.284Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792, upload-time = "2025-10-14T04:40:29.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198, upload-time = "2025-10-14T04:40:30.644Z" }, + { url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262, upload-time = "2025-10-14T04:40:32.108Z" }, + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/46/7c/0c4760bccf082737ca7ab84a4c2034fcc06b1f21cf3032ea98bd6feb1725/charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9", size = 209609, upload-time = "2025-10-14T04:42:10.922Z" }, + { url = "https://files.pythonhosted.org/packages/bb/a4/69719daef2f3d7f1819de60c9a6be981b8eeead7542d5ec4440f3c80e111/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d", size = 149029, upload-time = "2025-10-14T04:42:12.38Z" }, + { url = "https://files.pythonhosted.org/packages/e6/21/8d4e1d6c1e6070d3672908b8e4533a71b5b53e71d16828cc24d0efec564c/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608", size = 144580, upload-time = "2025-10-14T04:42:13.549Z" }, + { url = "https://files.pythonhosted.org/packages/a7/0a/a616d001b3f25647a9068e0b9199f697ce507ec898cacb06a0d5a1617c99/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc", size = 162340, upload-time = "2025-10-14T04:42:14.892Z" }, + { url = "https://files.pythonhosted.org/packages/85/93/060b52deb249a5450460e0585c88a904a83aec474ab8e7aba787f45e79f2/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e", size = 159619, upload-time = "2025-10-14T04:42:16.676Z" }, + { url = "https://files.pythonhosted.org/packages/dd/21/0274deb1cc0632cd587a9a0ec6b4674d9108e461cb4cd40d457adaeb0564/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1", size = 153980, upload-time = "2025-10-14T04:42:17.917Z" }, + { url = "https://files.pythonhosted.org/packages/28/2b/e3d7d982858dccc11b31906976323d790dded2017a0572f093ff982d692f/charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3", size = 152174, upload-time = "2025-10-14T04:42:19.018Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ff/4a269f8e35f1e58b2df52c131a1fa019acb7ef3f8697b7d464b07e9b492d/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6", size = 151666, upload-time = "2025-10-14T04:42:20.171Z" }, + { url = "https://files.pythonhosted.org/packages/da/c9/ec39870f0b330d58486001dd8e532c6b9a905f5765f58a6f8204926b4a93/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88", size = 145550, upload-time = "2025-10-14T04:42:21.324Z" }, + { url = "https://files.pythonhosted.org/packages/75/8f/d186ab99e40e0ed9f82f033d6e49001701c81244d01905dd4a6924191a30/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1", size = 163721, upload-time = "2025-10-14T04:42:22.46Z" }, + { url = "https://files.pythonhosted.org/packages/96/b1/6047663b9744df26a7e479ac1e77af7134b1fcf9026243bb48ee2d18810f/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf", size = 152127, upload-time = "2025-10-14T04:42:23.712Z" }, + { url = "https://files.pythonhosted.org/packages/59/78/e5a6eac9179f24f704d1be67d08704c3c6ab9f00963963524be27c18ed87/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318", size = 161175, upload-time = "2025-10-14T04:42:24.87Z" }, + { url = "https://files.pythonhosted.org/packages/e5/43/0e626e42d54dd2f8dd6fc5e1c5ff00f05fbca17cb699bedead2cae69c62f/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c", size = 155375, upload-time = "2025-10-14T04:42:27.246Z" }, + { url = "https://files.pythonhosted.org/packages/e9/91/d9615bf2e06f35e4997616ff31248c3657ed649c5ab9d35ea12fce54e380/charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505", size = 99692, upload-time = "2025-10-14T04:42:28.425Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a9/6c040053909d9d1ef4fcab45fddec083aedc9052c10078339b47c8573ea8/charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966", size = 107192, upload-time = "2025-10-14T04:42:29.482Z" }, + { url = "https://files.pythonhosted.org/packages/f0/c6/4fa536b2c0cd3edfb7ccf8469fa0f363ea67b7213a842b90909ca33dd851/charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50", size = 100220, upload-time = "2025-10-14T04:42:30.632Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "cryptography" +version = "46.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/04/ee2a9e8542e4fa2773b81771ff8349ff19cdd56b7258a0cc442639052edb/cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d", size = 750064, upload-time = "2026-02-10T19:18:38.255Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/81/b0bb27f2ba931a65409c6b8a8b358a7f03c0e46eceacddff55f7c84b1f3b/cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad", size = 7176289, upload-time = "2026-02-10T19:17:08.274Z" }, + { url = "https://files.pythonhosted.org/packages/ff/9e/6b4397a3e3d15123de3b1806ef342522393d50736c13b20ec4c9ea6693a6/cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b", size = 4275637, upload-time = "2026-02-10T19:17:10.53Z" }, + { url = "https://files.pythonhosted.org/packages/63/e7/471ab61099a3920b0c77852ea3f0ea611c9702f651600397ac567848b897/cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b", size = 4424742, upload-time = "2026-02-10T19:17:12.388Z" }, + { url = "https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263", size = 4277528, upload-time = "2026-02-10T19:17:13.853Z" }, + { url = "https://files.pythonhosted.org/packages/22/29/c2e812ebc38c57b40e7c583895e73c8c5adb4d1e4a0cc4c5a4fdab2b1acc/cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d", size = 4947993, upload-time = "2026-02-10T19:17:15.618Z" }, + { url = "https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed", size = 4456855, upload-time = "2026-02-10T19:17:17.221Z" }, + { url = "https://files.pythonhosted.org/packages/2d/87/fc628a7ad85b81206738abbd213b07702bcbdada1dd43f72236ef3cffbb5/cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2", size = 3984635, upload-time = "2026-02-10T19:17:18.792Z" }, + { url = "https://files.pythonhosted.org/packages/84/29/65b55622bde135aedf4565dc509d99b560ee4095e56989e815f8fd2aa910/cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2", size = 4277038, upload-time = "2026-02-10T19:17:20.256Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/45e76c68d7311432741faf1fbf7fac8a196a0a735ca21f504c75d37e2558/cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0", size = 4912181, upload-time = "2026-02-10T19:17:21.825Z" }, + { url = "https://files.pythonhosted.org/packages/6d/1a/c1ba8fead184d6e3d5afcf03d569acac5ad063f3ac9fb7258af158f7e378/cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731", size = 4456482, upload-time = "2026-02-10T19:17:25.133Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e5/3fb22e37f66827ced3b902cf895e6a6bc1d095b5b26be26bd13c441fdf19/cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82", size = 4405497, upload-time = "2026-02-10T19:17:26.66Z" }, + { url = "https://files.pythonhosted.org/packages/1a/df/9d58bb32b1121a8a2f27383fabae4d63080c7ca60b9b5c88be742be04ee7/cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1", size = 4667819, upload-time = "2026-02-10T19:17:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ed/325d2a490c5e94038cdb0117da9397ece1f11201f425c4e9c57fe5b9f08b/cryptography-46.0.5-cp311-abi3-win32.whl", hash = "sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48", size = 3028230, upload-time = "2026-02-10T19:17:30.518Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5a/ac0f49e48063ab4255d9e3b79f5def51697fce1a95ea1370f03dc9db76f6/cryptography-46.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4", size = 3480909, upload-time = "2026-02-10T19:17:32.083Z" }, + { url = "https://files.pythonhosted.org/packages/00/13/3d278bfa7a15a96b9dc22db5a12ad1e48a9eb3d40e1827ef66a5df75d0d0/cryptography-46.0.5-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:94a76daa32eb78d61339aff7952ea819b1734b46f73646a07decb40e5b3448e2", size = 7119287, upload-time = "2026-02-10T19:17:33.801Z" }, + { url = "https://files.pythonhosted.org/packages/67/c8/581a6702e14f0898a0848105cbefd20c058099e2c2d22ef4e476dfec75d7/cryptography-46.0.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5be7bf2fb40769e05739dd0046e7b26f9d4670badc7b032d6ce4db64dddc0678", size = 4265728, upload-time = "2026-02-10T19:17:35.569Z" }, + { url = "https://files.pythonhosted.org/packages/dd/4a/ba1a65ce8fc65435e5a849558379896c957870dd64fecea97b1ad5f46a37/cryptography-46.0.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe346b143ff9685e40192a4960938545c699054ba11d4f9029f94751e3f71d87", size = 4408287, upload-time = "2026-02-10T19:17:36.938Z" }, + { url = "https://files.pythonhosted.org/packages/f8/67/8ffdbf7b65ed1ac224d1c2df3943553766914a8ca718747ee3871da6107e/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c69fd885df7d089548a42d5ec05be26050ebcd2283d89b3d30676eb32ff87dee", size = 4270291, upload-time = "2026-02-10T19:17:38.748Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e5/f52377ee93bc2f2bba55a41a886fd208c15276ffbd2569f2ddc89d50e2c5/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:8293f3dea7fc929ef7240796ba231413afa7b68ce38fd21da2995549f5961981", size = 4927539, upload-time = "2026-02-10T19:17:40.241Z" }, + { url = "https://files.pythonhosted.org/packages/3b/02/cfe39181b02419bbbbcf3abdd16c1c5c8541f03ca8bda240debc467d5a12/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:1abfdb89b41c3be0365328a410baa9df3ff8a9110fb75e7b52e66803ddabc9a9", size = 4442199, upload-time = "2026-02-10T19:17:41.789Z" }, + { url = "https://files.pythonhosted.org/packages/c0/96/2fcaeb4873e536cf71421a388a6c11b5bc846e986b2b069c79363dc1648e/cryptography-46.0.5-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d66e421495fdb797610a08f43b05269e0a5ea7f5e652a89bfd5a7d3c1dee3648", size = 3960131, upload-time = "2026-02-10T19:17:43.379Z" }, + { url = "https://files.pythonhosted.org/packages/d8/d2/b27631f401ddd644e94c5cf33c9a4069f72011821cf3dc7309546b0642a0/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4e817a8920bfbcff8940ecfd60f23d01836408242b30f1a708d93198393a80b4", size = 4270072, upload-time = "2026-02-10T19:17:45.481Z" }, + { url = "https://files.pythonhosted.org/packages/f4/a7/60d32b0370dae0b4ebe55ffa10e8599a2a59935b5ece1b9f06edb73abdeb/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:68f68d13f2e1cb95163fa3b4db4bf9a159a418f5f6e7242564fc75fcae667fd0", size = 4892170, upload-time = "2026-02-10T19:17:46.997Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b9/cf73ddf8ef1164330eb0b199a589103c363afa0cf794218c24d524a58eab/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a3d1fae9863299076f05cb8a778c467578262fae09f9dc0ee9b12eb4268ce663", size = 4441741, upload-time = "2026-02-10T19:17:48.661Z" }, + { url = "https://files.pythonhosted.org/packages/5f/eb/eee00b28c84c726fe8fa0158c65afe312d9c3b78d9d01daf700f1f6e37ff/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4143987a42a2397f2fc3b4d7e3a7d313fbe684f67ff443999e803dd75a76826", size = 4396728, upload-time = "2026-02-10T19:17:50.058Z" }, + { url = "https://files.pythonhosted.org/packages/65/f4/6bc1a9ed5aef7145045114b75b77c2a8261b4d38717bd8dea111a63c3442/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7d731d4b107030987fd61a7f8ab512b25b53cef8f233a97379ede116f30eb67d", size = 4652001, upload-time = "2026-02-10T19:17:51.54Z" }, + { url = "https://files.pythonhosted.org/packages/86/ef/5d00ef966ddd71ac2e6951d278884a84a40ffbd88948ef0e294b214ae9e4/cryptography-46.0.5-cp314-cp314t-win32.whl", hash = "sha256:c3bcce8521d785d510b2aad26ae2c966092b7daa8f45dd8f44734a104dc0bc1a", size = 3003637, upload-time = "2026-02-10T19:17:52.997Z" }, + { url = "https://files.pythonhosted.org/packages/b7/57/f3f4160123da6d098db78350fdfd9705057aad21de7388eacb2401dceab9/cryptography-46.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:4d8ae8659ab18c65ced284993c2265910f6c9e650189d4e3f68445ef82a810e4", size = 3469487, upload-time = "2026-02-10T19:17:54.549Z" }, + { url = "https://files.pythonhosted.org/packages/e2/fa/a66aa722105ad6a458bebd64086ca2b72cdd361fed31763d20390f6f1389/cryptography-46.0.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31", size = 7170514, upload-time = "2026-02-10T19:17:56.267Z" }, + { url = "https://files.pythonhosted.org/packages/0f/04/c85bdeab78c8bc77b701bf0d9bdcf514c044e18a46dcff330df5448631b0/cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18", size = 4275349, upload-time = "2026-02-10T19:17:58.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/32/9b87132a2f91ee7f5223b091dc963055503e9b442c98fc0b8a5ca765fab0/cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235", size = 4420667, upload-time = "2026-02-10T19:18:00.619Z" }, + { url = "https://files.pythonhosted.org/packages/a1/a6/a7cb7010bec4b7c5692ca6f024150371b295ee1c108bdc1c400e4c44562b/cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a", size = 4276980, upload-time = "2026-02-10T19:18:02.379Z" }, + { url = "https://files.pythonhosted.org/packages/8e/7c/c4f45e0eeff9b91e3f12dbd0e165fcf2a38847288fcfd889deea99fb7b6d/cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76", size = 4939143, upload-time = "2026-02-10T19:18:03.964Z" }, + { url = "https://files.pythonhosted.org/packages/37/19/e1b8f964a834eddb44fa1b9a9976f4e414cbb7aa62809b6760c8803d22d1/cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614", size = 4453674, upload-time = "2026-02-10T19:18:05.588Z" }, + { url = "https://files.pythonhosted.org/packages/db/ed/db15d3956f65264ca204625597c410d420e26530c4e2943e05a0d2f24d51/cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229", size = 3978801, upload-time = "2026-02-10T19:18:07.167Z" }, + { url = "https://files.pythonhosted.org/packages/41/e2/df40a31d82df0a70a0daf69791f91dbb70e47644c58581d654879b382d11/cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1", size = 4276755, upload-time = "2026-02-10T19:18:09.813Z" }, + { url = "https://files.pythonhosted.org/packages/33/45/726809d1176959f4a896b86907b98ff4391a8aa29c0aaaf9450a8a10630e/cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d", size = 4901539, upload-time = "2026-02-10T19:18:11.263Z" }, + { url = "https://files.pythonhosted.org/packages/99/0f/a3076874e9c88ecb2ecc31382f6e7c21b428ede6f55aafa1aa272613e3cd/cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c", size = 4452794, upload-time = "2026-02-10T19:18:12.914Z" }, + { url = "https://files.pythonhosted.org/packages/02/ef/ffeb542d3683d24194a38f66ca17c0a4b8bf10631feef44a7ef64e631b1a/cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4", size = 4404160, upload-time = "2026-02-10T19:18:14.375Z" }, + { url = "https://files.pythonhosted.org/packages/96/93/682d2b43c1d5f1406ed048f377c0fc9fc8f7b0447a478d5c65ab3d3a66eb/cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9", size = 4667123, upload-time = "2026-02-10T19:18:15.886Z" }, + { url = "https://files.pythonhosted.org/packages/45/2d/9c5f2926cb5300a8eefc3f4f0b3f3df39db7f7ce40c8365444c49363cbda/cryptography-46.0.5-cp38-abi3-win32.whl", hash = "sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72", size = 3010220, upload-time = "2026-02-10T19:18:17.361Z" }, + { url = "https://files.pythonhosted.org/packages/48/ef/0c2f4a8e31018a986949d34a01115dd057bf536905dca38897bacd21fac3/cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595", size = 3467050, upload-time = "2026-02-10T19:18:18.899Z" }, + { url = "https://files.pythonhosted.org/packages/eb/dd/2d9fdb07cebdf3d51179730afb7d5e576153c6744c3ff8fded23030c204e/cryptography-46.0.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:3b4995dc971c9fb83c25aa44cf45f02ba86f71ee600d81091c2f0cbae116b06c", size = 3476964, upload-time = "2026-02-10T19:18:20.687Z" }, + { url = "https://files.pythonhosted.org/packages/e9/6f/6cc6cc9955caa6eaf83660b0da2b077c7fe8ff9950a3c5e45d605038d439/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bc84e875994c3b445871ea7181d424588171efec3e185dced958dad9e001950a", size = 4218321, upload-time = "2026-02-10T19:18:22.349Z" }, + { url = "https://files.pythonhosted.org/packages/3e/5d/c4da701939eeee699566a6c1367427ab91a8b7088cc2328c09dbee940415/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2ae6971afd6246710480e3f15824ed3029a60fc16991db250034efd0b9fb4356", size = 4381786, upload-time = "2026-02-10T19:18:24.529Z" }, + { url = "https://files.pythonhosted.org/packages/ac/97/a538654732974a94ff96c1db621fa464f455c02d4bb7d2652f4edc21d600/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d861ee9e76ace6cf36a6a89b959ec08e7bc2493ee39d07ffe5acb23ef46d27da", size = 4217990, upload-time = "2026-02-10T19:18:25.957Z" }, + { url = "https://files.pythonhosted.org/packages/ae/11/7e500d2dd3ba891197b9efd2da5454b74336d64a7cc419aa7327ab74e5f6/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:2b7a67c9cd56372f3249b39699f2ad479f6991e62ea15800973b956f4b73e257", size = 4381252, upload-time = "2026-02-10T19:18:27.496Z" }, + { url = "https://files.pythonhosted.org/packages/bc/58/6b3d24e6b9bc474a2dcdee65dfd1f008867015408a271562e4b690561a4d/cryptography-46.0.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8456928655f856c6e1533ff59d5be76578a7157224dbd9ce6872f25055ab9ab7", size = 3407605, upload-time = "2026-02-10T19:18:29.233Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "groupeng" +version = "1.3" +source = { git = "https://github.com/scott-yj-yang/groupeng.git?rev=dev#75c674856c83c385f6f0cd70b501e87d932b11ba" } + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "numpy" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/75/10dd1f8116a8b796cb2c737b674e02d02e80454bda953fa7e65d8c12b016/numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78", size = 18902015, upload-time = "2024-08-26T20:19:40.945Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/91/3495b3237510f79f5d81f2508f9f13fea78ebfdf07538fc7444badda173d/numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece", size = 21165245, upload-time = "2024-08-26T20:04:14.625Z" }, + { url = "https://files.pythonhosted.org/packages/05/33/26178c7d437a87082d11019292dce6d3fe6f0e9026b7b2309cbf3e489b1d/numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04", size = 13738540, upload-time = "2024-08-26T20:04:36.784Z" }, + { url = "https://files.pythonhosted.org/packages/ec/31/cc46e13bf07644efc7a4bf68df2df5fb2a1a88d0cd0da9ddc84dc0033e51/numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66", size = 5300623, upload-time = "2024-08-26T20:04:46.491Z" }, + { url = "https://files.pythonhosted.org/packages/6e/16/7bfcebf27bb4f9d7ec67332ffebee4d1bf085c84246552d52dbb548600e7/numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b", size = 6901774, upload-time = "2024-08-26T20:04:58.173Z" }, + { url = "https://files.pythonhosted.org/packages/f9/a3/561c531c0e8bf082c5bef509d00d56f82e0ea7e1e3e3a7fc8fa78742a6e5/numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd", size = 13907081, upload-time = "2024-08-26T20:05:19.098Z" }, + { url = "https://files.pythonhosted.org/packages/fa/66/f7177ab331876200ac7563a580140643d1179c8b4b6a6b0fc9838de2a9b8/numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318", size = 19523451, upload-time = "2024-08-26T20:05:47.479Z" }, + { url = "https://files.pythonhosted.org/packages/25/7f/0b209498009ad6453e4efc2c65bcdf0ae08a182b2b7877d7ab38a92dc542/numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8", size = 19927572, upload-time = "2024-08-26T20:06:17.137Z" }, + { url = "https://files.pythonhosted.org/packages/3e/df/2619393b1e1b565cd2d4c4403bdd979621e2c4dea1f8532754b2598ed63b/numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326", size = 14400722, upload-time = "2024-08-26T20:06:39.16Z" }, + { url = "https://files.pythonhosted.org/packages/22/ad/77e921b9f256d5da36424ffb711ae79ca3f451ff8489eeca544d0701d74a/numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97", size = 6472170, upload-time = "2024-08-26T20:06:50.361Z" }, + { url = "https://files.pythonhosted.org/packages/10/05/3442317535028bc29cf0c0dd4c191a4481e8376e9f0db6bcf29703cadae6/numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131", size = 15905558, upload-time = "2024-08-26T20:07:13.881Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cf/034500fb83041aa0286e0fb16e7c76e5c8b67c0711bb6e9e9737a717d5fe/numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448", size = 21169137, upload-time = "2024-08-26T20:07:45.345Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d9/32de45561811a4b87fbdee23b5797394e3d1504b4a7cf40c10199848893e/numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195", size = 13703552, upload-time = "2024-08-26T20:08:06.666Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ca/2f384720020c7b244d22508cb7ab23d95f179fcfff33c31a6eeba8d6c512/numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57", size = 5298957, upload-time = "2024-08-26T20:08:15.83Z" }, + { url = "https://files.pythonhosted.org/packages/0e/78/a3e4f9fb6aa4e6fdca0c5428e8ba039408514388cf62d89651aade838269/numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a", size = 6905573, upload-time = "2024-08-26T20:08:27.185Z" }, + { url = "https://files.pythonhosted.org/packages/a0/72/cfc3a1beb2caf4efc9d0b38a15fe34025230da27e1c08cc2eb9bfb1c7231/numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669", size = 13914330, upload-time = "2024-08-26T20:08:48.058Z" }, + { url = "https://files.pythonhosted.org/packages/ba/a8/c17acf65a931ce551fee11b72e8de63bf7e8a6f0e21add4c937c83563538/numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951", size = 19534895, upload-time = "2024-08-26T20:09:16.536Z" }, + { url = "https://files.pythonhosted.org/packages/ba/86/8767f3d54f6ae0165749f84648da9dcc8cd78ab65d415494962c86fac80f/numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9", size = 19937253, upload-time = "2024-08-26T20:09:46.263Z" }, + { url = "https://files.pythonhosted.org/packages/df/87/f76450e6e1c14e5bb1eae6836478b1028e096fd02e85c1c37674606ab752/numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15", size = 14414074, upload-time = "2024-08-26T20:10:08.483Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ca/0f0f328e1e59f73754f06e1adfb909de43726d4f24c6a3f8805f34f2b0fa/numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4", size = 6470640, upload-time = "2024-08-26T20:10:19.732Z" }, + { url = "https://files.pythonhosted.org/packages/eb/57/3a3f14d3a759dcf9bf6e9eda905794726b758819df4663f217d658a58695/numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc", size = 15910230, upload-time = "2024-08-26T20:10:43.413Z" }, + { url = "https://files.pythonhosted.org/packages/45/40/2e117be60ec50d98fa08c2f8c48e09b3edea93cfcabd5a9ff6925d54b1c2/numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b", size = 20895803, upload-time = "2024-08-26T20:11:13.916Z" }, + { url = "https://files.pythonhosted.org/packages/46/92/1b8b8dee833f53cef3e0a3f69b2374467789e0bb7399689582314df02651/numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e", size = 13471835, upload-time = "2024-08-26T20:11:34.779Z" }, + { url = "https://files.pythonhosted.org/packages/7f/19/e2793bde475f1edaea6945be141aef6c8b4c669b90c90a300a8954d08f0a/numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c", size = 5038499, upload-time = "2024-08-26T20:11:43.902Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ff/ddf6dac2ff0dd50a7327bcdba45cb0264d0e96bb44d33324853f781a8f3c/numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c", size = 6633497, upload-time = "2024-08-26T20:11:55.09Z" }, + { url = "https://files.pythonhosted.org/packages/72/21/67f36eac8e2d2cd652a2e69595a54128297cdcb1ff3931cfc87838874bd4/numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692", size = 13621158, upload-time = "2024-08-26T20:12:14.95Z" }, + { url = "https://files.pythonhosted.org/packages/39/68/e9f1126d757653496dbc096cb429014347a36b228f5a991dae2c6b6cfd40/numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a", size = 19236173, upload-time = "2024-08-26T20:12:44.049Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e9/1f5333281e4ebf483ba1c888b1d61ba7e78d7e910fdd8e6499667041cc35/numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c", size = 19634174, upload-time = "2024-08-26T20:13:13.634Z" }, + { url = "https://files.pythonhosted.org/packages/71/af/a469674070c8d8408384e3012e064299f7a2de540738a8e414dcfd639996/numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded", size = 14099701, upload-time = "2024-08-26T20:13:34.851Z" }, + { url = "https://files.pythonhosted.org/packages/d0/3d/08ea9f239d0e0e939b6ca52ad403c84a2bce1bde301a8eb4888c1c1543f1/numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5", size = 6174313, upload-time = "2024-08-26T20:13:45.653Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b5/4ac39baebf1fdb2e72585c8352c56d063b6126be9fc95bd2bb5ef5770c20/numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a", size = 15606179, upload-time = "2024-08-26T20:14:08.786Z" }, + { url = "https://files.pythonhosted.org/packages/43/c1/41c8f6df3162b0c6ffd4437d729115704bd43363de0090c7f913cfbc2d89/numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c", size = 21169942, upload-time = "2024-08-26T20:14:40.108Z" }, + { url = "https://files.pythonhosted.org/packages/39/bc/fd298f308dcd232b56a4031fd6ddf11c43f9917fbc937e53762f7b5a3bb1/numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd", size = 13711512, upload-time = "2024-08-26T20:15:00.985Z" }, + { url = "https://files.pythonhosted.org/packages/96/ff/06d1aa3eeb1c614eda245c1ba4fb88c483bee6520d361641331872ac4b82/numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b", size = 5306976, upload-time = "2024-08-26T20:15:10.876Z" }, + { url = "https://files.pythonhosted.org/packages/2d/98/121996dcfb10a6087a05e54453e28e58694a7db62c5a5a29cee14c6e047b/numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729", size = 6906494, upload-time = "2024-08-26T20:15:22.055Z" }, + { url = "https://files.pythonhosted.org/packages/15/31/9dffc70da6b9bbf7968f6551967fc21156207366272c2a40b4ed6008dc9b/numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1", size = 13912596, upload-time = "2024-08-26T20:15:42.452Z" }, + { url = "https://files.pythonhosted.org/packages/b9/14/78635daab4b07c0930c919d451b8bf8c164774e6a3413aed04a6d95758ce/numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd", size = 19526099, upload-time = "2024-08-26T20:16:11.048Z" }, + { url = "https://files.pythonhosted.org/packages/26/4c/0eeca4614003077f68bfe7aac8b7496f04221865b3a5e7cb230c9d055afd/numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d", size = 19932823, upload-time = "2024-08-26T20:16:40.171Z" }, + { url = "https://files.pythonhosted.org/packages/f1/46/ea25b98b13dccaebddf1a803f8c748680d972e00507cd9bc6dcdb5aa2ac1/numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d", size = 14404424, upload-time = "2024-08-26T20:17:02.604Z" }, + { url = "https://files.pythonhosted.org/packages/c8/a6/177dd88d95ecf07e722d21008b1b40e681a929eb9e329684d449c36586b2/numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa", size = 6476809, upload-time = "2024-08-26T20:17:13.553Z" }, + { url = "https://files.pythonhosted.org/packages/ea/2b/7fc9f4e7ae5b507c1a3a21f0f15ed03e794c1242ea8a242ac158beb56034/numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73", size = 15911314, upload-time = "2024-08-26T20:17:36.72Z" }, + { url = "https://files.pythonhosted.org/packages/8f/3b/df5a870ac6a3be3a86856ce195ef42eec7ae50d2a202be1f5a4b3b340e14/numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8", size = 21025288, upload-time = "2024-08-26T20:18:07.732Z" }, + { url = "https://files.pythonhosted.org/packages/2c/97/51af92f18d6f6f2d9ad8b482a99fb74e142d71372da5d834b3a2747a446e/numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4", size = 6762793, upload-time = "2024-08-26T20:18:19.125Z" }, + { url = "https://files.pythonhosted.org/packages/12/46/de1fbd0c1b5ccaa7f9a005b66761533e2f6a3e560096682683a223631fe9/numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c", size = 19334885, upload-time = "2024-08-26T20:18:47.237Z" }, + { url = "https://files.pythonhosted.org/packages/cc/dc/d330a6faefd92b446ec0f0dfea4c3207bb1fef3c4771d19cf4543efd2c78/numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385", size = 15828784, upload-time = "2024-08-26T20:19:11.19Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/44/71852273146957899753e69986246d6a176061ea183407e95418c2aa4d9a/numpy-2.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7e88598032542bd49af7c4747541422884219056c268823ef6e5e89851c8825", size = 16955478, upload-time = "2026-01-31T23:10:25.623Z" }, + { url = "https://files.pythonhosted.org/packages/74/41/5d17d4058bd0cd96bcbd4d9ff0fb2e21f52702aab9a72e4a594efa18692f/numpy-2.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7edc794af8b36ca37ef5fcb5e0d128c7e0595c7b96a2318d1badb6fcd8ee86b1", size = 14965467, upload-time = "2026-01-31T23:10:28.186Z" }, + { url = "https://files.pythonhosted.org/packages/49/48/fb1ce8136c19452ed15f033f8aee91d5defe515094e330ce368a0647846f/numpy-2.4.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6e9f61981ace1360e42737e2bae58b27bf28a1b27e781721047d84bd754d32e7", size = 5475172, upload-time = "2026-01-31T23:10:30.848Z" }, + { url = "https://files.pythonhosted.org/packages/40/a9/3feb49f17bbd1300dd2570432961f5c8a4ffeff1db6f02c7273bd020a4c9/numpy-2.4.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cb7bbb88aa74908950d979eeaa24dbdf1a865e3c7e45ff0121d8f70387b55f73", size = 6805145, upload-time = "2026-01-31T23:10:32.352Z" }, + { url = "https://files.pythonhosted.org/packages/3f/39/fdf35cbd6d6e2fcad42fcf85ac04a85a0d0fbfbf34b30721c98d602fd70a/numpy-2.4.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f069069931240b3fc703f1e23df63443dbd6390614c8c44a87d96cd0ec81eb1", size = 15966084, upload-time = "2026-01-31T23:10:34.502Z" }, + { url = "https://files.pythonhosted.org/packages/1b/46/6fa4ea94f1ddf969b2ee941290cca6f1bfac92b53c76ae5f44afe17ceb69/numpy-2.4.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c02ef4401a506fb60b411467ad501e1429a3487abca4664871d9ae0b46c8ba32", size = 16899477, upload-time = "2026-01-31T23:10:37.075Z" }, + { url = "https://files.pythonhosted.org/packages/09/a1/2a424e162b1a14a5bd860a464ab4e07513916a64ab1683fae262f735ccd2/numpy-2.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2653de5c24910e49c2b106499803124dde62a5a1fe0eedeaecf4309a5f639390", size = 17323429, upload-time = "2026-01-31T23:10:39.704Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a2/73014149ff250628df72c58204822ac01d768697913881aacf839ff78680/numpy-2.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1ae241bbfc6ae276f94a170b14785e561cb5e7f626b6688cf076af4110887413", size = 18635109, upload-time = "2026-01-31T23:10:41.924Z" }, + { url = "https://files.pythonhosted.org/packages/6c/0c/73e8be2f1accd56df74abc1c5e18527822067dced5ec0861b5bb882c2ce0/numpy-2.4.2-cp311-cp311-win32.whl", hash = "sha256:df1b10187212b198dd45fa943d8985a3c8cf854aed4923796e0e019e113a1bda", size = 6237915, upload-time = "2026-01-31T23:10:45.26Z" }, + { url = "https://files.pythonhosted.org/packages/76/ae/e0265e0163cf127c24c3969d29f1c4c64551a1e375d95a13d32eab25d364/numpy-2.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:b9c618d56a29c9cb1c4da979e9899be7578d2e0b3c24d52079c166324c9e8695", size = 12607972, upload-time = "2026-01-31T23:10:47.021Z" }, + { url = "https://files.pythonhosted.org/packages/29/a5/c43029af9b8014d6ea157f192652c50042e8911f4300f8f6ed3336bf437f/numpy-2.4.2-cp311-cp311-win_arm64.whl", hash = "sha256:47c5a6ed21d9452b10227e5e8a0e1c22979811cad7dcc19d8e3e2fb8fa03f1a3", size = 10485763, upload-time = "2026-01-31T23:10:50.087Z" }, + { url = "https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a", size = 16667963, upload-time = "2026-01-31T23:10:52.147Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1", size = 14693571, upload-time = "2026-01-31T23:10:54.789Z" }, + { url = "https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e", size = 5203469, upload-time = "2026-01-31T23:10:57.343Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27", size = 6550820, upload-time = "2026-01-31T23:10:59.429Z" }, + { url = "https://files.pythonhosted.org/packages/b1/91/b97fdfd12dc75b02c44e26c6638241cc004d4079a0321a69c62f51470c4c/numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548", size = 15663067, upload-time = "2026-01-31T23:11:01.291Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c6/a18e59f3f0b8071cc85cbc8d80cd02d68aa9710170b2553a117203d46936/numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f", size = 16619782, upload-time = "2026-01-31T23:11:03.669Z" }, + { url = "https://files.pythonhosted.org/packages/b7/83/9751502164601a79e18847309f5ceec0b1446d7b6aa12305759b72cf98b2/numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460", size = 17013128, upload-time = "2026-01-31T23:11:05.913Z" }, + { url = "https://files.pythonhosted.org/packages/61/c4/c4066322256ec740acc1c8923a10047818691d2f8aec254798f3dd90f5f2/numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba", size = 18345324, upload-time = "2026-01-31T23:11:08.248Z" }, + { url = "https://files.pythonhosted.org/packages/ab/af/6157aa6da728fa4525a755bfad486ae7e3f76d4c1864138003eb84328497/numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f", size = 5960282, upload-time = "2026-01-31T23:11:10.497Z" }, + { url = "https://files.pythonhosted.org/packages/92/0f/7ceaaeaacb40567071e94dbf2c9480c0ae453d5bb4f52bea3892c39dc83c/numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85", size = 12314210, upload-time = "2026-01-31T23:11:12.176Z" }, + { url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z" }, + { url = "https://files.pythonhosted.org/packages/a1/22/815b9fe25d1d7ae7d492152adbc7226d3eff731dffc38fe970589fcaaa38/numpy-2.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c", size = 16663696, upload-time = "2026-01-31T23:11:17.516Z" }, + { url = "https://files.pythonhosted.org/packages/09/f0/817d03a03f93ba9c6c8993de509277d84e69f9453601915e4a69554102a1/numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979", size = 14688322, upload-time = "2026-01-31T23:11:19.883Z" }, + { url = "https://files.pythonhosted.org/packages/da/b4/f805ab79293c728b9a99438775ce51885fd4f31b76178767cfc718701a39/numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98", size = 5198157, upload-time = "2026-01-31T23:11:22.375Z" }, + { url = "https://files.pythonhosted.org/packages/74/09/826e4289844eccdcd64aac27d13b0fd3f32039915dd5b9ba01baae1f436c/numpy-2.4.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef", size = 6546330, upload-time = "2026-01-31T23:11:23.958Z" }, + { url = "https://files.pythonhosted.org/packages/19/fb/cbfdbfa3057a10aea5422c558ac57538e6acc87ec1669e666d32ac198da7/numpy-2.4.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7", size = 15660968, upload-time = "2026-01-31T23:11:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/04/dc/46066ce18d01645541f0186877377b9371b8fa8017fa8262002b4ef22612/numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499", size = 16607311, upload-time = "2026-01-31T23:11:28.117Z" }, + { url = "https://files.pythonhosted.org/packages/14/d9/4b5adfc39a43fa6bf918c6d544bc60c05236cc2f6339847fc5b35e6cb5b0/numpy-2.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb", size = 17012850, upload-time = "2026-01-31T23:11:30.888Z" }, + { url = "https://files.pythonhosted.org/packages/b7/20/adb6e6adde6d0130046e6fdfb7675cc62bc2f6b7b02239a09eb58435753d/numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7", size = 18334210, upload-time = "2026-01-31T23:11:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/78/0e/0a73b3dff26803a8c02baa76398015ea2a5434d9b8265a7898a6028c1591/numpy-2.4.2-cp313-cp313-win32.whl", hash = "sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110", size = 5958199, upload-time = "2026-01-31T23:11:35.385Z" }, + { url = "https://files.pythonhosted.org/packages/43/bc/6352f343522fcb2c04dbaf94cb30cca6fd32c1a750c06ad6231b4293708c/numpy-2.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622", size = 12310848, upload-time = "2026-01-31T23:11:38.001Z" }, + { url = "https://files.pythonhosted.org/packages/6e/8d/6da186483e308da5da1cc6918ce913dcfe14ffde98e710bfeff2a6158d4e/numpy-2.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71", size = 10221082, upload-time = "2026-01-31T23:11:40.392Z" }, + { url = "https://files.pythonhosted.org/packages/25/a1/9510aa43555b44781968935c7548a8926274f815de42ad3997e9e83680dd/numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262", size = 14815866, upload-time = "2026-01-31T23:11:42.495Z" }, + { url = "https://files.pythonhosted.org/packages/36/30/6bbb5e76631a5ae46e7923dd16ca9d3f1c93cfa8d4ed79a129814a9d8db3/numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913", size = 5325631, upload-time = "2026-01-31T23:11:44.7Z" }, + { url = "https://files.pythonhosted.org/packages/46/00/3a490938800c1923b567b3a15cd17896e68052e2145d8662aaf3e1ffc58f/numpy-2.4.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab", size = 6646254, upload-time = "2026-01-31T23:11:46.341Z" }, + { url = "https://files.pythonhosted.org/packages/d3/e9/fac0890149898a9b609caa5af7455a948b544746e4b8fe7c212c8edd71f8/numpy-2.4.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82", size = 15720138, upload-time = "2026-01-31T23:11:48.082Z" }, + { url = "https://files.pythonhosted.org/packages/ea/5c/08887c54e68e1e28df53709f1893ce92932cc6f01f7c3d4dc952f61ffd4e/numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f", size = 16655398, upload-time = "2026-01-31T23:11:50.293Z" }, + { url = "https://files.pythonhosted.org/packages/4d/89/253db0fa0e66e9129c745e4ef25631dc37d5f1314dad2b53e907b8538e6d/numpy-2.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554", size = 17079064, upload-time = "2026-01-31T23:11:52.927Z" }, + { url = "https://files.pythonhosted.org/packages/2a/d5/cbade46ce97c59c6c3da525e8d95b7abe8a42974a1dc5c1d489c10433e88/numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257", size = 18379680, upload-time = "2026-01-31T23:11:55.22Z" }, + { url = "https://files.pythonhosted.org/packages/40/62/48f99ae172a4b63d981babe683685030e8a3df4f246c893ea5c6ef99f018/numpy-2.4.2-cp313-cp313t-win32.whl", hash = "sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657", size = 6082433, upload-time = "2026-01-31T23:11:58.096Z" }, + { url = "https://files.pythonhosted.org/packages/07/38/e054a61cfe48ad9f1ed0d188e78b7e26859d0b60ef21cd9de4897cdb5326/numpy-2.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b", size = 12451181, upload-time = "2026-01-31T23:11:59.782Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a4/a05c3a6418575e185dd84d0b9680b6bb2e2dc3e4202f036b7b4e22d6e9dc/numpy-2.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1", size = 10290756, upload-time = "2026-01-31T23:12:02.438Z" }, + { url = "https://files.pythonhosted.org/packages/18/88/b7df6050bf18fdcfb7046286c6535cabbdd2064a3440fca3f069d319c16e/numpy-2.4.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:444be170853f1f9d528428eceb55f12918e4fda5d8805480f36a002f1415e09b", size = 16663092, upload-time = "2026-01-31T23:12:04.521Z" }, + { url = "https://files.pythonhosted.org/packages/25/7a/1fee4329abc705a469a4afe6e69b1ef7e915117747886327104a8493a955/numpy-2.4.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d1240d50adff70c2a88217698ca844723068533f3f5c5fa6ee2e3220e3bdb000", size = 14698770, upload-time = "2026-01-31T23:12:06.96Z" }, + { url = "https://files.pythonhosted.org/packages/fb/0b/f9e49ba6c923678ad5bc38181c08ac5e53b7a5754dbca8e581aa1a56b1ff/numpy-2.4.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:7cdde6de52fb6664b00b056341265441192d1291c130e99183ec0d4b110ff8b1", size = 5208562, upload-time = "2026-01-31T23:12:09.632Z" }, + { url = "https://files.pythonhosted.org/packages/7d/12/d7de8f6f53f9bb76997e5e4c069eda2051e3fe134e9181671c4391677bb2/numpy-2.4.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:cda077c2e5b780200b6b3e09d0b42205a3d1c68f30c6dceb90401c13bff8fe74", size = 6543710, upload-time = "2026-01-31T23:12:11.969Z" }, + { url = "https://files.pythonhosted.org/packages/09/63/c66418c2e0268a31a4cf8a8b512685748200f8e8e8ec6c507ce14e773529/numpy-2.4.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d30291931c915b2ab5717c2974bb95ee891a1cf22ebc16a8006bd59cd210d40a", size = 15677205, upload-time = "2026-01-31T23:12:14.33Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6c/7f237821c9642fb2a04d2f1e88b4295677144ca93285fd76eff3bcba858d/numpy-2.4.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bba37bc29d4d85761deed3954a1bc62be7cf462b9510b51d367b769a8c8df325", size = 16611738, upload-time = "2026-01-31T23:12:16.525Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a7/39c4cdda9f019b609b5c473899d87abff092fc908cfe4d1ecb2fcff453b0/numpy-2.4.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b2f0073ed0868db1dcd86e052d37279eef185b9c8db5bf61f30f46adac63c909", size = 17028888, upload-time = "2026-01-31T23:12:19.306Z" }, + { url = "https://files.pythonhosted.org/packages/da/b3/e84bb64bdfea967cc10950d71090ec2d84b49bc691df0025dddb7c26e8e3/numpy-2.4.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f54844851cdb630ceb623dcec4db3240d1ac13d4990532446761baede94996a", size = 18339556, upload-time = "2026-01-31T23:12:21.816Z" }, + { url = "https://files.pythonhosted.org/packages/88/f5/954a291bc1192a27081706862ac62bb5920fbecfbaa302f64682aa90beed/numpy-2.4.2-cp314-cp314-win32.whl", hash = "sha256:12e26134a0331d8dbd9351620f037ec470b7c75929cb8a1537f6bfe411152a1a", size = 6006899, upload-time = "2026-01-31T23:12:24.14Z" }, + { url = "https://files.pythonhosted.org/packages/05/cb/eff72a91b2efdd1bc98b3b8759f6a1654aa87612fc86e3d87d6fe4f948c4/numpy-2.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:068cdb2d0d644cdb45670810894f6a0600797a69c05f1ac478e8d31670b8ee75", size = 12443072, upload-time = "2026-01-31T23:12:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/37/75/62726948db36a56428fce4ba80a115716dc4fad6a3a4352487f8bb950966/numpy-2.4.2-cp314-cp314-win_arm64.whl", hash = "sha256:6ed0be1ee58eef41231a5c943d7d1375f093142702d5723ca2eb07db9b934b05", size = 10494886, upload-time = "2026-01-31T23:12:28.488Z" }, + { url = "https://files.pythonhosted.org/packages/36/2f/ee93744f1e0661dc267e4b21940870cabfae187c092e1433b77b09b50ac4/numpy-2.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:98f16a80e917003a12c0580f97b5f875853ebc33e2eaa4bccfc8201ac6869308", size = 14818567, upload-time = "2026-01-31T23:12:30.709Z" }, + { url = "https://files.pythonhosted.org/packages/a7/24/6535212add7d76ff938d8bdc654f53f88d35cddedf807a599e180dcb8e66/numpy-2.4.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:20abd069b9cda45874498b245c8015b18ace6de8546bf50dfa8cea1696ed06ef", size = 5328372, upload-time = "2026-01-31T23:12:32.962Z" }, + { url = "https://files.pythonhosted.org/packages/5e/9d/c48f0a035725f925634bf6b8994253b43f2047f6778a54147d7e213bc5a7/numpy-2.4.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e98c97502435b53741540a5717a6749ac2ada901056c7db951d33e11c885cc7d", size = 6649306, upload-time = "2026-01-31T23:12:34.797Z" }, + { url = "https://files.pythonhosted.org/packages/81/05/7c73a9574cd4a53a25907bad38b59ac83919c0ddc8234ec157f344d57d9a/numpy-2.4.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da6cad4e82cb893db4b69105c604d805e0c3ce11501a55b5e9f9083b47d2ffe8", size = 15722394, upload-time = "2026-01-31T23:12:36.565Z" }, + { url = "https://files.pythonhosted.org/packages/35/fa/4de10089f21fc7d18442c4a767ab156b25c2a6eaf187c0db6d9ecdaeb43f/numpy-2.4.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4424677ce4b47fe73c8b5556d876571f7c6945d264201180db2dc34f676ab5", size = 16653343, upload-time = "2026-01-31T23:12:39.188Z" }, + { url = "https://files.pythonhosted.org/packages/b8/f9/d33e4ffc857f3763a57aa85650f2e82486832d7492280ac21ba9efda80da/numpy-2.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2b8f157c8a6f20eb657e240f8985cc135598b2b46985c5bccbde7616dc9c6b1e", size = 17078045, upload-time = "2026-01-31T23:12:42.041Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b8/54bdb43b6225badbea6389fa038c4ef868c44f5890f95dd530a218706da3/numpy-2.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5daf6f3914a733336dab21a05cdec343144600e964d2fcdabaac0c0269874b2a", size = 18380024, upload-time = "2026-01-31T23:12:44.331Z" }, + { url = "https://files.pythonhosted.org/packages/a5/55/6e1a61ded7af8df04016d81b5b02daa59f2ea9252ee0397cb9f631efe9e5/numpy-2.4.2-cp314-cp314t-win32.whl", hash = "sha256:8c50dd1fc8826f5b26a5ee4d77ca55d88a895f4e4819c7ecc2a9f5905047a443", size = 6153937, upload-time = "2026-01-31T23:12:47.229Z" }, + { url = "https://files.pythonhosted.org/packages/45/aa/fa6118d1ed6d776b0983f3ceac9b1a5558e80df9365b1c3aa6d42bf9eee4/numpy-2.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fcf92bee92742edd401ba41135185866f7026c502617f422eb432cfeca4fe236", size = 12631844, upload-time = "2026-01-31T23:12:48.997Z" }, + { url = "https://files.pythonhosted.org/packages/32/0a/2ec5deea6dcd158f254a7b372fb09cfba5719419c8d66343bab35237b3fb/numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181", size = 10565379, upload-time = "2026-01-31T23:12:51.345Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f8/50e14d36d915ef64d8f8bc4a087fc8264d82c785eda6711f80ab7e620335/numpy-2.4.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:89f7268c009bc492f506abd6f5265defa7cb3f7487dc21d357c3d290add45082", size = 16833179, upload-time = "2026-01-31T23:12:53.5Z" }, + { url = "https://files.pythonhosted.org/packages/17/17/809b5cad63812058a8189e91a1e2d55a5a18fd04611dbad244e8aeae465c/numpy-2.4.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6dee3bb76aa4009d5a912180bf5b2de012532998d094acee25d9cb8dee3e44a", size = 14889755, upload-time = "2026-01-31T23:12:55.933Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ea/181b9bcf7627fc8371720316c24db888dcb9829b1c0270abf3d288b2e29b/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:cd2bd2bbed13e213d6b55dc1d035a4f91748a7d3edc9480c13898b0353708920", size = 5399500, upload-time = "2026-01-31T23:12:58.671Z" }, + { url = "https://files.pythonhosted.org/packages/33/9f/413adf3fc955541ff5536b78fcf0754680b3c6d95103230252a2c9408d23/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:cf28c0c1d4c4bf00f509fa7eb02c58d7caf221b50b467bcb0d9bbf1584d5c821", size = 6714252, upload-time = "2026-01-31T23:13:00.518Z" }, + { url = "https://files.pythonhosted.org/packages/91/da/643aad274e29ccbdf42ecd94dafe524b81c87bcb56b83872d54827f10543/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e04ae107ac591763a47398bb45b568fc38f02dbc4aa44c063f67a131f99346cb", size = 15797142, upload-time = "2026-01-31T23:13:02.219Z" }, + { url = "https://files.pythonhosted.org/packages/66/27/965b8525e9cb5dc16481b30a1b3c21e50c7ebf6e9dbd48d0c4d0d5089c7e/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:602f65afdef699cda27ec0b9224ae5dc43e328f4c24c689deaf77133dbee74d0", size = 16727979, upload-time = "2026-01-31T23:13:04.62Z" }, + { url = "https://files.pythonhosted.org/packages/de/e5/b7d20451657664b07986c2f6e3be564433f5dcaf3482d68eaecd79afaf03/numpy-2.4.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be71bf1edb48ebbbf7f6337b5bfd2f895d1902f6335a5830b20141fc126ffba0", size = 12502577, upload-time = "2026-01-31T23:13:07.08Z" }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.10.*'", + "python_full_version < '3.10'", +] +dependencies = [ + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "python-dateutil", marker = "python_full_version < '3.11'" }, + { name = "pytz", marker = "python_full_version < '3.11'" }, + { name = "tzdata", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/f7/f425a00df4fcc22b292c6895c6831c0c8ae1d9fac1e024d16f98a9ce8749/pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c", size = 11555763, upload-time = "2025-09-29T23:16:53.287Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/66d99628ff8ce7857aca52fed8f0066ce209f96be2fede6cef9f84e8d04f/pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a", size = 10801217, upload-time = "2025-09-29T23:17:04.522Z" }, + { url = "https://files.pythonhosted.org/packages/1d/03/3fc4a529a7710f890a239cc496fc6d50ad4a0995657dccc1d64695adb9f4/pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1", size = 12148791, upload-time = "2025-09-29T23:17:18.444Z" }, + { url = "https://files.pythonhosted.org/packages/40/a8/4dac1f8f8235e5d25b9955d02ff6f29396191d4e665d71122c3722ca83c5/pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838", size = 12769373, upload-time = "2025-09-29T23:17:35.846Z" }, + { url = "https://files.pythonhosted.org/packages/df/91/82cc5169b6b25440a7fc0ef3a694582418d875c8e3ebf796a6d6470aa578/pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250", size = 13200444, upload-time = "2025-09-29T23:17:49.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/ae/89b3283800ab58f7af2952704078555fa60c807fff764395bb57ea0b0dbd/pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4", size = 13858459, upload-time = "2025-09-29T23:18:03.722Z" }, + { url = "https://files.pythonhosted.org/packages/85/72/530900610650f54a35a19476eca5104f38555afccda1aa11a92ee14cb21d/pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826", size = 11346086, upload-time = "2025-09-29T23:18:18.505Z" }, + { url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790, upload-time = "2025-09-29T23:18:30.065Z" }, + { url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831, upload-time = "2025-09-29T23:38:56.071Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267, upload-time = "2025-09-29T23:18:41.627Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281, upload-time = "2025-09-29T23:18:56.834Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453, upload-time = "2025-09-29T23:19:09.247Z" }, + { url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361, upload-time = "2025-09-29T23:19:25.342Z" }, + { url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702, upload-time = "2025-09-29T23:19:38.296Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, + { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, + { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, + { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, + { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, + { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, + { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, + { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, + { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, + { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, + { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, + { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" }, + { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" }, + { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" }, + { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" }, + { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" }, + { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" }, + { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" }, + { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" }, + { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" }, + { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, + { url = "https://files.pythonhosted.org/packages/56/b4/52eeb530a99e2a4c55ffcd352772b599ed4473a0f892d127f4147cf0f88e/pandas-2.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c503ba5216814e295f40711470446bc3fd00f0faea8a086cbc688808e26f92a2", size = 11567720, upload-time = "2025-09-29T23:33:06.209Z" }, + { url = "https://files.pythonhosted.org/packages/48/4a/2d8b67632a021bced649ba940455ed441ca854e57d6e7658a6024587b083/pandas-2.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a637c5cdfa04b6d6e2ecedcb81fc52ffb0fd78ce2ebccc9ea964df9f658de8c8", size = 10810302, upload-time = "2025-09-29T23:33:35.846Z" }, + { url = "https://files.pythonhosted.org/packages/13/e6/d2465010ee0569a245c975dc6967b801887068bc893e908239b1f4b6c1ac/pandas-2.3.3-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:854d00d556406bffe66a4c0802f334c9ad5a96b4f1f868adf036a21b11ef13ff", size = 12154874, upload-time = "2025-09-29T23:33:49.939Z" }, + { url = "https://files.pythonhosted.org/packages/1f/18/aae8c0aa69a386a3255940e9317f793808ea79d0a525a97a903366bb2569/pandas-2.3.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf1f8a81d04ca90e32a0aceb819d34dbd378a98bf923b6398b9a3ec0bf44de29", size = 12790141, upload-time = "2025-09-29T23:34:05.655Z" }, + { url = "https://files.pythonhosted.org/packages/f7/26/617f98de789de00c2a444fbe6301bb19e66556ac78cff933d2c98f62f2b4/pandas-2.3.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:23ebd657a4d38268c7dfbdf089fbc31ea709d82e4923c5ffd4fbd5747133ce73", size = 13208697, upload-time = "2025-09-29T23:34:21.835Z" }, + { url = "https://files.pythonhosted.org/packages/b9/fb/25709afa4552042bd0e15717c75e9b4a2294c3dc4f7e6ea50f03c5136600/pandas-2.3.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5554c929ccc317d41a5e3d1234f3be588248e61f08a74dd17c9eabb535777dc9", size = 13879233, upload-time = "2025-09-29T23:34:35.079Z" }, + { url = "https://files.pythonhosted.org/packages/98/af/7be05277859a7bc399da8ba68b88c96b27b48740b6cf49688899c6eb4176/pandas-2.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:d3e28b3e83862ccf4d85ff19cf8c20b2ae7e503881711ff2d534dc8f761131aa", size = 11359119, upload-time = "2025-09-29T23:34:46.339Z" }, +] + +[[package]] +name = "pandas" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +dependencies = [ + { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "python-dateutil", marker = "python_full_version >= '3.11'" }, + { name = "tzdata", marker = "(python_full_version >= '3.11' and sys_platform == 'emscripten') or (python_full_version >= '3.11' and sys_platform == 'win32')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/0c/b28ed414f080ee0ad153f848586d61d1878f91689950f037f976ce15f6c8/pandas-3.0.1.tar.gz", hash = "sha256:4186a699674af418f655dbd420ed87f50d56b4cd6603784279d9eef6627823c8", size = 4641901, upload-time = "2026-02-17T22:20:16.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/07/c7087e003ceee9b9a82539b40414ec557aa795b584a1a346e89180853d79/pandas-3.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de09668c1bf3b925c07e5762291602f0d789eca1b3a781f99c1c78f6cac0e7ea", size = 10323380, upload-time = "2026-02-17T22:18:16.133Z" }, + { url = "https://files.pythonhosted.org/packages/c1/27/90683c7122febeefe84a56f2cde86a9f05f68d53885cebcc473298dfc33e/pandas-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:24ba315ba3d6e5806063ac6eb717504e499ce30bd8c236d8693a5fd3f084c796", size = 9923455, upload-time = "2026-02-17T22:18:19.13Z" }, + { url = "https://files.pythonhosted.org/packages/0e/f1/ed17d927f9950643bc7631aa4c99ff0cc83a37864470bc419345b656a41f/pandas-3.0.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:406ce835c55bac912f2a0dcfaf27c06d73c6b04a5dde45f1fd3169ce31337389", size = 10753464, upload-time = "2026-02-17T22:18:21.134Z" }, + { url = "https://files.pythonhosted.org/packages/2e/7c/870c7e7daec2a6c7ff2ac9e33b23317230d4e4e954b35112759ea4a924a7/pandas-3.0.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:830994d7e1f31dd7e790045235605ab61cff6c94defc774547e8b7fdfbff3dc7", size = 11255234, upload-time = "2026-02-17T22:18:24.175Z" }, + { url = "https://files.pythonhosted.org/packages/5c/39/3653fe59af68606282b989c23d1a543ceba6e8099cbcc5f1d506a7bae2aa/pandas-3.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a64ce8b0f2de1d2efd2ae40b0abe7f8ae6b29fbfb3812098ed5a6f8e235ad9bf", size = 11767299, upload-time = "2026-02-17T22:18:26.824Z" }, + { url = "https://files.pythonhosted.org/packages/9b/31/1daf3c0c94a849c7a8dab8a69697b36d313b229918002ba3e409265c7888/pandas-3.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9832c2c69da24b602c32e0c7b1b508a03949c18ba08d4d9f1c1033426685b447", size = 12333292, upload-time = "2026-02-17T22:18:28.996Z" }, + { url = "https://files.pythonhosted.org/packages/1f/67/af63f83cd6ca603a00fe8530c10a60f0879265b8be00b5930e8e78c5b30b/pandas-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:84f0904a69e7365f79a0c77d3cdfccbfb05bf87847e3a51a41e1426b0edb9c79", size = 9892176, upload-time = "2026-02-17T22:18:31.79Z" }, + { url = "https://files.pythonhosted.org/packages/79/ab/9c776b14ac4b7b4140788eca18468ea39894bc7340a408f1d1e379856a6b/pandas-3.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:4a68773d5a778afb31d12e34f7dd4612ab90de8c6fb1d8ffe5d4a03b955082a1", size = 9151328, upload-time = "2026-02-17T22:18:35.721Z" }, + { url = "https://files.pythonhosted.org/packages/37/51/b467209c08dae2c624873d7491ea47d2b47336e5403309d433ea79c38571/pandas-3.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:476f84f8c20c9f5bc47252b66b4bb25e1a9fc2fa98cead96744d8116cb85771d", size = 10344357, upload-time = "2026-02-17T22:18:38.262Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f1/e2567ffc8951ab371db2e40b2fe068e36b81d8cf3260f06ae508700e5504/pandas-3.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0ab749dfba921edf641d4036c4c21c0b3ea70fea478165cb98a998fb2a261955", size = 9884543, upload-time = "2026-02-17T22:18:41.476Z" }, + { url = "https://files.pythonhosted.org/packages/d7/39/327802e0b6d693182403c144edacbc27eb82907b57062f23ef5a4c4a5ea7/pandas-3.0.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8e36891080b87823aff3640c78649b91b8ff6eea3c0d70aeabd72ea43ab069b", size = 10396030, upload-time = "2026-02-17T22:18:43.822Z" }, + { url = "https://files.pythonhosted.org/packages/3d/fe/89d77e424365280b79d99b3e1e7d606f5165af2f2ecfaf0c6d24c799d607/pandas-3.0.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:532527a701281b9dd371e2f582ed9094f4c12dd9ffb82c0c54ee28d8ac9520c4", size = 10876435, upload-time = "2026-02-17T22:18:45.954Z" }, + { url = "https://files.pythonhosted.org/packages/b5/a6/2a75320849dd154a793f69c951db759aedb8d1dd3939eeacda9bdcfa1629/pandas-3.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:356e5c055ed9b0da1580d465657bc7d00635af4fd47f30afb23025352ba764d1", size = 11405133, upload-time = "2026-02-17T22:18:48.533Z" }, + { url = "https://files.pythonhosted.org/packages/58/53/1d68fafb2e02d7881df66aa53be4cd748d25cbe311f3b3c85c93ea5d30ca/pandas-3.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9d810036895f9ad6345b8f2a338dd6998a74e8483847403582cab67745bff821", size = 11932065, upload-time = "2026-02-17T22:18:50.837Z" }, + { url = "https://files.pythonhosted.org/packages/75/08/67cc404b3a966b6df27b38370ddd96b3b023030b572283d035181854aac5/pandas-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:536232a5fe26dd989bd633e7a0c450705fdc86a207fec7254a55e9a22950fe43", size = 9741627, upload-time = "2026-02-17T22:18:53.905Z" }, + { url = "https://files.pythonhosted.org/packages/86/4f/caf9952948fb00d23795f09b893d11f1cacb384e666854d87249530f7cbe/pandas-3.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f463ebfd8de7f326d38037c7363c6dacb857c5881ab8961fb387804d6daf2f7", size = 9052483, upload-time = "2026-02-17T22:18:57.31Z" }, + { url = "https://files.pythonhosted.org/packages/0b/48/aad6ec4f8d007534c091e9a7172b3ec1b1ee6d99a9cbb936b5eab6c6cf58/pandas-3.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5272627187b5d9c20e55d27caf5f2cd23e286aba25cadf73c8590e432e2b7262", size = 10317509, upload-time = "2026-02-17T22:18:59.498Z" }, + { url = "https://files.pythonhosted.org/packages/a8/14/5990826f779f79148ae9d3a2c39593dc04d61d5d90541e71b5749f35af95/pandas-3.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:661e0f665932af88c7877f31da0dc743fe9c8f2524bdffe23d24fdcb67ef9d56", size = 9860561, upload-time = "2026-02-17T22:19:02.265Z" }, + { url = "https://files.pythonhosted.org/packages/fa/80/f01ff54664b6d70fed71475543d108a9b7c888e923ad210795bef04ffb7d/pandas-3.0.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75e6e292ff898679e47a2199172593d9f6107fd2dd3617c22c2946e97d5df46e", size = 10365506, upload-time = "2026-02-17T22:19:05.017Z" }, + { url = "https://files.pythonhosted.org/packages/f2/85/ab6d04733a7d6ff32bfc8382bf1b07078228f5d6ebec5266b91bfc5c4ff7/pandas-3.0.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ff8cf1d2896e34343197685f432450ec99a85ba8d90cce2030c5eee2ef98791", size = 10873196, upload-time = "2026-02-17T22:19:07.204Z" }, + { url = "https://files.pythonhosted.org/packages/48/a9/9301c83d0b47c23ac5deab91c6b39fd98d5b5db4d93b25df8d381451828f/pandas-3.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eca8b4510f6763f3d37359c2105df03a7a221a508f30e396a51d0713d462e68a", size = 11370859, upload-time = "2026-02-17T22:19:09.436Z" }, + { url = "https://files.pythonhosted.org/packages/59/fe/0c1fc5bd2d29c7db2ab372330063ad555fb83e08422829c785f5ec2176ca/pandas-3.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:06aff2ad6f0b94a17822cf8b83bbb563b090ed82ff4fe7712db2ce57cd50d9b8", size = 11924584, upload-time = "2026-02-17T22:19:11.562Z" }, + { url = "https://files.pythonhosted.org/packages/d6/7d/216a1588b65a7aa5f4535570418a599d943c85afb1d95b0876fc00aa1468/pandas-3.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:9fea306c783e28884c29057a1d9baa11a349bbf99538ec1da44c8476563d1b25", size = 9742769, upload-time = "2026-02-17T22:19:13.926Z" }, + { url = "https://files.pythonhosted.org/packages/c4/cb/810a22a6af9a4e97c8ab1c946b47f3489c5bca5adc483ce0ffc84c9cc768/pandas-3.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:a8d37a43c52917427e897cb2e429f67a449327394396a81034a4449b99afda59", size = 9043855, upload-time = "2026-02-17T22:19:16.09Z" }, + { url = "https://files.pythonhosted.org/packages/92/fa/423c89086cca1f039cf1253c3ff5b90f157b5b3757314aa635f6bf3e30aa/pandas-3.0.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d54855f04f8246ed7b6fc96b05d4871591143c46c0b6f4af874764ed0d2d6f06", size = 10752673, upload-time = "2026-02-17T22:19:18.304Z" }, + { url = "https://files.pythonhosted.org/packages/22/23/b5a08ec1f40020397f0faba72f1e2c11f7596a6169c7b3e800abff0e433f/pandas-3.0.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e1b677accee34a09e0dc2ce5624e4a58a1870ffe56fc021e9caf7f23cd7668f", size = 10404967, upload-time = "2026-02-17T22:19:20.726Z" }, + { url = "https://files.pythonhosted.org/packages/5c/81/94841f1bb4afdc2b52a99daa895ac2c61600bb72e26525ecc9543d453ebc/pandas-3.0.1-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a9cabbdcd03f1b6cd254d6dda8ae09b0252524be1592594c00b7895916cb1324", size = 10320575, upload-time = "2026-02-17T22:19:24.919Z" }, + { url = "https://files.pythonhosted.org/packages/0a/8b/2ae37d66a5342a83adadfd0cb0b4bf9c3c7925424dd5f40d15d6cfaa35ee/pandas-3.0.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ae2ab1f166668b41e770650101e7090824fd34d17915dd9cd479f5c5e0065e9", size = 10710921, upload-time = "2026-02-17T22:19:27.181Z" }, + { url = "https://files.pythonhosted.org/packages/a2/61/772b2e2757855e232b7ccf7cb8079a5711becb3a97f291c953def15a833f/pandas-3.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6bf0603c2e30e2cafac32807b06435f28741135cb8697eae8b28c7d492fc7d76", size = 11334191, upload-time = "2026-02-17T22:19:29.411Z" }, + { url = "https://files.pythonhosted.org/packages/1b/08/b16c6df3ef555d8495d1d265a7963b65be166785d28f06a350913a4fac78/pandas-3.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c426422973973cae1f4a23e51d4ae85974f44871b24844e4f7de752dd877098", size = 11782256, upload-time = "2026-02-17T22:19:32.34Z" }, + { url = "https://files.pythonhosted.org/packages/55/80/178af0594890dee17e239fca96d3d8670ba0f5ff59b7d0439850924a9c09/pandas-3.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b03f91ae8c10a85c1613102c7bef5229b5379f343030a3ccefeca8a33414cf35", size = 10485047, upload-time = "2026-02-17T22:19:34.605Z" }, + { url = "https://files.pythonhosted.org/packages/bb/8b/4bb774a998b97e6c2fd62a9e6cfdaae133b636fd1c468f92afb4ae9a447a/pandas-3.0.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:99d0f92ed92d3083d140bf6b97774f9f13863924cf3f52a70711f4e7588f9d0a", size = 10322465, upload-time = "2026-02-17T22:19:36.803Z" }, + { url = "https://files.pythonhosted.org/packages/72/3a/5b39b51c64159f470f1ca3b1c2a87da290657ca022f7cd11442606f607d1/pandas-3.0.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3b66857e983208654294bb6477b8a63dee26b37bdd0eb34d010556e91261784f", size = 9910632, upload-time = "2026-02-17T22:19:39.001Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f7/b449ffb3f68c11da12fc06fbf6d2fa3a41c41e17d0284d23a79e1c13a7e4/pandas-3.0.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56cf59638bf24dc9bdf2154c81e248b3289f9a09a6d04e63608c159022352749", size = 10440535, upload-time = "2026-02-17T22:19:41.157Z" }, + { url = "https://files.pythonhosted.org/packages/55/77/6ea82043db22cb0f2bbfe7198da3544000ddaadb12d26be36e19b03a2dc5/pandas-3.0.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1a9f55e0f46951874b863d1f3906dcb57df2d9be5c5847ba4dfb55b2c815249", size = 10893940, upload-time = "2026-02-17T22:19:43.493Z" }, + { url = "https://files.pythonhosted.org/packages/03/30/f1b502a72468c89412c1b882a08f6eed8a4ee9dc033f35f65d0663df6081/pandas-3.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1849f0bba9c8a2fb0f691d492b834cc8dadf617e29015c66e989448d58d011ee", size = 11442711, upload-time = "2026-02-17T22:19:46.074Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f0/ebb6ddd8fc049e98cabac5c2924d14d1dda26a20adb70d41ea2e428d3ec4/pandas-3.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3d288439e11b5325b02ae6e9cc83e6805a62c40c5a6220bea9beb899c073b1c", size = 11963918, upload-time = "2026-02-17T22:19:48.838Z" }, + { url = "https://files.pythonhosted.org/packages/09/f8/8ce132104074f977f907442790eaae24e27bce3b3b454e82faa3237ff098/pandas-3.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:93325b0fe372d192965f4cca88d97667f49557398bbf94abdda3bf1b591dbe66", size = 9862099, upload-time = "2026-02-17T22:19:51.081Z" }, + { url = "https://files.pythonhosted.org/packages/e6/b7/6af9aac41ef2456b768ef0ae60acf8abcebb450a52043d030a65b4b7c9bd/pandas-3.0.1-cp314-cp314-win_arm64.whl", hash = "sha256:97ca08674e3287c7148f4858b01136f8bdfe7202ad25ad04fec602dd1d29d132", size = 9185333, upload-time = "2026-02-17T22:19:53.266Z" }, + { url = "https://files.pythonhosted.org/packages/66/fc/848bb6710bc6061cb0c5badd65b92ff75c81302e0e31e496d00029fe4953/pandas-3.0.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:58eeb1b2e0fb322befcf2bbc9ba0af41e616abadb3d3414a6bc7167f6cbfce32", size = 10772664, upload-time = "2026-02-17T22:19:55.806Z" }, + { url = "https://files.pythonhosted.org/packages/69/5c/866a9bbd0f79263b4b0db6ec1a341be13a1473323f05c122388e0f15b21d/pandas-3.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cd9af1276b5ca9e298bd79a26bda32fa9cc87ed095b2a9a60978d2ca058eaf87", size = 10421286, upload-time = "2026-02-17T22:19:58.091Z" }, + { url = "https://files.pythonhosted.org/packages/51/a4/2058fb84fb1cfbfb2d4a6d485e1940bb4ad5716e539d779852494479c580/pandas-3.0.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f87a04984d6b63788327cd9f79dda62b7f9043909d2440ceccf709249ca988", size = 10342050, upload-time = "2026-02-17T22:20:01.376Z" }, + { url = "https://files.pythonhosted.org/packages/22/1b/674e89996cc4be74db3c4eb09240c4bb549865c9c3f5d9b086ff8fcfbf00/pandas-3.0.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85fe4c4df62e1e20f9db6ebfb88c844b092c22cd5324bdcf94bfa2fc1b391221", size = 10740055, upload-time = "2026-02-17T22:20:04.328Z" }, + { url = "https://files.pythonhosted.org/packages/d0/f8/e954b750764298c22fa4614376531fe63c521ef517e7059a51f062b87dca/pandas-3.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:331ca75a2f8672c365ae25c0b29e46f5ac0c6551fdace8eec4cd65e4fac271ff", size = 11357632, upload-time = "2026-02-17T22:20:06.647Z" }, + { url = "https://files.pythonhosted.org/packages/6d/02/c6e04b694ffd68568297abd03588b6d30295265176a5c01b7459d3bc35a3/pandas-3.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:15860b1fdb1973fffade772fdb931ccf9b2f400a3f5665aef94a00445d7d8dd5", size = 11810974, upload-time = "2026-02-17T22:20:08.946Z" }, + { url = "https://files.pythonhosted.org/packages/89/41/d7dfb63d2407f12055215070c42fc6ac41b66e90a2946cdc5e759058398b/pandas-3.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:44f1364411d5670efa692b146c748f4ed013df91ee91e9bec5677fb1fd58b937", size = 10884622, upload-time = "2026-02-17T22:20:11.711Z" }, + { url = "https://files.pythonhosted.org/packages/68/b0/34937815889fa982613775e4b97fddd13250f11012d769949c5465af2150/pandas-3.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:108dd1790337a494aa80e38def654ca3f0968cf4f362c85f44c15e471667102d", size = 9452085, upload-time = "2026-02-17T22:20:14.331Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pycparser" +version = "2.23" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pygithub" +version = "2.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyjwt", extra = ["crypto"] }, + { name = "pynacl" }, + { name = "requests" }, + { name = "typing-extensions" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c1/74/e560bdeffea72ecb26cff27f0fad548bbff5ecc51d6a155311ea7f9e4c4c/pygithub-2.8.1.tar.gz", hash = "sha256:341b7c78521cb07324ff670afd1baa2bf5c286f8d9fd302c1798ba594a5400c9", size = 2246994, upload-time = "2025-09-02T17:41:54.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/ba/7049ce39f653f6140aac4beb53a5aaf08b4407b6a3019aae394c1c5244ff/pygithub-2.8.1-py3-none-any.whl", hash = "sha256:23a0a5bca93baef082e03411bf0ce27204c32be8bfa7abc92fe4a3e132936df0", size = 432709, upload-time = "2025-09-02T17:41:52.947Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyjwt" +version = "2.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/5a/b46fa56bf322901eee5b0454a34343cdbdae202cd421775a8ee4e42fd519/pyjwt-2.11.0.tar.gz", hash = "sha256:35f95c1f0fbe5d5ba6e43f00271c275f7a1a4db1dab27bf708073b75318ea623", size = 98019, upload-time = "2026-01-30T19:59:55.694Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/01/c26ce75ba460d5cd503da9e13b21a33804d38c2165dec7b716d06b13010c/pyjwt-2.11.0-py3-none-any.whl", hash = "sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469", size = 28224, upload-time = "2026-01-30T19:59:54.539Z" }, +] + +[package.optional-dependencies] +crypto = [ + { name = "cryptography" }, +] + +[[package]] +name = "pynacl" +version = "1.6.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/9a/4019b524b03a13438637b11538c82781a5eda427394380381af8f04f467a/pynacl-1.6.2.tar.gz", hash = "sha256:018494d6d696ae03c7e656e5e74cdfd8ea1326962cc401bcf018f1ed8436811c", size = 3511692, upload-time = "2026-01-01T17:48:10.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/79/0e3c34dc3c4671f67d251c07aa8eb100916f250ee470df230b0ab89551b4/pynacl-1.6.2-cp314-cp314t-macosx_10_10_universal2.whl", hash = "sha256:622d7b07cc5c02c666795792931b50c91f3ce3c2649762efb1ef0d5684c81594", size = 390064, upload-time = "2026-01-01T17:31:57.264Z" }, + { url = "https://files.pythonhosted.org/packages/eb/1c/23a26e931736e13b16483795c8a6b2f641bf6a3d5238c22b070a5112722c/pynacl-1.6.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d071c6a9a4c94d79eb665db4ce5cedc537faf74f2355e4d502591d850d3913c0", size = 809370, upload-time = "2026-01-01T17:31:59.198Z" }, + { url = "https://files.pythonhosted.org/packages/87/74/8d4b718f8a22aea9e8dcc8b95deb76d4aae380e2f5b570cc70b5fd0a852d/pynacl-1.6.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe9847ca47d287af41e82be1dd5e23023d3c31a951da134121ab02e42ac218c9", size = 1408304, upload-time = "2026-01-01T17:32:01.162Z" }, + { url = "https://files.pythonhosted.org/packages/fd/73/be4fdd3a6a87fe8a4553380c2b47fbd1f7f58292eb820902f5c8ac7de7b0/pynacl-1.6.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:04316d1fc625d860b6c162fff704eb8426b1a8bcd3abacea11142cbd99a6b574", size = 844871, upload-time = "2026-01-01T17:32:02.824Z" }, + { url = "https://files.pythonhosted.org/packages/55/ad/6efc57ab75ee4422e96b5f2697d51bbcf6cdcc091e66310df91fbdc144a8/pynacl-1.6.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44081faff368d6c5553ccf55322ef2819abb40e25afaec7e740f159f74813634", size = 1446356, upload-time = "2026-01-01T17:32:04.452Z" }, + { url = "https://files.pythonhosted.org/packages/78/b7/928ee9c4779caa0a915844311ab9fb5f99585621c5d6e4574538a17dca07/pynacl-1.6.2-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:a9f9932d8d2811ce1a8ffa79dcbdf3970e7355b5c8eb0c1a881a57e7f7d96e88", size = 826814, upload-time = "2026-01-01T17:32:06.078Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a9/1bdba746a2be20f8809fee75c10e3159d75864ef69c6b0dd168fc60e485d/pynacl-1.6.2-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:bc4a36b28dd72fb4845e5d8f9760610588a96d5a51f01d84d8c6ff9849968c14", size = 1411742, upload-time = "2026-01-01T17:32:07.651Z" }, + { url = "https://files.pythonhosted.org/packages/f3/2f/5e7ea8d85f9f3ea5b6b87db1d8388daa3587eed181bdeb0306816fdbbe79/pynacl-1.6.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bffb6d0f6becacb6526f8f42adfb5efb26337056ee0831fb9a7044d1a964444", size = 801714, upload-time = "2026-01-01T17:32:09.558Z" }, + { url = "https://files.pythonhosted.org/packages/06/ea/43fe2f7eab5f200e40fb10d305bf6f87ea31b3bbc83443eac37cd34a9e1e/pynacl-1.6.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2fef529ef3ee487ad8113d287a593fa26f48ee3620d92ecc6f1d09ea38e0709b", size = 1372257, upload-time = "2026-01-01T17:32:11.026Z" }, + { url = "https://files.pythonhosted.org/packages/4d/54/c9ea116412788629b1347e415f72195c25eb2f3809b2d3e7b25f5c79f13a/pynacl-1.6.2-cp314-cp314t-win32.whl", hash = "sha256:a84bf1c20339d06dc0c85d9aea9637a24f718f375d861b2668b2f9f96fa51145", size = 231319, upload-time = "2026-01-01T17:32:12.46Z" }, + { url = "https://files.pythonhosted.org/packages/ce/04/64e9d76646abac2dccf904fccba352a86e7d172647557f35b9fe2a5ee4a1/pynacl-1.6.2-cp314-cp314t-win_amd64.whl", hash = "sha256:320ef68a41c87547c91a8b58903c9caa641ab01e8512ce291085b5fe2fcb7590", size = 244044, upload-time = "2026-01-01T17:32:13.781Z" }, + { url = "https://files.pythonhosted.org/packages/33/33/7873dc161c6a06f43cda13dec67b6fe152cb2f982581151956fa5e5cdb47/pynacl-1.6.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d29bfe37e20e015a7d8b23cfc8bd6aa7909c92a1b8f41ee416bbb3e79ef182b2", size = 188740, upload-time = "2026-01-01T17:32:15.083Z" }, + { url = "https://files.pythonhosted.org/packages/be/7b/4845bbf88e94586ec47a432da4e9107e3fc3ce37eb412b1398630a37f7dd/pynacl-1.6.2-cp38-abi3-macosx_10_10_universal2.whl", hash = "sha256:c949ea47e4206af7c8f604b8278093b674f7c79ed0d4719cc836902bf4517465", size = 388458, upload-time = "2026-01-01T17:32:16.829Z" }, + { url = "https://files.pythonhosted.org/packages/1e/b4/e927e0653ba63b02a4ca5b4d852a8d1d678afbf69b3dbf9c4d0785ac905c/pynacl-1.6.2-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8845c0631c0be43abdd865511c41eab235e0be69c81dc66a50911594198679b0", size = 800020, upload-time = "2026-01-01T17:32:18.34Z" }, + { url = "https://files.pythonhosted.org/packages/7f/81/d60984052df5c97b1d24365bc1e30024379b42c4edcd79d2436b1b9806f2/pynacl-1.6.2-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22de65bb9010a725b0dac248f353bb072969c94fa8d6b1f34b87d7953cf7bbe4", size = 1399174, upload-time = "2026-01-01T17:32:20.239Z" }, + { url = "https://files.pythonhosted.org/packages/68/f7/322f2f9915c4ef27d140101dd0ed26b479f7e6f5f183590fd32dfc48c4d3/pynacl-1.6.2-cp38-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:46065496ab748469cdd999246d17e301b2c24ae2fdf739132e580a0e94c94a87", size = 835085, upload-time = "2026-01-01T17:32:22.24Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d0/f301f83ac8dbe53442c5a43f6a39016f94f754d7a9815a875b65e218a307/pynacl-1.6.2-cp38-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a66d6fb6ae7661c58995f9c6435bda2b1e68b54b598a6a10247bfcdadac996c", size = 1437614, upload-time = "2026-01-01T17:32:23.766Z" }, + { url = "https://files.pythonhosted.org/packages/c4/58/fc6e649762b029315325ace1a8c6be66125e42f67416d3dbd47b69563d61/pynacl-1.6.2-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:26bfcd00dcf2cf160f122186af731ae30ab120c18e8375684ec2670dccd28130", size = 818251, upload-time = "2026-01-01T17:32:25.69Z" }, + { url = "https://files.pythonhosted.org/packages/c9/a8/b917096b1accc9acd878819a49d3d84875731a41eb665f6ebc826b1af99e/pynacl-1.6.2-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c8a231e36ec2cab018c4ad4358c386e36eede0319a0c41fed24f840b1dac59f6", size = 1402859, upload-time = "2026-01-01T17:32:27.215Z" }, + { url = "https://files.pythonhosted.org/packages/85/42/fe60b5f4473e12c72f977548e4028156f4d340b884c635ec6b063fe7e9a5/pynacl-1.6.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:68be3a09455743ff9505491220b64440ced8973fe930f270c8e07ccfa25b1f9e", size = 791926, upload-time = "2026-01-01T17:32:29.314Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f9/e40e318c604259301cc091a2a63f237d9e7b424c4851cafaea4ea7c4834e/pynacl-1.6.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8b097553b380236d51ed11356c953bf8ce36a29a3e596e934ecabe76c985a577", size = 1363101, upload-time = "2026-01-01T17:32:31.263Z" }, + { url = "https://files.pythonhosted.org/packages/48/47/e761c254f410c023a469284a9bc210933e18588ca87706ae93002c05114c/pynacl-1.6.2-cp38-abi3-win32.whl", hash = "sha256:5811c72b473b2f38f7e2a3dc4f8642e3a3e9b5e7317266e4ced1fba85cae41aa", size = 227421, upload-time = "2026-01-01T17:32:33.076Z" }, + { url = "https://files.pythonhosted.org/packages/41/ad/334600e8cacc7d86587fe5f565480fde569dfb487389c8e1be56ac21d8ac/pynacl-1.6.2-cp38-abi3-win_amd64.whl", hash = "sha256:62985f233210dee6548c223301b6c25440852e13d59a8b81490203c3227c5ba0", size = 239754, upload-time = "2026-01-01T17:32:34.557Z" }, + { url = "https://files.pythonhosted.org/packages/29/7d/5945b5af29534641820d3bd7b00962abbbdfee84ec7e19f0d5b3175f9a31/pynacl-1.6.2-cp38-abi3-win_arm64.whl", hash = "sha256:834a43af110f743a754448463e8fd61259cd4ab5bbedcf70f9dabad1d28a394c", size = 184801, upload-time = "2026-01-01T17:32:36.309Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.10'" }, + { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "packaging", marker = "python_full_version < '3.10'" }, + { name = "pluggy", marker = "python_full_version < '3.10'" }, + { name = "pygments", marker = "python_full_version < '3.10'" }, + { name = "tomli", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, + { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "pluggy", marker = "python_full_version >= '3.10'" }, + { name = "pygments", marker = "python_full_version >= '3.10'" }, + { name = "tomli", marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "tomli" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" }, + { url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" }, + { url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" }, + { url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" }, + { url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" }, + { url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" }, + { url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" }, + { url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" }, + { url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" }, + { url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" }, + { url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" }, + { url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" }, + { url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" }, + { url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" }, + { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" }, + { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" }, + { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" }, + { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" }, + { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" }, + { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" }, + { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" }, + { url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" }, + { url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" }, + { url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" }, + { url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" }, + { url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" }, + { url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" }, + { url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" }, + { url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" }, + { url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" }, + { url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" }, + { url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" }, + { url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" }, + { url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" }, + { url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" }, + { url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" }, + { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, +] + +[[package]] +name = "urllib3" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, +] From e647da0bce00cccf353e0c14e40d84cd544e7016 Mon Sep 17 00:00:00 2001 From: scott-yj-yang Date: Fri, 27 Feb 2026 11:46:52 -0800 Subject: [PATCH 02/24] Update Python requirement to >=3.12, drop 3.9-3.11 support pandas 3.0 and NumPy 2.4 both require Python 3.11+. Bumping to 3.12 aligns with current ecosystem and keeps the project on supported versions. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/test.yaml | 2 +- pyproject.toml | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 05ede43..df86382 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.10", "3.11", "3.12"] + python-version: ["3.12", "3.13"] steps: - uses: actions/checkout@v4 - name: Install uv diff --git a/pyproject.toml b/pyproject.toml index 61d0b76..4d6b3d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ version = "0.0.3" description = "Canvas Grouping Python Script" readme = "README.md" license = "Apache-2.0" -requires-python = ">=3.9" +requires-python = ">=3.12" authors = [ { name = "scottyang", email = "yuy004@ucsd.edu" }, ] @@ -17,10 +17,9 @@ classifiers = [ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "Natural Language :: English", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "License :: OSI Approved :: Apache Software License", ] dependencies = [ From a3e79aa147bb9ad294799f0ecb8f77480bd7421d Mon Sep 17 00:00:00 2001 From: scott-yj-yang Date: Fri, 27 Feb 2026 15:40:47 -0800 Subject: [PATCH 03/24] refactor: replace GroupEng with CSV/DataFrame input in AssignGroup Co-Authored-By: Claude Opus 4.6 --- CanvasGroupy/assign.py | 180 ++++++++++++++++++++++++----------------- tests/test_assign.py | 50 ++++++++++++ 2 files changed, 154 insertions(+), 76 deletions(-) create mode 100644 tests/test_assign.py diff --git a/CanvasGroupy/assign.py b/CanvasGroupy/assign.py index c92a7ea..d7e281d 100644 --- a/CanvasGroupy/assign.py +++ b/CanvasGroupy/assign.py @@ -1,87 +1,125 @@ -__all__ = ['AssignGroup'] +__all__ = ["AssignGroup"] import os -import GroupEng -import canvasapi -import github +import pandas as pd from . import GitHubGroup, CanvasGroup + class AssignGroup: - def __init__(self, - ghg: GitHubGroup, # authenticated GitHub object - cg: CanvasGroup, # authenticated canvas object - groupeng_config="", # Directory for the GroupEng config yml file - ): - "Initializer for Assign Group" - self.status = None - self.out_dir = None - self.prefix = None + """Orchestrate group creation across Canvas LMS and GitHub. + + Accepts group assignments as a CSV file path or pandas DataFrame + with columns ``group_name`` and ``student_id``, then creates + corresponding groups on Canvas and/or GitHub. + + Args: + ghg: Authenticated GitHubGroup instance. + cg: Authenticated CanvasGroup instance. + groups: Optional CSV path or DataFrame to load immediately. + """ + + def __init__( + self, + ghg: GitHubGroup, + cg: CanvasGroup, + groups=None, + ): self.cg = cg self.ghg = ghg - # Initialize if appropriate parameters are defined - if groupeng_config != "": - self.assign_groups(groupeng_config) - - def assign_groups(self, - groupeng_config:str, # Directory for the GroupEng config yml file - assign_canvas_group=False, # directly assign canvas groups - create_gh_repo=False, # directly create GitHub repos - username_quiz_id=-1, # username quiz id from canvas course - in_group_category="", # specify which group category the group belongs to - suffix="", # suffix to the group name - ) -> (bool, str): # Status and output directory of the compiled file. - status, out_dir = GroupEng.run(groupeng_config) - self.status, self.out_dir = status, out_dir - file = os.path.split(groupeng_config)[1] - self.prefix = os.path.splitext(file)[0] - if assign_canvas_group: - if self.cg.group_category is None and in_group_category == "": - raise ValueError("Have to specify in_group_category to create canvas group") - self.create_canvas_group(in_group_category, suffix) - if create_gh_repo: - if username_quiz_id == -1: - raise ValueError("Have to specify the canvas username quiz id") - self.create_github_group(username_quiz_id) - return status, out_dir - - def create_canvas_group(self, - in_group_category="", # specify which group category the group belongs to - suffix="", # suffix to the group name - ): - "Create canvas groups based on the generated group configuration" - if self.out_dir is None: - raise ValueError("The group configuration has not been set. Please assign group via assign_groups") - if self.cg.group_category is None: - raise ValueError("The group category has not been set.") + self.groups: dict[str, list[str]] = {} + + if groups is not None: + self.load_groups(groups) + + def load_groups(self, source) -> dict[str, list[str]]: + """Load group assignments from a CSV file path or DataFrame. + + The input must have columns ``group_name`` and ``student_id``. + + Args: + source: A file path (str) to a CSV or a pandas DataFrame. + + Returns: + Dictionary mapping group names to lists of student IDs. + + Raises: + TypeError: If source is not a str or DataFrame. + ValueError: If required columns are missing. + """ + if isinstance(source, str): + source = pd.read_csv(source) + if not isinstance(source, pd.DataFrame): + raise TypeError( + f"Expected a file path (str) or DataFrame, got {type(source).__name__}" + ) + for col in ("group_name", "student_id"): + if col not in source.columns: + raise ValueError( + f"Missing required column: '{col}'. " + f"Got columns: {list(source.columns)}" + ) + self.groups = ( + source.groupby("group_name")["student_id"] + .apply(list) + .to_dict() + ) + return self.groups + + def create_canvas_group( + self, + in_group_category: str = "", + suffix: str = "", + ): + """Create Canvas groups from loaded group assignments. + + Args: + in_group_category: Canvas group category name. Falls back to + the category already set on the CanvasGroup instance. + suffix: Suffix to append to each group name. + + Raises: + ValueError: If no groups are loaded or no group category is set. + """ + if not self.groups: + raise ValueError( + "No groups loaded. Call load_groups() first." + ) + if self.cg.group_category is None and in_group_category == "": + raise ValueError( + "Specify in_group_category or set it on the CanvasGroup instance." + ) if in_group_category == "": in_group_category = self.cg.group_category.name - # load the generated configuration file - groups_generated_fp = os.path.join(self.out_dir, f"{self.prefix}_groups.csv") - with open(groups_generated_fp, "r") as f: - groups = f.read().splitlines() - # create canvas groups for each. - for group in groups: - group = group.replace(" ", "").split(",") - group_name, group_members = group[0], group[1:] + for group_name, members in self.groups.items(): self.cg.assign_canvas_group( group_name=f"{group_name}{suffix}", - group_members=group_members, - in_group_category=in_group_category + group_members=members, + in_group_category=in_group_category, ) - def create_github_group(self, - username_quiz_id:int # username quiz id from canvas course - ): + def create_github_group( + self, + username_quiz_id: int, + ): + """Create GitHub repositories for each group. + + Fetches GitHub usernames from a Canvas quiz, then creates + a repository per group with appropriate collaborators. + + Args: + username_quiz_id: Canvas quiz ID where students submitted + their GitHub usernames. + """ + if not self.groups: + raise ValueError( + "No groups loaded. Call load_groups() first." + ) github_usernames = self.cg.fetch_username_from_quiz(username_quiz_id) - self.cg.set_group_category(cg.group_category.name) - groups = self.cg.group_to_emails repos = [] - for group_name, members in groups.items(): + for group_name, members in self.groups.items(): group_git_usernames = [] for email in members: try: - # try to get the git username for each student. - # not all students completed their quiz. group_git_usernames.append(github_usernames[email]) except KeyError: print(f"{email}'s GitHub Username not found") @@ -89,17 +127,7 @@ def create_github_group(self, repo_name=group_name, collaborators=group_git_usernames, permission="write", - repo_template="COGS118A/group_template", - rename_files={ - "Checkpoint_groupXXX.ipynb": f"Checkpoint_{group_name}.ipynb", - "FinalProject_groupXXX.ipynb": f"FinalProject_{group_name}.ipynb", - "Proposal_groupXXX.ipynb": f"Proposal_{group_name}.ipynb" - }, private=True, - description=f"COGS118A Final Project {group_name} Repository", - team_slug="Instructors_Sp23", - team_permission="admin" ) - print("") repos.append(repo) return repos diff --git a/tests/test_assign.py b/tests/test_assign.py new file mode 100644 index 0000000..1effc98 --- /dev/null +++ b/tests/test_assign.py @@ -0,0 +1,50 @@ +import pytest +import pandas as pd +from unittest.mock import MagicMock +from CanvasGroupy.assign import AssignGroup + + +@pytest.fixture +def mock_services(): + ghg = MagicMock() + cg = MagicMock() + return ghg, cg + + +class TestAssignGroupFromDataFrame: + def test_load_groups_from_dataframe(self, mock_services): + ghg, cg = mock_services + ag = AssignGroup(ghg=ghg, cg=cg) + df = pd.DataFrame({ + "group_name": ["Group1", "Group1", "Group2", "Group2"], + "student_id": ["alice", "bob", "carol", "dave"], + }) + ag.load_groups(df) + assert ag.groups == { + "Group1": ["alice", "bob"], + "Group2": ["carol", "dave"], + } + + def test_load_groups_from_csv(self, mock_services, tmp_path): + ghg, cg = mock_services + ag = AssignGroup(ghg=ghg, cg=cg) + csv_path = tmp_path / "groups.csv" + csv_path.write_text("group_name,student_id\nGroup1,alice\nGroup1,bob\nGroup2,carol\n") + ag.load_groups(str(csv_path)) + assert ag.groups == { + "Group1": ["alice", "bob"], + "Group2": ["carol"], + } + + def test_load_groups_rejects_bad_input(self, mock_services): + ghg, cg = mock_services + ag = AssignGroup(ghg=ghg, cg=cg) + with pytest.raises(TypeError): + ag.load_groups(12345) + + def test_load_groups_requires_columns(self, mock_services): + ghg, cg = mock_services + ag = AssignGroup(ghg=ghg, cg=cg) + df = pd.DataFrame({"wrong_col": ["a"], "also_wrong": ["b"]}) + with pytest.raises(ValueError, match="group_name"): + ag.load_groups(df) From 204a727d3f3e71511851fd05d1927a3c5efc30f2 Mon Sep 17 00:00:00 2001 From: scott-yj-yang Date: Fri, 27 Feb 2026 15:43:17 -0800 Subject: [PATCH 04/24] fix: remove unused import os, add tests for create_canvas_group and create_github_group - Remove unused `import os` from assign.py - Add TestCreateCanvasGroup: verifies assign_canvas_group calls, suffix handling, explicit group category, and empty-groups ValueError guard - Add TestCreateGitHubGroup: verifies create_group_repo calls, missing username handling, and empty-groups ValueError guard - Rename TestAssignGroupFromDataFrame to TestLoadGroups for clarity Co-Authored-By: Claude Opus 4.6 --- CanvasGroupy/assign.py | 1 - tests/test_assign.py | 106 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 104 insertions(+), 3 deletions(-) diff --git a/CanvasGroupy/assign.py b/CanvasGroupy/assign.py index d7e281d..2b7c7fb 100644 --- a/CanvasGroupy/assign.py +++ b/CanvasGroupy/assign.py @@ -1,6 +1,5 @@ __all__ = ["AssignGroup"] -import os import pandas as pd from . import GitHubGroup, CanvasGroup diff --git a/tests/test_assign.py b/tests/test_assign.py index 1effc98..4205c89 100644 --- a/tests/test_assign.py +++ b/tests/test_assign.py @@ -1,6 +1,6 @@ import pytest import pandas as pd -from unittest.mock import MagicMock +from unittest.mock import MagicMock, call from CanvasGroupy.assign import AssignGroup @@ -11,7 +11,20 @@ def mock_services(): return ghg, cg -class TestAssignGroupFromDataFrame: +@pytest.fixture +def loaded_ag(mock_services): + """AssignGroup with two groups already loaded.""" + ghg, cg = mock_services + ag = AssignGroup(ghg=ghg, cg=cg) + df = pd.DataFrame({ + "group_name": ["Group1", "Group1", "Group2"], + "student_id": ["alice", "bob", "carol"], + }) + ag.load_groups(df) + return ag, ghg, cg + + +class TestLoadGroups: def test_load_groups_from_dataframe(self, mock_services): ghg, cg = mock_services ag = AssignGroup(ghg=ghg, cg=cg) @@ -48,3 +61,92 @@ def test_load_groups_requires_columns(self, mock_services): df = pd.DataFrame({"wrong_col": ["a"], "also_wrong": ["b"]}) with pytest.raises(ValueError, match="group_name"): ag.load_groups(df) + + +class TestCreateCanvasGroup: + def test_calls_assign_canvas_group_for_each_group(self, loaded_ag): + ag, ghg, cg = loaded_ag + cg.group_category.name = "Project Groups" + ag.create_canvas_group() + assert cg.assign_canvas_group.call_count == 2 + cg.assign_canvas_group.assert_any_call( + group_name="Group1", + group_members=["alice", "bob"], + in_group_category="Project Groups", + ) + cg.assign_canvas_group.assert_any_call( + group_name="Group2", + group_members=["carol"], + in_group_category="Project Groups", + ) + + def test_applies_suffix_to_group_names(self, loaded_ag): + ag, ghg, cg = loaded_ag + cg.group_category.name = "Project Groups" + ag.create_canvas_group(suffix="_W25") + cg.assign_canvas_group.assert_any_call( + group_name="Group1_W25", + group_members=["alice", "bob"], + in_group_category="Project Groups", + ) + + def test_uses_explicit_group_category(self, loaded_ag): + ag, ghg, cg = loaded_ag + ag.create_canvas_group(in_group_category="Custom Category") + cg.assign_canvas_group.assert_any_call( + group_name="Group1", + group_members=["alice", "bob"], + in_group_category="Custom Category", + ) + + def test_raises_when_no_groups_loaded(self, mock_services): + ghg, cg = mock_services + ag = AssignGroup(ghg=ghg, cg=cg) + with pytest.raises(ValueError, match="No groups loaded"): + ag.create_canvas_group(in_group_category="Anything") + + +class TestCreateGitHubGroup: + def test_calls_create_group_repo_for_each_group(self, loaded_ag): + ag, ghg, cg = loaded_ag + cg.fetch_username_from_quiz.return_value = { + "alice": "alice_gh", + "bob": "bob_gh", + "carol": "carol_gh", + } + repos = ag.create_github_group(username_quiz_id=42) + assert ghg.create_group_repo.call_count == 2 + ghg.create_group_repo.assert_any_call( + repo_name="Group1", + collaborators=["alice_gh", "bob_gh"], + permission="write", + private=True, + ) + ghg.create_group_repo.assert_any_call( + repo_name="Group2", + collaborators=["carol_gh"], + permission="write", + private=True, + ) + assert len(repos) == 2 + + def test_skips_missing_github_usernames(self, loaded_ag): + ag, ghg, cg = loaded_ag + # bob has no GitHub username + cg.fetch_username_from_quiz.return_value = { + "alice": "alice_gh", + "carol": "carol_gh", + } + ag.create_github_group(username_quiz_id=42) + ghg.create_group_repo.assert_any_call( + repo_name="Group1", + collaborators=["alice_gh"], + permission="write", + private=True, + ) + + def test_raises_when_no_groups_loaded(self, mock_services): + ghg, cg = mock_services + ag = AssignGroup(ghg=ghg, cg=cg) + with pytest.raises(ValueError, match="No groups loaded"): + ag.create_github_group(username_quiz_id=42) From 6da602fd68cc6d68846e4e8d7d430daf497236e2 Mon Sep 17 00:00:00 2001 From: scott-yj-yang Date: Fri, 27 Feb 2026 15:44:51 -0800 Subject: [PATCH 05/24] add uv lock --- uv.lock | 506 ++------------------------------------------------------ 1 file changed, 16 insertions(+), 490 deletions(-) diff --git a/uv.lock b/uv.lock index e44f778..845e876 100644 --- a/uv.lock +++ b/uv.lock @@ -1,15 +1,13 @@ version = 1 revision = 3 -requires-python = ">=3.9" +requires-python = ">=3.12" resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", "python_full_version >= '3.14' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", - "python_full_version == '3.10.*'", - "python_full_version < '3.10'", + "python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] [[package]] @@ -46,18 +44,14 @@ source = { editable = "." } dependencies = [ { name = "canvasapi" }, { name = "groupeng" }, - { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "pandas", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy" }, + { name = "pandas" }, { name = "pygithub" }, ] [package.optional-dependencies] dev = [ - { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytest" }, ] [package.metadata] @@ -85,36 +79,10 @@ name = "cffi" version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pycparser", version = "2.23", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' and implementation_name != 'PyPy'" }, - { name = "pycparser", version = "3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and implementation_name != 'PyPy'" }, + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, - { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, - { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, - { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, - { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, - { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, - { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, - { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, - { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, - { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, - { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, - { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, - { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, - { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, - { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, - { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, - { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, - { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, - { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, - { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, - { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, - { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, - { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, @@ -161,18 +129,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, - { url = "https://files.pythonhosted.org/packages/c0/cc/08ed5a43f2996a16b462f64a7055c6e962803534924b9b2f1371d8c00b7b/cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf", size = 184288, upload-time = "2025-09-08T23:23:48.404Z" }, - { url = "https://files.pythonhosted.org/packages/3d/de/38d9726324e127f727b4ecc376bc85e505bfe61ef130eaf3f290c6847dd4/cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7", size = 180509, upload-time = "2025-09-08T23:23:49.73Z" }, - { url = "https://files.pythonhosted.org/packages/9b/13/c92e36358fbcc39cf0962e83223c9522154ee8630e1df7c0b3a39a8124e2/cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c", size = 208813, upload-time = "2025-09-08T23:23:51.263Z" }, - { url = "https://files.pythonhosted.org/packages/15/12/a7a79bd0df4c3bff744b2d7e52cc1b68d5e7e427b384252c42366dc1ecbc/cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165", size = 216498, upload-time = "2025-09-08T23:23:52.494Z" }, - { url = "https://files.pythonhosted.org/packages/a3/ad/5c51c1c7600bdd7ed9a24a203ec255dccdd0ebf4527f7b922a0bde2fb6ed/cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534", size = 203243, upload-time = "2025-09-08T23:23:53.836Z" }, - { url = "https://files.pythonhosted.org/packages/32/f2/81b63e288295928739d715d00952c8c6034cb6c6a516b17d37e0c8be5600/cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f", size = 203158, upload-time = "2025-09-08T23:23:55.169Z" }, - { url = "https://files.pythonhosted.org/packages/1f/74/cc4096ce66f5939042ae094e2e96f53426a979864aa1f96a621ad128be27/cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63", size = 216548, upload-time = "2025-09-08T23:23:56.506Z" }, - { url = "https://files.pythonhosted.org/packages/e8/be/f6424d1dc46b1091ffcc8964fa7c0ab0cd36839dd2761b49c90481a6ba1b/cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2", size = 218897, upload-time = "2025-09-08T23:23:57.825Z" }, - { url = "https://files.pythonhosted.org/packages/f7/e0/dda537c2309817edf60109e39265f24f24aa7f050767e22c98c53fe7f48b/cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65", size = 211249, upload-time = "2025-09-08T23:23:59.139Z" }, - { url = "https://files.pythonhosted.org/packages/2b/e7/7c769804eb75e4c4b35e658dba01de1640a351a9653c3d49ca89d16ccc91/cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322", size = 218041, upload-time = "2025-09-08T23:24:00.496Z" }, - { url = "https://files.pythonhosted.org/packages/aa/d9/6218d78f920dcd7507fc16a766b5ef8f3b913cc7aa938e7fc80b9978d089/cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a", size = 172138, upload-time = "2025-09-08T23:24:01.7Z" }, - { url = "https://files.pythonhosted.org/packages/54/8f/a1e836f82d8e32a97e6b29cc8f641779181ac7363734f12df27db803ebda/cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9", size = 182794, upload-time = "2025-09-08T23:24:02.943Z" }, ] [[package]] @@ -181,38 +137,6 @@ version = "3.4.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709, upload-time = "2025-10-14T04:40:11.385Z" }, - { url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814, upload-time = "2025-10-14T04:40:13.135Z" }, - { url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467, upload-time = "2025-10-14T04:40:14.728Z" }, - { url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280, upload-time = "2025-10-14T04:40:16.14Z" }, - { url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454, upload-time = "2025-10-14T04:40:17.567Z" }, - { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609, upload-time = "2025-10-14T04:40:19.08Z" }, - { url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849, upload-time = "2025-10-14T04:40:20.607Z" }, - { url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586, upload-time = "2025-10-14T04:40:21.719Z" }, - { url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290, upload-time = "2025-10-14T04:40:23.069Z" }, - { url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663, upload-time = "2025-10-14T04:40:24.17Z" }, - { url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964, upload-time = "2025-10-14T04:40:25.368Z" }, - { url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064, upload-time = "2025-10-14T04:40:26.806Z" }, - { url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015, upload-time = "2025-10-14T04:40:28.284Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792, upload-time = "2025-10-14T04:40:29.613Z" }, - { url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198, upload-time = "2025-10-14T04:40:30.644Z" }, - { url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262, upload-time = "2025-10-14T04:40:32.108Z" }, - { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, - { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, - { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, - { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, - { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, - { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, - { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, - { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, - { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, - { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, - { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, - { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, - { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, - { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, - { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, - { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, @@ -261,22 +185,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, - { url = "https://files.pythonhosted.org/packages/46/7c/0c4760bccf082737ca7ab84a4c2034fcc06b1f21cf3032ea98bd6feb1725/charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9", size = 209609, upload-time = "2025-10-14T04:42:10.922Z" }, - { url = "https://files.pythonhosted.org/packages/bb/a4/69719daef2f3d7f1819de60c9a6be981b8eeead7542d5ec4440f3c80e111/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d", size = 149029, upload-time = "2025-10-14T04:42:12.38Z" }, - { url = "https://files.pythonhosted.org/packages/e6/21/8d4e1d6c1e6070d3672908b8e4533a71b5b53e71d16828cc24d0efec564c/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608", size = 144580, upload-time = "2025-10-14T04:42:13.549Z" }, - { url = "https://files.pythonhosted.org/packages/a7/0a/a616d001b3f25647a9068e0b9199f697ce507ec898cacb06a0d5a1617c99/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc", size = 162340, upload-time = "2025-10-14T04:42:14.892Z" }, - { url = "https://files.pythonhosted.org/packages/85/93/060b52deb249a5450460e0585c88a904a83aec474ab8e7aba787f45e79f2/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e", size = 159619, upload-time = "2025-10-14T04:42:16.676Z" }, - { url = "https://files.pythonhosted.org/packages/dd/21/0274deb1cc0632cd587a9a0ec6b4674d9108e461cb4cd40d457adaeb0564/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1", size = 153980, upload-time = "2025-10-14T04:42:17.917Z" }, - { url = "https://files.pythonhosted.org/packages/28/2b/e3d7d982858dccc11b31906976323d790dded2017a0572f093ff982d692f/charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3", size = 152174, upload-time = "2025-10-14T04:42:19.018Z" }, - { url = "https://files.pythonhosted.org/packages/6e/ff/4a269f8e35f1e58b2df52c131a1fa019acb7ef3f8697b7d464b07e9b492d/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6", size = 151666, upload-time = "2025-10-14T04:42:20.171Z" }, - { url = "https://files.pythonhosted.org/packages/da/c9/ec39870f0b330d58486001dd8e532c6b9a905f5765f58a6f8204926b4a93/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88", size = 145550, upload-time = "2025-10-14T04:42:21.324Z" }, - { url = "https://files.pythonhosted.org/packages/75/8f/d186ab99e40e0ed9f82f033d6e49001701c81244d01905dd4a6924191a30/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1", size = 163721, upload-time = "2025-10-14T04:42:22.46Z" }, - { url = "https://files.pythonhosted.org/packages/96/b1/6047663b9744df26a7e479ac1e77af7134b1fcf9026243bb48ee2d18810f/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf", size = 152127, upload-time = "2025-10-14T04:42:23.712Z" }, - { url = "https://files.pythonhosted.org/packages/59/78/e5a6eac9179f24f704d1be67d08704c3c6ab9f00963963524be27c18ed87/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318", size = 161175, upload-time = "2025-10-14T04:42:24.87Z" }, - { url = "https://files.pythonhosted.org/packages/e5/43/0e626e42d54dd2f8dd6fc5e1c5ff00f05fbca17cb699bedead2cae69c62f/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c", size = 155375, upload-time = "2025-10-14T04:42:27.246Z" }, - { url = "https://files.pythonhosted.org/packages/e9/91/d9615bf2e06f35e4997616ff31248c3657ed649c5ab9d35ea12fce54e380/charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505", size = 99692, upload-time = "2025-10-14T04:42:28.425Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a9/6c040053909d9d1ef4fcab45fddec083aedc9052c10078339b47c8573ea8/charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966", size = 107192, upload-time = "2025-10-14T04:42:29.482Z" }, - { url = "https://files.pythonhosted.org/packages/f0/c6/4fa536b2c0cd3edfb7ccf8469fa0f363ea67b7213a842b90909ca33dd851/charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50", size = 100220, upload-time = "2025-10-14T04:42:30.632Z" }, { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, ] @@ -295,7 +203,6 @@ version = "46.0.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/60/04/ee2a9e8542e4fa2773b81771ff8349ff19cdd56b7258a0cc442639052edb/cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d", size = 750064, upload-time = "2026-02-10T19:18:38.255Z" } wheels = [ @@ -341,24 +248,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/96/93/682d2b43c1d5f1406ed048f377c0fc9fc8f7b0447a478d5c65ab3d3a66eb/cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9", size = 4667123, upload-time = "2026-02-10T19:18:15.886Z" }, { url = "https://files.pythonhosted.org/packages/45/2d/9c5f2926cb5300a8eefc3f4f0b3f3df39db7f7ce40c8365444c49363cbda/cryptography-46.0.5-cp38-abi3-win32.whl", hash = "sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72", size = 3010220, upload-time = "2026-02-10T19:18:17.361Z" }, { url = "https://files.pythonhosted.org/packages/48/ef/0c2f4a8e31018a986949d34a01115dd057bf536905dca38897bacd21fac3/cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595", size = 3467050, upload-time = "2026-02-10T19:18:18.899Z" }, - { url = "https://files.pythonhosted.org/packages/eb/dd/2d9fdb07cebdf3d51179730afb7d5e576153c6744c3ff8fded23030c204e/cryptography-46.0.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:3b4995dc971c9fb83c25aa44cf45f02ba86f71ee600d81091c2f0cbae116b06c", size = 3476964, upload-time = "2026-02-10T19:18:20.687Z" }, - { url = "https://files.pythonhosted.org/packages/e9/6f/6cc6cc9955caa6eaf83660b0da2b077c7fe8ff9950a3c5e45d605038d439/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bc84e875994c3b445871ea7181d424588171efec3e185dced958dad9e001950a", size = 4218321, upload-time = "2026-02-10T19:18:22.349Z" }, - { url = "https://files.pythonhosted.org/packages/3e/5d/c4da701939eeee699566a6c1367427ab91a8b7088cc2328c09dbee940415/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2ae6971afd6246710480e3f15824ed3029a60fc16991db250034efd0b9fb4356", size = 4381786, upload-time = "2026-02-10T19:18:24.529Z" }, - { url = "https://files.pythonhosted.org/packages/ac/97/a538654732974a94ff96c1db621fa464f455c02d4bb7d2652f4edc21d600/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d861ee9e76ace6cf36a6a89b959ec08e7bc2493ee39d07ffe5acb23ef46d27da", size = 4217990, upload-time = "2026-02-10T19:18:25.957Z" }, - { url = "https://files.pythonhosted.org/packages/ae/11/7e500d2dd3ba891197b9efd2da5454b74336d64a7cc419aa7327ab74e5f6/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:2b7a67c9cd56372f3249b39699f2ad479f6991e62ea15800973b956f4b73e257", size = 4381252, upload-time = "2026-02-10T19:18:27.496Z" }, - { url = "https://files.pythonhosted.org/packages/bc/58/6b3d24e6b9bc474a2dcdee65dfd1f008867015408a271562e4b690561a4d/cryptography-46.0.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8456928655f856c6e1533ff59d5be76578a7157224dbd9ce6872f25055ab9ab7", size = 3407605, upload-time = "2026-02-10T19:18:29.233Z" }, -] - -[[package]] -name = "exceptiongroup" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, ] [[package]] @@ -375,181 +264,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] -[[package]] -name = "iniconfig" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, -] - [[package]] name = "iniconfig" version = "2.3.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", - "python_full_version == '3.10.*'", -] sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] -[[package]] -name = "numpy" -version = "2.0.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/a9/75/10dd1f8116a8b796cb2c737b674e02d02e80454bda953fa7e65d8c12b016/numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78", size = 18902015, upload-time = "2024-08-26T20:19:40.945Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/21/91/3495b3237510f79f5d81f2508f9f13fea78ebfdf07538fc7444badda173d/numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece", size = 21165245, upload-time = "2024-08-26T20:04:14.625Z" }, - { url = "https://files.pythonhosted.org/packages/05/33/26178c7d437a87082d11019292dce6d3fe6f0e9026b7b2309cbf3e489b1d/numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04", size = 13738540, upload-time = "2024-08-26T20:04:36.784Z" }, - { url = "https://files.pythonhosted.org/packages/ec/31/cc46e13bf07644efc7a4bf68df2df5fb2a1a88d0cd0da9ddc84dc0033e51/numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66", size = 5300623, upload-time = "2024-08-26T20:04:46.491Z" }, - { url = "https://files.pythonhosted.org/packages/6e/16/7bfcebf27bb4f9d7ec67332ffebee4d1bf085c84246552d52dbb548600e7/numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b", size = 6901774, upload-time = "2024-08-26T20:04:58.173Z" }, - { url = "https://files.pythonhosted.org/packages/f9/a3/561c531c0e8bf082c5bef509d00d56f82e0ea7e1e3e3a7fc8fa78742a6e5/numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd", size = 13907081, upload-time = "2024-08-26T20:05:19.098Z" }, - { url = "https://files.pythonhosted.org/packages/fa/66/f7177ab331876200ac7563a580140643d1179c8b4b6a6b0fc9838de2a9b8/numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318", size = 19523451, upload-time = "2024-08-26T20:05:47.479Z" }, - { url = "https://files.pythonhosted.org/packages/25/7f/0b209498009ad6453e4efc2c65bcdf0ae08a182b2b7877d7ab38a92dc542/numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8", size = 19927572, upload-time = "2024-08-26T20:06:17.137Z" }, - { url = "https://files.pythonhosted.org/packages/3e/df/2619393b1e1b565cd2d4c4403bdd979621e2c4dea1f8532754b2598ed63b/numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326", size = 14400722, upload-time = "2024-08-26T20:06:39.16Z" }, - { url = "https://files.pythonhosted.org/packages/22/ad/77e921b9f256d5da36424ffb711ae79ca3f451ff8489eeca544d0701d74a/numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97", size = 6472170, upload-time = "2024-08-26T20:06:50.361Z" }, - { url = "https://files.pythonhosted.org/packages/10/05/3442317535028bc29cf0c0dd4c191a4481e8376e9f0db6bcf29703cadae6/numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131", size = 15905558, upload-time = "2024-08-26T20:07:13.881Z" }, - { url = "https://files.pythonhosted.org/packages/8b/cf/034500fb83041aa0286e0fb16e7c76e5c8b67c0711bb6e9e9737a717d5fe/numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448", size = 21169137, upload-time = "2024-08-26T20:07:45.345Z" }, - { url = "https://files.pythonhosted.org/packages/4a/d9/32de45561811a4b87fbdee23b5797394e3d1504b4a7cf40c10199848893e/numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195", size = 13703552, upload-time = "2024-08-26T20:08:06.666Z" }, - { url = "https://files.pythonhosted.org/packages/c1/ca/2f384720020c7b244d22508cb7ab23d95f179fcfff33c31a6eeba8d6c512/numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57", size = 5298957, upload-time = "2024-08-26T20:08:15.83Z" }, - { url = "https://files.pythonhosted.org/packages/0e/78/a3e4f9fb6aa4e6fdca0c5428e8ba039408514388cf62d89651aade838269/numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a", size = 6905573, upload-time = "2024-08-26T20:08:27.185Z" }, - { url = "https://files.pythonhosted.org/packages/a0/72/cfc3a1beb2caf4efc9d0b38a15fe34025230da27e1c08cc2eb9bfb1c7231/numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669", size = 13914330, upload-time = "2024-08-26T20:08:48.058Z" }, - { url = "https://files.pythonhosted.org/packages/ba/a8/c17acf65a931ce551fee11b72e8de63bf7e8a6f0e21add4c937c83563538/numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951", size = 19534895, upload-time = "2024-08-26T20:09:16.536Z" }, - { url = "https://files.pythonhosted.org/packages/ba/86/8767f3d54f6ae0165749f84648da9dcc8cd78ab65d415494962c86fac80f/numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9", size = 19937253, upload-time = "2024-08-26T20:09:46.263Z" }, - { url = "https://files.pythonhosted.org/packages/df/87/f76450e6e1c14e5bb1eae6836478b1028e096fd02e85c1c37674606ab752/numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15", size = 14414074, upload-time = "2024-08-26T20:10:08.483Z" }, - { url = "https://files.pythonhosted.org/packages/5c/ca/0f0f328e1e59f73754f06e1adfb909de43726d4f24c6a3f8805f34f2b0fa/numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4", size = 6470640, upload-time = "2024-08-26T20:10:19.732Z" }, - { url = "https://files.pythonhosted.org/packages/eb/57/3a3f14d3a759dcf9bf6e9eda905794726b758819df4663f217d658a58695/numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc", size = 15910230, upload-time = "2024-08-26T20:10:43.413Z" }, - { url = "https://files.pythonhosted.org/packages/45/40/2e117be60ec50d98fa08c2f8c48e09b3edea93cfcabd5a9ff6925d54b1c2/numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b", size = 20895803, upload-time = "2024-08-26T20:11:13.916Z" }, - { url = "https://files.pythonhosted.org/packages/46/92/1b8b8dee833f53cef3e0a3f69b2374467789e0bb7399689582314df02651/numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e", size = 13471835, upload-time = "2024-08-26T20:11:34.779Z" }, - { url = "https://files.pythonhosted.org/packages/7f/19/e2793bde475f1edaea6945be141aef6c8b4c669b90c90a300a8954d08f0a/numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c", size = 5038499, upload-time = "2024-08-26T20:11:43.902Z" }, - { url = "https://files.pythonhosted.org/packages/e3/ff/ddf6dac2ff0dd50a7327bcdba45cb0264d0e96bb44d33324853f781a8f3c/numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c", size = 6633497, upload-time = "2024-08-26T20:11:55.09Z" }, - { url = "https://files.pythonhosted.org/packages/72/21/67f36eac8e2d2cd652a2e69595a54128297cdcb1ff3931cfc87838874bd4/numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692", size = 13621158, upload-time = "2024-08-26T20:12:14.95Z" }, - { url = "https://files.pythonhosted.org/packages/39/68/e9f1126d757653496dbc096cb429014347a36b228f5a991dae2c6b6cfd40/numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a", size = 19236173, upload-time = "2024-08-26T20:12:44.049Z" }, - { url = "https://files.pythonhosted.org/packages/d1/e9/1f5333281e4ebf483ba1c888b1d61ba7e78d7e910fdd8e6499667041cc35/numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c", size = 19634174, upload-time = "2024-08-26T20:13:13.634Z" }, - { url = "https://files.pythonhosted.org/packages/71/af/a469674070c8d8408384e3012e064299f7a2de540738a8e414dcfd639996/numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded", size = 14099701, upload-time = "2024-08-26T20:13:34.851Z" }, - { url = "https://files.pythonhosted.org/packages/d0/3d/08ea9f239d0e0e939b6ca52ad403c84a2bce1bde301a8eb4888c1c1543f1/numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5", size = 6174313, upload-time = "2024-08-26T20:13:45.653Z" }, - { url = "https://files.pythonhosted.org/packages/b2/b5/4ac39baebf1fdb2e72585c8352c56d063b6126be9fc95bd2bb5ef5770c20/numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a", size = 15606179, upload-time = "2024-08-26T20:14:08.786Z" }, - { url = "https://files.pythonhosted.org/packages/43/c1/41c8f6df3162b0c6ffd4437d729115704bd43363de0090c7f913cfbc2d89/numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c", size = 21169942, upload-time = "2024-08-26T20:14:40.108Z" }, - { url = "https://files.pythonhosted.org/packages/39/bc/fd298f308dcd232b56a4031fd6ddf11c43f9917fbc937e53762f7b5a3bb1/numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd", size = 13711512, upload-time = "2024-08-26T20:15:00.985Z" }, - { url = "https://files.pythonhosted.org/packages/96/ff/06d1aa3eeb1c614eda245c1ba4fb88c483bee6520d361641331872ac4b82/numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b", size = 5306976, upload-time = "2024-08-26T20:15:10.876Z" }, - { url = "https://files.pythonhosted.org/packages/2d/98/121996dcfb10a6087a05e54453e28e58694a7db62c5a5a29cee14c6e047b/numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729", size = 6906494, upload-time = "2024-08-26T20:15:22.055Z" }, - { url = "https://files.pythonhosted.org/packages/15/31/9dffc70da6b9bbf7968f6551967fc21156207366272c2a40b4ed6008dc9b/numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1", size = 13912596, upload-time = "2024-08-26T20:15:42.452Z" }, - { url = "https://files.pythonhosted.org/packages/b9/14/78635daab4b07c0930c919d451b8bf8c164774e6a3413aed04a6d95758ce/numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd", size = 19526099, upload-time = "2024-08-26T20:16:11.048Z" }, - { url = "https://files.pythonhosted.org/packages/26/4c/0eeca4614003077f68bfe7aac8b7496f04221865b3a5e7cb230c9d055afd/numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d", size = 19932823, upload-time = "2024-08-26T20:16:40.171Z" }, - { url = "https://files.pythonhosted.org/packages/f1/46/ea25b98b13dccaebddf1a803f8c748680d972e00507cd9bc6dcdb5aa2ac1/numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d", size = 14404424, upload-time = "2024-08-26T20:17:02.604Z" }, - { url = "https://files.pythonhosted.org/packages/c8/a6/177dd88d95ecf07e722d21008b1b40e681a929eb9e329684d449c36586b2/numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa", size = 6476809, upload-time = "2024-08-26T20:17:13.553Z" }, - { url = "https://files.pythonhosted.org/packages/ea/2b/7fc9f4e7ae5b507c1a3a21f0f15ed03e794c1242ea8a242ac158beb56034/numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73", size = 15911314, upload-time = "2024-08-26T20:17:36.72Z" }, - { url = "https://files.pythonhosted.org/packages/8f/3b/df5a870ac6a3be3a86856ce195ef42eec7ae50d2a202be1f5a4b3b340e14/numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8", size = 21025288, upload-time = "2024-08-26T20:18:07.732Z" }, - { url = "https://files.pythonhosted.org/packages/2c/97/51af92f18d6f6f2d9ad8b482a99fb74e142d71372da5d834b3a2747a446e/numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4", size = 6762793, upload-time = "2024-08-26T20:18:19.125Z" }, - { url = "https://files.pythonhosted.org/packages/12/46/de1fbd0c1b5ccaa7f9a005b66761533e2f6a3e560096682683a223631fe9/numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c", size = 19334885, upload-time = "2024-08-26T20:18:47.237Z" }, - { url = "https://files.pythonhosted.org/packages/cc/dc/d330a6faefd92b446ec0f0dfea4c3207bb1fef3c4771d19cf4543efd2c78/numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385", size = 15828784, upload-time = "2024-08-26T20:19:11.19Z" }, -] - -[[package]] -name = "numpy" -version = "2.2.6" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version == '3.10.*'", -] -sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, - { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, - { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, - { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, - { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, - { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, - { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, - { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, - { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, - { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, - { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, - { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, - { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, - { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, - { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, - { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, - { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, - { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, - { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, - { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, - { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, - { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, - { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, - { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, - { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, - { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, - { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, - { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, - { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, - { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, - { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, - { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, - { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, - { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, - { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, - { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, - { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, - { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, - { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, - { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, - { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, - { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, - { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, - { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, - { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, - { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, - { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, - { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, - { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, - { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, - { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, - { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, - { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, -] - [[package]] name = "numpy" version = "2.4.2" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", -] sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/44/71852273146957899753e69986246d6a176061ea183407e95418c2aa4d9a/numpy-2.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7e88598032542bd49af7c4747541422884219056c268823ef6e5e89851c8825", size = 16955478, upload-time = "2026-01-31T23:10:25.623Z" }, - { url = "https://files.pythonhosted.org/packages/74/41/5d17d4058bd0cd96bcbd4d9ff0fb2e21f52702aab9a72e4a594efa18692f/numpy-2.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7edc794af8b36ca37ef5fcb5e0d128c7e0595c7b96a2318d1badb6fcd8ee86b1", size = 14965467, upload-time = "2026-01-31T23:10:28.186Z" }, - { url = "https://files.pythonhosted.org/packages/49/48/fb1ce8136c19452ed15f033f8aee91d5defe515094e330ce368a0647846f/numpy-2.4.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6e9f61981ace1360e42737e2bae58b27bf28a1b27e781721047d84bd754d32e7", size = 5475172, upload-time = "2026-01-31T23:10:30.848Z" }, - { url = "https://files.pythonhosted.org/packages/40/a9/3feb49f17bbd1300dd2570432961f5c8a4ffeff1db6f02c7273bd020a4c9/numpy-2.4.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cb7bbb88aa74908950d979eeaa24dbdf1a865e3c7e45ff0121d8f70387b55f73", size = 6805145, upload-time = "2026-01-31T23:10:32.352Z" }, - { url = "https://files.pythonhosted.org/packages/3f/39/fdf35cbd6d6e2fcad42fcf85ac04a85a0d0fbfbf34b30721c98d602fd70a/numpy-2.4.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f069069931240b3fc703f1e23df63443dbd6390614c8c44a87d96cd0ec81eb1", size = 15966084, upload-time = "2026-01-31T23:10:34.502Z" }, - { url = "https://files.pythonhosted.org/packages/1b/46/6fa4ea94f1ddf969b2ee941290cca6f1bfac92b53c76ae5f44afe17ceb69/numpy-2.4.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c02ef4401a506fb60b411467ad501e1429a3487abca4664871d9ae0b46c8ba32", size = 16899477, upload-time = "2026-01-31T23:10:37.075Z" }, - { url = "https://files.pythonhosted.org/packages/09/a1/2a424e162b1a14a5bd860a464ab4e07513916a64ab1683fae262f735ccd2/numpy-2.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2653de5c24910e49c2b106499803124dde62a5a1fe0eedeaecf4309a5f639390", size = 17323429, upload-time = "2026-01-31T23:10:39.704Z" }, - { url = "https://files.pythonhosted.org/packages/ce/a2/73014149ff250628df72c58204822ac01d768697913881aacf839ff78680/numpy-2.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1ae241bbfc6ae276f94a170b14785e561cb5e7f626b6688cf076af4110887413", size = 18635109, upload-time = "2026-01-31T23:10:41.924Z" }, - { url = "https://files.pythonhosted.org/packages/6c/0c/73e8be2f1accd56df74abc1c5e18527822067dced5ec0861b5bb882c2ce0/numpy-2.4.2-cp311-cp311-win32.whl", hash = "sha256:df1b10187212b198dd45fa943d8985a3c8cf854aed4923796e0e019e113a1bda", size = 6237915, upload-time = "2026-01-31T23:10:45.26Z" }, - { url = "https://files.pythonhosted.org/packages/76/ae/e0265e0163cf127c24c3969d29f1c4c64551a1e375d95a13d32eab25d364/numpy-2.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:b9c618d56a29c9cb1c4da979e9899be7578d2e0b3c24d52079c166324c9e8695", size = 12607972, upload-time = "2026-01-31T23:10:47.021Z" }, - { url = "https://files.pythonhosted.org/packages/29/a5/c43029af9b8014d6ea157f192652c50042e8911f4300f8f6ed3336bf437f/numpy-2.4.2-cp311-cp311-win_arm64.whl", hash = "sha256:47c5a6ed21d9452b10227e5e8a0e1c22979811cad7dcc19d8e3e2fb8fa03f1a3", size = 10485763, upload-time = "2026-01-31T23:10:50.087Z" }, { url = "https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a", size = 16667963, upload-time = "2026-01-31T23:10:52.147Z" }, { url = "https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1", size = 14693571, upload-time = "2026-01-31T23:10:54.789Z" }, { url = "https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e", size = 5203469, upload-time = "2026-01-31T23:10:57.343Z" }, @@ -603,13 +332,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a5/55/6e1a61ded7af8df04016d81b5b02daa59f2ea9252ee0397cb9f631efe9e5/numpy-2.4.2-cp314-cp314t-win32.whl", hash = "sha256:8c50dd1fc8826f5b26a5ee4d77ca55d88a895f4e4819c7ecc2a9f5905047a443", size = 6153937, upload-time = "2026-01-31T23:12:47.229Z" }, { url = "https://files.pythonhosted.org/packages/45/aa/fa6118d1ed6d776b0983f3ceac9b1a5558e80df9365b1c3aa6d42bf9eee4/numpy-2.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fcf92bee92742edd401ba41135185866f7026c502617f422eb432cfeca4fe236", size = 12631844, upload-time = "2026-01-31T23:12:48.997Z" }, { url = "https://files.pythonhosted.org/packages/32/0a/2ec5deea6dcd158f254a7b372fb09cfba5719419c8d66343bab35237b3fb/numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181", size = 10565379, upload-time = "2026-01-31T23:12:51.345Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f8/50e14d36d915ef64d8f8bc4a087fc8264d82c785eda6711f80ab7e620335/numpy-2.4.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:89f7268c009bc492f506abd6f5265defa7cb3f7487dc21d357c3d290add45082", size = 16833179, upload-time = "2026-01-31T23:12:53.5Z" }, - { url = "https://files.pythonhosted.org/packages/17/17/809b5cad63812058a8189e91a1e2d55a5a18fd04611dbad244e8aeae465c/numpy-2.4.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6dee3bb76aa4009d5a912180bf5b2de012532998d094acee25d9cb8dee3e44a", size = 14889755, upload-time = "2026-01-31T23:12:55.933Z" }, - { url = "https://files.pythonhosted.org/packages/3e/ea/181b9bcf7627fc8371720316c24db888dcb9829b1c0270abf3d288b2e29b/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:cd2bd2bbed13e213d6b55dc1d035a4f91748a7d3edc9480c13898b0353708920", size = 5399500, upload-time = "2026-01-31T23:12:58.671Z" }, - { url = "https://files.pythonhosted.org/packages/33/9f/413adf3fc955541ff5536b78fcf0754680b3c6d95103230252a2c9408d23/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:cf28c0c1d4c4bf00f509fa7eb02c58d7caf221b50b467bcb0d9bbf1584d5c821", size = 6714252, upload-time = "2026-01-31T23:13:00.518Z" }, - { url = "https://files.pythonhosted.org/packages/91/da/643aad274e29ccbdf42ecd94dafe524b81c87bcb56b83872d54827f10543/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e04ae107ac591763a47398bb45b568fc38f02dbc4aa44c063f67a131f99346cb", size = 15797142, upload-time = "2026-01-31T23:13:02.219Z" }, - { url = "https://files.pythonhosted.org/packages/66/27/965b8525e9cb5dc16481b30a1b3c21e50c7ebf6e9dbd48d0c4d0d5089c7e/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:602f65afdef699cda27ec0b9224ae5dc43e328f4c24c689deaf77133dbee74d0", size = 16727979, upload-time = "2026-01-31T23:13:04.62Z" }, - { url = "https://files.pythonhosted.org/packages/de/e5/b7d20451657664b07986c2f6e3be564433f5dcaf3482d68eaecd79afaf03/numpy-2.4.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be71bf1edb48ebbbf7f6337b5bfd2f895d1902f6335a5830b20141fc126ffba0", size = 12502577, upload-time = "2026-01-31T23:13:07.08Z" }, ] [[package]] @@ -621,106 +343,17 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] -[[package]] -name = "pandas" -version = "2.3.3" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version == '3.10.*'", - "python_full_version < '3.10'", -] -dependencies = [ - { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "python-dateutil", marker = "python_full_version < '3.11'" }, - { name = "pytz", marker = "python_full_version < '3.11'" }, - { name = "tzdata", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/f7/f425a00df4fcc22b292c6895c6831c0c8ae1d9fac1e024d16f98a9ce8749/pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c", size = 11555763, upload-time = "2025-09-29T23:16:53.287Z" }, - { url = "https://files.pythonhosted.org/packages/13/4f/66d99628ff8ce7857aca52fed8f0066ce209f96be2fede6cef9f84e8d04f/pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a", size = 10801217, upload-time = "2025-09-29T23:17:04.522Z" }, - { url = "https://files.pythonhosted.org/packages/1d/03/3fc4a529a7710f890a239cc496fc6d50ad4a0995657dccc1d64695adb9f4/pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1", size = 12148791, upload-time = "2025-09-29T23:17:18.444Z" }, - { url = "https://files.pythonhosted.org/packages/40/a8/4dac1f8f8235e5d25b9955d02ff6f29396191d4e665d71122c3722ca83c5/pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838", size = 12769373, upload-time = "2025-09-29T23:17:35.846Z" }, - { url = "https://files.pythonhosted.org/packages/df/91/82cc5169b6b25440a7fc0ef3a694582418d875c8e3ebf796a6d6470aa578/pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250", size = 13200444, upload-time = "2025-09-29T23:17:49.341Z" }, - { url = "https://files.pythonhosted.org/packages/10/ae/89b3283800ab58f7af2952704078555fa60c807fff764395bb57ea0b0dbd/pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4", size = 13858459, upload-time = "2025-09-29T23:18:03.722Z" }, - { url = "https://files.pythonhosted.org/packages/85/72/530900610650f54a35a19476eca5104f38555afccda1aa11a92ee14cb21d/pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826", size = 11346086, upload-time = "2025-09-29T23:18:18.505Z" }, - { url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790, upload-time = "2025-09-29T23:18:30.065Z" }, - { url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831, upload-time = "2025-09-29T23:38:56.071Z" }, - { url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267, upload-time = "2025-09-29T23:18:41.627Z" }, - { url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281, upload-time = "2025-09-29T23:18:56.834Z" }, - { url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453, upload-time = "2025-09-29T23:19:09.247Z" }, - { url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361, upload-time = "2025-09-29T23:19:25.342Z" }, - { url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702, upload-time = "2025-09-29T23:19:38.296Z" }, - { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, - { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, - { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, - { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, - { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, - { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, - { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, - { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, - { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, - { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, - { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, - { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, - { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, - { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, - { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, - { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, - { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, - { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, - { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, - { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, - { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" }, - { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" }, - { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" }, - { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" }, - { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" }, - { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" }, - { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" }, - { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" }, - { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" }, - { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" }, - { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" }, - { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" }, - { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, - { url = "https://files.pythonhosted.org/packages/56/b4/52eeb530a99e2a4c55ffcd352772b599ed4473a0f892d127f4147cf0f88e/pandas-2.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c503ba5216814e295f40711470446bc3fd00f0faea8a086cbc688808e26f92a2", size = 11567720, upload-time = "2025-09-29T23:33:06.209Z" }, - { url = "https://files.pythonhosted.org/packages/48/4a/2d8b67632a021bced649ba940455ed441ca854e57d6e7658a6024587b083/pandas-2.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a637c5cdfa04b6d6e2ecedcb81fc52ffb0fd78ce2ebccc9ea964df9f658de8c8", size = 10810302, upload-time = "2025-09-29T23:33:35.846Z" }, - { url = "https://files.pythonhosted.org/packages/13/e6/d2465010ee0569a245c975dc6967b801887068bc893e908239b1f4b6c1ac/pandas-2.3.3-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:854d00d556406bffe66a4c0802f334c9ad5a96b4f1f868adf036a21b11ef13ff", size = 12154874, upload-time = "2025-09-29T23:33:49.939Z" }, - { url = "https://files.pythonhosted.org/packages/1f/18/aae8c0aa69a386a3255940e9317f793808ea79d0a525a97a903366bb2569/pandas-2.3.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf1f8a81d04ca90e32a0aceb819d34dbd378a98bf923b6398b9a3ec0bf44de29", size = 12790141, upload-time = "2025-09-29T23:34:05.655Z" }, - { url = "https://files.pythonhosted.org/packages/f7/26/617f98de789de00c2a444fbe6301bb19e66556ac78cff933d2c98f62f2b4/pandas-2.3.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:23ebd657a4d38268c7dfbdf089fbc31ea709d82e4923c5ffd4fbd5747133ce73", size = 13208697, upload-time = "2025-09-29T23:34:21.835Z" }, - { url = "https://files.pythonhosted.org/packages/b9/fb/25709afa4552042bd0e15717c75e9b4a2294c3dc4f7e6ea50f03c5136600/pandas-2.3.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5554c929ccc317d41a5e3d1234f3be588248e61f08a74dd17c9eabb535777dc9", size = 13879233, upload-time = "2025-09-29T23:34:35.079Z" }, - { url = "https://files.pythonhosted.org/packages/98/af/7be05277859a7bc399da8ba68b88c96b27b48740b6cf49688899c6eb4176/pandas-2.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:d3e28b3e83862ccf4d85ff19cf8c20b2ae7e503881711ff2d534dc8f761131aa", size = 11359119, upload-time = "2025-09-29T23:34:46.339Z" }, -] - [[package]] name = "pandas" version = "3.0.1" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", -] dependencies = [ - { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "python-dateutil", marker = "python_full_version >= '3.11'" }, - { name = "tzdata", marker = "(python_full_version >= '3.11' and sys_platform == 'emscripten') or (python_full_version >= '3.11' and sys_platform == 'win32')" }, + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2e/0c/b28ed414f080ee0ad153f848586d61d1878f91689950f037f976ce15f6c8/pandas-3.0.1.tar.gz", hash = "sha256:4186a699674af418f655dbd420ed87f50d56b4cd6603784279d9eef6627823c8", size = 4641901, upload-time = "2026-02-17T22:20:16.434Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/07/c7087e003ceee9b9a82539b40414ec557aa795b584a1a346e89180853d79/pandas-3.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de09668c1bf3b925c07e5762291602f0d789eca1b3a781f99c1c78f6cac0e7ea", size = 10323380, upload-time = "2026-02-17T22:18:16.133Z" }, - { url = "https://files.pythonhosted.org/packages/c1/27/90683c7122febeefe84a56f2cde86a9f05f68d53885cebcc473298dfc33e/pandas-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:24ba315ba3d6e5806063ac6eb717504e499ce30bd8c236d8693a5fd3f084c796", size = 9923455, upload-time = "2026-02-17T22:18:19.13Z" }, - { url = "https://files.pythonhosted.org/packages/0e/f1/ed17d927f9950643bc7631aa4c99ff0cc83a37864470bc419345b656a41f/pandas-3.0.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:406ce835c55bac912f2a0dcfaf27c06d73c6b04a5dde45f1fd3169ce31337389", size = 10753464, upload-time = "2026-02-17T22:18:21.134Z" }, - { url = "https://files.pythonhosted.org/packages/2e/7c/870c7e7daec2a6c7ff2ac9e33b23317230d4e4e954b35112759ea4a924a7/pandas-3.0.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:830994d7e1f31dd7e790045235605ab61cff6c94defc774547e8b7fdfbff3dc7", size = 11255234, upload-time = "2026-02-17T22:18:24.175Z" }, - { url = "https://files.pythonhosted.org/packages/5c/39/3653fe59af68606282b989c23d1a543ceba6e8099cbcc5f1d506a7bae2aa/pandas-3.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a64ce8b0f2de1d2efd2ae40b0abe7f8ae6b29fbfb3812098ed5a6f8e235ad9bf", size = 11767299, upload-time = "2026-02-17T22:18:26.824Z" }, - { url = "https://files.pythonhosted.org/packages/9b/31/1daf3c0c94a849c7a8dab8a69697b36d313b229918002ba3e409265c7888/pandas-3.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9832c2c69da24b602c32e0c7b1b508a03949c18ba08d4d9f1c1033426685b447", size = 12333292, upload-time = "2026-02-17T22:18:28.996Z" }, - { url = "https://files.pythonhosted.org/packages/1f/67/af63f83cd6ca603a00fe8530c10a60f0879265b8be00b5930e8e78c5b30b/pandas-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:84f0904a69e7365f79a0c77d3cdfccbfb05bf87847e3a51a41e1426b0edb9c79", size = 9892176, upload-time = "2026-02-17T22:18:31.79Z" }, - { url = "https://files.pythonhosted.org/packages/79/ab/9c776b14ac4b7b4140788eca18468ea39894bc7340a408f1d1e379856a6b/pandas-3.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:4a68773d5a778afb31d12e34f7dd4612ab90de8c6fb1d8ffe5d4a03b955082a1", size = 9151328, upload-time = "2026-02-17T22:18:35.721Z" }, { url = "https://files.pythonhosted.org/packages/37/51/b467209c08dae2c624873d7491ea47d2b47336e5403309d433ea79c38571/pandas-3.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:476f84f8c20c9f5bc47252b66b4bb25e1a9fc2fa98cead96744d8116cb85771d", size = 10344357, upload-time = "2026-02-17T22:18:38.262Z" }, { url = "https://files.pythonhosted.org/packages/7c/f1/e2567ffc8951ab371db2e40b2fe068e36b81d8cf3260f06ae508700e5504/pandas-3.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0ab749dfba921edf641d4036c4c21c0b3ea70fea478165cb98a998fb2a261955", size = 9884543, upload-time = "2026-02-17T22:18:41.476Z" }, { url = "https://files.pythonhosted.org/packages/d7/39/327802e0b6d693182403c144edacbc27eb82907b57062f23ef5a4c4a5ea7/pandas-3.0.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8e36891080b87823aff3640c78649b91b8ff6eea3c0d70aeabd72ea43ab069b", size = 10396030, upload-time = "2026-02-17T22:18:43.822Z" }, @@ -771,31 +404,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] -[[package]] -name = "pycparser" -version = "2.23" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, -] - [[package]] name = "pycparser" version = "3.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", - "python_full_version == '3.10.*'", -] sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, @@ -875,48 +487,16 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/29/7d/5945b5af29534641820d3bd7b00962abbbdfee84ec7e19f0d5b3175f9a31/pynacl-1.6.2-cp38-abi3-win_arm64.whl", hash = "sha256:834a43af110f743a754448463e8fd61259cd4ab5bbedcf70f9dabad1d28a394c", size = 184801, upload-time = "2026-01-01T17:32:36.309Z" }, ] -[[package]] -name = "pytest" -version = "8.4.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.10'" }, - { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "packaging", marker = "python_full_version < '3.10'" }, - { name = "pluggy", marker = "python_full_version < '3.10'" }, - { name = "pygments", marker = "python_full_version < '3.10'" }, - { name = "tomli", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, -] - [[package]] name = "pytest" version = "9.0.2" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", - "python_full_version == '3.10.*'", -] dependencies = [ - { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, - { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "packaging", marker = "python_full_version >= '3.10'" }, - { name = "pluggy", marker = "python_full_version >= '3.10'" }, - { name = "pygments", marker = "python_full_version >= '3.10'" }, - { name = "tomli", marker = "python_full_version == '3.10.*'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } wheels = [ @@ -968,60 +548,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] -[[package]] -name = "tomli" -version = "2.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" }, - { url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" }, - { url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" }, - { url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" }, - { url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" }, - { url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" }, - { url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" }, - { url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" }, - { url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" }, - { url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" }, - { url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" }, - { url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" }, - { url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" }, - { url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" }, - { url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" }, - { url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" }, - { url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" }, - { url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" }, - { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" }, - { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" }, - { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" }, - { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" }, - { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" }, - { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" }, - { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" }, - { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" }, - { url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" }, - { url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" }, - { url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" }, - { url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" }, - { url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" }, - { url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" }, - { url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" }, - { url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" }, - { url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" }, - { url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" }, - { url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" }, - { url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" }, - { url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" }, - { url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" }, - { url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" }, - { url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" }, - { url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" }, - { url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" }, - { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" }, -] - [[package]] name = "typing-extensions" version = "4.15.0" From 8895527889ee9a5a89d824c03ae16e21c096dca8 Mon Sep 17 00:00:00 2001 From: scott-yj-yang Date: Fri, 27 Feb 2026 15:45:57 -0800 Subject: [PATCH 06/24] fix: add **repo_kwargs pass-through, remove unused import, expand tests - Add **repo_kwargs to create_github_group() so callers can forward repo_template, description, team_slug, team_permission, etc. - Remove unused `call` import from test_assign.py - Add test for constructor `groups` parameter auto-loading - Add test for create_canvas_group with group_category=None but explicit in_group_category provided - Add test verifying **repo_kwargs are forwarded to create_group_repo Co-Authored-By: Claude Opus 4.6 --- CanvasGroupy/assign.py | 5 ++++ tests/test_assign.py | 55 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/CanvasGroupy/assign.py b/CanvasGroupy/assign.py index 2b7c7fb..998a9a9 100644 --- a/CanvasGroupy/assign.py +++ b/CanvasGroupy/assign.py @@ -99,6 +99,7 @@ def create_canvas_group( def create_github_group( self, username_quiz_id: int, + **repo_kwargs, ): """Create GitHub repositories for each group. @@ -108,6 +109,9 @@ def create_github_group( Args: username_quiz_id: Canvas quiz ID where students submitted their GitHub usernames. + **repo_kwargs: Additional keyword arguments forwarded to + ``ghg.create_group_repo()`` (e.g. ``repo_template``, + ``description``, ``team_slug``, ``team_permission``). """ if not self.groups: raise ValueError( @@ -127,6 +131,7 @@ def create_github_group( collaborators=group_git_usernames, permission="write", private=True, + **repo_kwargs, ) repos.append(repo) return repos diff --git a/tests/test_assign.py b/tests/test_assign.py index 4205c89..17f1e4e 100644 --- a/tests/test_assign.py +++ b/tests/test_assign.py @@ -1,6 +1,6 @@ import pytest import pandas as pd -from unittest.mock import MagicMock, call +from unittest.mock import MagicMock from CanvasGroupy.assign import AssignGroup @@ -62,6 +62,18 @@ def test_load_groups_requires_columns(self, mock_services): with pytest.raises(ValueError, match="group_name"): ag.load_groups(df) + def test_constructor_groups_parameter_auto_loads(self, mock_services): + ghg, cg = mock_services + df = pd.DataFrame({ + "group_name": ["Team1", "Team1", "Team2"], + "student_id": ["alice", "bob", "carol"], + }) + ag = AssignGroup(ghg=ghg, cg=cg, groups=df) + assert ag.groups == { + "Team1": ["alice", "bob"], + "Team2": ["carol"], + } + class TestCreateCanvasGroup: def test_calls_assign_canvas_group_for_each_group(self, loaded_ag): @@ -105,6 +117,22 @@ def test_raises_when_no_groups_loaded(self, mock_services): with pytest.raises(ValueError, match="No groups loaded"): ag.create_canvas_group(in_group_category="Anything") + def test_works_with_group_category_none_and_explicit_category(self, loaded_ag): + ag, ghg, cg = loaded_ag + cg.group_category = None + ag.create_canvas_group(in_group_category="Explicit Category") + assert cg.assign_canvas_group.call_count == 2 + cg.assign_canvas_group.assert_any_call( + group_name="Group1", + group_members=["alice", "bob"], + in_group_category="Explicit Category", + ) + cg.assign_canvas_group.assert_any_call( + group_name="Group2", + group_members=["carol"], + in_group_category="Explicit Category", + ) + class TestCreateGitHubGroup: def test_calls_create_group_repo_for_each_group(self, loaded_ag): @@ -150,3 +178,28 @@ def test_raises_when_no_groups_loaded(self, mock_services): ag = AssignGroup(ghg=ghg, cg=cg) with pytest.raises(ValueError, match="No groups loaded"): ag.create_github_group(username_quiz_id=42) + + def test_forwards_repo_kwargs_to_create_group_repo(self, loaded_ag): + ag, ghg, cg = loaded_ag + cg.fetch_username_from_quiz.return_value = { + "alice": "alice_gh", + "bob": "bob_gh", + "carol": "carol_gh", + } + ag.create_github_group( + username_quiz_id=42, + repo_template="org/template", + description="A project repo", + team_slug="Instructors", + team_permission="admin", + ) + ghg.create_group_repo.assert_any_call( + repo_name="Group1", + collaborators=["alice_gh", "bob_gh"], + permission="write", + private=True, + repo_template="org/template", + description="A project repo", + team_slug="Instructors", + team_permission="admin", + ) From 5971bd335bef040507d70c41faf28cb246ef6368 Mon Sep 17 00:00:00 2001 From: scott-yj-yang Date: Fri, 27 Feb 2026 15:47:37 -0800 Subject: [PATCH 07/24] chore: remove GroupEng dependency, bump version to 0.1.0 Co-Authored-By: Claude Opus 4.6 --- CanvasGroupy/__init__.py | 2 +- pyproject.toml | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/CanvasGroupy/__init__.py b/CanvasGroupy/__init__.py index 7866363..ad0dc72 100644 --- a/CanvasGroupy/__init__.py +++ b/CanvasGroupy/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.0.3" +__version__ = "0.1.0" from .github import GitHubGroup from .canvas import CanvasGroup diff --git a/pyproject.toml b/pyproject.toml index 4d6b3d4..845f348 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "CanvasGroupy" -version = "0.0.3" +version = "0.1.0" description = "Canvas Grouping Python Script" readme = "README.md" license = "Apache-2.0" @@ -27,7 +27,6 @@ dependencies = [ "numpy", "PyGithub", "canvasapi", - "groupeng @ git+https://github.com/scott-yj-yang/groupeng.git@dev", ] [project.optional-dependencies] @@ -35,9 +34,6 @@ dev = [ "pytest", ] -[tool.hatch.metadata] -allow-direct-references = true - [project.urls] Homepage = "https://github.com/FleischerResearchLab/CanvasGroupy" Documentation = "https://FleischerResearchLab.github.io/CanvasGroupy" From b4f6f2dbe3cbd4bfcc0222b0877e29b103805b79 Mon Sep 17 00:00:00 2001 From: scott-yj-yang Date: Fri, 27 Feb 2026 15:48:13 -0800 Subject: [PATCH 08/24] chore: update LICENSE from MIT to Apache-2.0 Co-Authored-By: Claude Opus 4.6 --- LICENSE | 221 ++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 200 insertions(+), 21 deletions(-) diff --git a/LICENSE b/LICENSE index 56ac9dc..8498c88 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,200 @@ -MIT License - -Copyright (c) 2023 Fleischer Research Lab @ UC San Diego - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to the Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by the Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding any notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. Please also get an approval + from your organization before applying this license to your work. + + Copyright 2023 Fleischer Research Lab @ UC San Diego + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. From b35bdd60236a31b998f90fc7af942d3cb439ae8b Mon Sep 17 00:00:00 2001 From: scott-yj-yang Date: Fri, 27 Feb 2026 15:48:38 -0800 Subject: [PATCH 09/24] docs: rewrite README with usage examples, remove GroupEng references Co-Authored-By: Claude Opus 4.6 --- README.md | 144 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 89 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 3715fc2..725d625 100644 --- a/README.md +++ b/README.md @@ -1,66 +1,100 @@ -CanvasGroupy -================ +# CanvasGroupy -View our documentation [at this -link](https://fleischerresearchlab.github.io/CanvasGroupy/) +> Automate project group management across Canvas LMS and GitHub. -This module will use `GroupEng` to create canvas and github groups. +[![CI](https://github.com/FleischerResearchLab/CanvasGroupy/actions/workflows/test.yaml/badge.svg)](https://github.com/FleischerResearchLab/CanvasGroupy/actions/workflows/test.yaml) +[![Documentation](https://github.com/FleischerResearchLab/CanvasGroupy/actions/workflows/deploy.yaml/badge.svg)](https://FleischerResearchLab.github.io/CanvasGroupy/) -## Install +CanvasGroupy helps instructors manage project groups across **Canvas LMS** and **GitHub**. Create student groups on Canvas, set up GitHub repositories with the right permissions, and automate grading workflows — all from Python. -``` sh +## Features + +- **Canvas Group Management** — Create group categories, assign students to groups, and send notifications via Canvas API +- **GitHub Repository Setup** — Create repos from templates, add collaborators, manage team permissions +- **Group Assignment** — Load group rosters from CSV or DataFrame and sync to Canvas + GitHub +- **Grading Workflow** — Parse scores from GitHub issue templates and post grades back to Canvas +- **Batch Feedback** — Release feedback to all groups via GitHub Issues from markdown templates + +## Installation + +```bash pip install CanvasGroupy ``` -## How to use +## Quick Start + +### 1. Set up credentials -Please visit [GroupEng Official Website](https://groupeng.org/) to see -the documnetation of how to use GroupEng. +Create a `credentials.json` file: -``` python -assign_groups("example/sample_group_specification.groupeng") +```json +{ + "Canvas Token": "your-canvas-api-token", + "GitHub Token": "your-github-personal-access-token" +} ``` - ['-', '-', 'B', '-', '-'] - ['B', '-', 'H', '-', '-'] - ['H', '-', '-', '-', '-'] - ['-', 'H', '-', 'B', 'H'] - ['-', '-', '-', 'B', '-'] - ['-', '-', 'H', '-', '-'] - ['nanotech', 'renewable energy', 'nanotech', 'nanotech', 'nanotech'] - ['automotive', 'automotive', 'robotics', 'automotive', 'renewable energy'] - ['automotive', 'statistics', 'automotive', 'renewable energy', 'renewable energy'] - ['automotive', 'automotive', 'statistics', 'renewable energy', 'renewable energy'] - ['automotive', 'automotive', 'renewable energy', 'statistics', 'renewable energy'] - ['renewable energy', 'automotive', 'automotive', 'statistics', 'renewable energy'] - ['CS', 'EE', 'Mech E', 'CS', 'EE'] - ['CS', 'EE', 'Mech E', 'Mech E', 'EE'] - ['CS', 'Civ E', 'EE', 'Mech E', 'Mech E'] - ['EE', 'Civ E', 'Civ E', 'EE', 'Mech E'] - ['EE', 'Mech E', 'CS', 'EE', 'EE'] - ['Civ E', 'Mech E', 'CS', 'Mech E', 'EE'] - ['y', 'y', 'y', 'y', 'y'] - ['y', 'y', 'y', 'y', 'y'] - ['y', 'y', 'y', 'y', 'y'] - ['y', 'y', 'y', 'y', 'y'] - ['y', 'y', 'y', 'y', 'y'] - ['-', '-', 'y', '-', 'y'] - ['y', 'y', 'y', 'y', 'y'] - ['y', 'y', 'y', 'y', 'y'] - ['y', 'y', 'y', 'y', 'y'] - ['y', 'y', 'y', 'y', 'y'] - ['y', 'y', 'y', 'y', 'y'] - ['y', '-', '-', 'y', 'y'] - ['y', 'y', 'y', '-', '-'] - ['y', 'y', '-', 'y', '-'] - ['-', '-', '-', 'y', 'y'] - ['y', '-', 'y', '-', 'y'] - ['-', '-', 'y', '-', 'y'] - [3.2579174234, 3.5995299693, 3.2756432963, 4.220160605, 2.5723254477] - [3.2579174234, 3.2756432963, 2.5723254477, 3.5995299693, 4.220160605] - [4.220160605, 3.5995299693, 3.2756432963, 3.2579174234, 2.5723254477] - [3.2756432963, 4.220160605, 3.2579174234, 2.5723254477, 3.5995299693] - [3.5995299693, 4.220160605, 3.2756432963, 3.2579174234, 2.5723254477] - [3.5995299693, 3.2756432963, 2.5723254477, 3.2579174234, 4.220160605] - - (False, 'groups_example_2023-04-18_13-00-35') +- **Canvas Token**: Generate at `Canvas > Account > Settings > New Access Token` +- **GitHub Token**: Generate at `GitHub > Settings > Developer settings > Personal access tokens` + +### 2. Create groups on Canvas + +```python +from CanvasGroupy import CanvasGroup + +cg = CanvasGroup(credentials_fp="credentials.json", + API_URL="https://canvas.ucsd.edu", + course_id=12345) + +# Create a group category and assign students +cg.create_group_category({"name": "Project Groups"}) +cg.assign_canvas_group( + group_name="Team Alpha", + group_members=["student1", "student2", "student3"], + in_group_category="Project Groups" +) +``` + +### 3. Load groups from CSV and create GitHub repos + +```python +from CanvasGroupy import AssignGroup, GitHubGroup, CanvasGroup + +ghg = GitHubGroup(credentials_fp="credentials.json", org="MyOrg") +cg = CanvasGroup(credentials_fp="credentials.json", + API_URL="https://canvas.ucsd.edu", + course_id=12345) + +ag = AssignGroup(ghg=ghg, cg=cg) + +# Load from CSV (columns: group_name, student_id) +ag.load_groups("groups.csv") + +# Create groups on Canvas +ag.create_canvas_group(in_group_category="Project Groups") +``` + +### 4. Grade projects via GitHub Issues + +```python +from CanvasGroupy import Grading, GitHubGroup, CanvasGroup + +grading = Grading(ghg=ghg, cg=cg) + +repo = ghg.get_repo("MyOrg/team-alpha") +grading.grade_project( + repo=repo, + component="checkpoint", + assignment_id=67890, + canvas_group_category="Project Groups", + post=True # Set False for dry run +) +``` + +## Documentation + +Full documentation: [FleischerResearchLab.github.io/CanvasGroupy](https://FleischerResearchLab.github.io/CanvasGroupy/) + +## License + +Apache-2.0 — see [LICENSE](LICENSE) for details. From f32234e871eb14c2c06e6005ea030fefc98fb0a3 Mon Sep 17 00:00:00 2001 From: scott-yj-yang Date: Fri, 27 Feb 2026 15:55:18 -0800 Subject: [PATCH 10/24] docs: add Google-style docstrings to all public classes and methods Co-Authored-By: Claude Opus 4.6 --- CanvasGroupy/canvas.py | 262 +++++++++++++++++++++++++++++++++++++-- CanvasGroupy/github.py | 263 +++++++++++++++++++++++++++++++++++++--- CanvasGroupy/grading.py | 125 ++++++++++++++++++- 3 files changed, 614 insertions(+), 36 deletions(-) diff --git a/CanvasGroupy/canvas.py b/CanvasGroupy/canvas.py index bc446b3..e97a2d5 100644 --- a/CanvasGroupy/canvas.py +++ b/CanvasGroupy/canvas.py @@ -23,6 +23,22 @@ class bcolors: UNDERLINE = '\033[4m' class CanvasGroup(): + """Manage Canvas LMS group operations including roster, grading, and messaging. + + Provides methods to authenticate with the Canvas API, manage courses and + group categories, create and populate groups, post grades, and send + messages to students. + + Attributes: + API_URL: The Canvas instance base URL. + canvas: Authenticated Canvas API client. + course: The currently selected Canvas course. + group_category: The active group category (group set). + users: List of enrolled students in the course. + assignment: The currently linked assignment for grading. + verbosity: Controls output verbosity (0 = silent, 1 = print all). + """ + def __init__(self, credentials_fp = "", # credential file path. [Template of the credentials.json](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/nbs/credentials.json) API_URL="https://canvas.ucsd.edu", # the domain name of canvas @@ -30,7 +46,16 @@ def __init__(self, group_category="", # target group category (set) of interests verbosity=1 # Controls the verbosity: 0 = Silent, 1 = print all messages ): - "Initialize Canvas Group within a Group Set and its appropriate memberships" + """Initialize a CanvasGroup instance and optionally authenticate and configure. + + Args: + credentials_fp: Path to the credentials JSON file containing API + tokens. See the template at the project repository. + API_URL: The Canvas instance base URL (e.g., ``https://canvas.ucsd.edu``). + course_id: The Canvas course ID, found in the course URL. + group_category: Name of the target group category (group set). + verbosity: Controls output verbosity (0 = silent, 1 = print all). + """ self.API_URL = API_URL self.canvas = None self.course = None @@ -61,7 +86,20 @@ def __init__(self, def auth_canvas(self, credentials_fp: str # the Authenticator key generated from canvas ): - "Authorize the canvas module with API_KEY" + """Authenticate with the Canvas API using a credentials file. + + Reads the Canvas API token from the JSON credentials file and + initializes the Canvas API client. Verifies the token by fetching + the activity stream summary. + + Args: + credentials_fp: Path to the JSON file containing a + ``"Canvas Token"`` key. + + Raises: + FileNotFoundError: If the credentials file does not exist. + canvasapi.exceptions.InvalidAccessToken: If the token is invalid. + """ self.credentials_fp = credentials_fp with open(credentials_fp, "r") as f: credentials = json.load(f) @@ -75,7 +113,18 @@ def auth_canvas(self, def set_course(self, course_id: int # the course id of the target course ): - "Set the target course by the course ID" + """Set the target course and fetch the student roster. + + Retrieves the course by ID, fetches all enrolled students, and + builds lookup dictionaries mapping emails to Canvas IDs and names. + + Args: + course_id: The numeric Canvas course ID. + + Raises: + canvasapi.exceptions.ResourceDoesNotExist: If the course ID is + invalid. + """ self.course = self.canvas.get_course(course_id) if self.verbosity != 0: print(f"Course Set: {bcolors.OKGREEN} {self.course.name} {bcolors.ENDC}") @@ -99,7 +148,22 @@ def set_course(self, def link_assignment(self, assignment_id: int # assignment id, found at the url of assignmnet tab ) -> canvasapi.assignment.Assignment: # target assignment - "Link the target assignment on canvas" + """Link a Canvas assignment for grading. + + Fetches the assignment by ID and stores it for subsequent grading + operations such as ``post_grade``. + + Args: + assignment_id: The Canvas assignment ID, found in the + assignment URL. + + Returns: + The linked Canvas assignment object. + + Raises: + canvasapi.exceptions.ResourceDoesNotExist: If the assignment ID + is invalid. + """ assignment = self.course.get_assignment(assignment_id) if self.verbosity != 0: print(f"Assignment {bcolors.OKGREEN+assignment.name+bcolors.ENDC} Link!") @@ -112,7 +176,25 @@ def post_grade(self, text_comment="", # text comment of the submission. Can feed force=False, # whether force to post grade for all students. If False (default), it will skip post for the same score. ) -> canvasapi.submission.Submission: # created submission - "Post grade and comment to canvas to the target assignment" + """Post a grade and optional comment to the linked Canvas assignment. + + Submits a grade for a specific student on the currently linked + assignment. If ``force`` is False and the existing score matches + the new grade, the submission is skipped. + + Args: + student_id: The Canvas user ID of the student, obtainable + from ``self.email_to_canvas_id``. + grade: The numeric grade to post. + text_comment: An optional text comment to attach to the + submission. + force: If False (default), skip posting when the existing + score already matches ``grade``. + + Returns: + The updated submission object, or None if the post was + skipped. + """ submission = self.assignment.get_submission(student_id) if not force and submission.score == grade: if self.verbosity != 0: @@ -134,6 +216,21 @@ def post_grade(self, def get_email_by_name(self, name_fussy: str # search by first name or last name of a student ) -> str: # email of a search student + """Look up a student's email prefix by a partial name match. + + Performs a case-insensitive substring search against all student + names in the roster and returns the first matching email prefix. + + Args: + name_fussy: A partial or full student name to search for + (first name, last name, or substring). + + Returns: + The email prefix (SIS Login ID) of the first matching student. + + Raises: + ValueError: If no student name contains the search string. + """ name_fussy = name_fussy.lower() for email, name in self.email_to_name.items(): if name_fussy in name.lower(): @@ -144,6 +241,22 @@ def get_email_by_name(self, def set_group_category(self, category_name: str # the target group category ) -> canvasapi.group.GroupCategory: # target group category object + """Set the active group category and fetch its groups. + + Selects a group category (group set) by name and retrieves all + groups and their member email lists within that category. + + Args: + category_name: The exact name of the group category to + activate. + + Returns: + The selected group category object. + + Raises: + KeyError: If ``category_name`` does not match any existing + group category in the course. + """ _ = self.get_group_categories() try: self.group_category = self.group_categories[category_name] @@ -164,6 +277,23 @@ def set_group_category(self, def get_groups(self, category_name="" # the target group category. If not provided, will look for self.group_category ) -> dict: # {group_name: [student_emails]} + """Get groups and their members in the current or specified category. + + Returns a dictionary mapping group names to lists of member email + prefixes. If ``category_name`` is provided, the category is set + first. + + Args: + category_name: Optional group category name. If empty, uses + the currently active group category. + + Returns: + A dict mapping group names to lists of student email prefixes. + + Raises: + ValueError: If no group category is set and ``category_name`` + is empty. + """ if category_name != "": self.set_group_category(category_name) return self.group_to_emails @@ -172,10 +302,23 @@ def get_groups(self, return self.group_to_emails def get_course(self): + """Get the current course object. + + Returns: + The currently selected Canvas course object, or None if no + course has been set. + """ return self.course def get_group_categories(self) -> dict: # return a name / group category object - "Grab all existing group categories (group set) in this course" + """List all group categories (group sets) in the current course. + + Fetches every group category from the Canvas course and caches + them in ``self.group_categories``. + + Returns: + A dict mapping category names to their GroupCategory objects. + """ categories = list(self.course.get_group_categories()) self.group_categories = {cat.name: cat for cat in categories} return {cat.name: cat for cat in categories} @@ -183,14 +326,35 @@ def get_group_categories(self) -> dict: # return a name / group category object def create_group_category(self, params: dict # the parameter of canvas group category API @ [this link](https://canvas.instructure.com/doc/api/group_categories.html#method.group_categories.create) ) -> canvasapi.group.GroupCategory: # the generated group category object - "Create group category (group set) in this course" + """Create a new group category (group set) in the current course. + + Args: + params: A dictionary of parameters for the Canvas group + category API. See the Canvas API documentation for + ``group_categories.create``. + + Returns: + The newly created group category object. + """ self.group_category = self.course.create_group_category(**params) return self.group_category def create_group(self, params: dict, #the parameter of canvas group create API at [this link](https://canvas.instructure.com/doc/api/groups.html#method.groups.create) ) -> canvasapi.group.Group: # the generated target group object - "Create canvas group under the target group category" + """Create a group under the currently active group category. + + Args: + params: A dictionary of parameters for the Canvas group + creation API, which must include a ``"name"`` key. See + the Canvas API documentation for ``groups.create``. + + Returns: + The newly created group object. + + Raises: + ValueError: If no group category has been set or created. + """ if self.group_category is None: raise ValueError("Have you specified or create a group category (group set)?") group = self.group_category.create_group(**params) @@ -203,7 +367,20 @@ def join_canvas_group(self, group: canvasapi.group.Group, # the group that students will join group_members:[str], # list of group member's SIS Login (email prefix, before the @.) ) -> [str]: # list of unsuccessful join - "Add membership access of each group member into the group" + """Add students to a Canvas group by their email prefixes. + + Iterates over the provided member list and creates a group + membership for each student. Students whose email prefixes are + not found in the roster are collected and returned. + + Args: + group: The Canvas group object to add members to. + group_members: List of student SIS Login IDs (email prefixes, + the part before the ``@``). + + Returns: + A list of email prefixes for students who could not be added. + """ unsuccessful_join = [] for group_member in group_members: try: @@ -221,7 +398,22 @@ def fetch_username_from_quiz(self, quiz_id: int, # quiz id of the username quiz col_index=7, # canvas quiz generated csv's question field column index ) -> dict: # {SIS Login ID: github username} dictionary - "Fetch the GitHub user name from the canvas quiz" + """Extract GitHub usernames from a Canvas quiz student analysis. + + Downloads the student analysis report for the specified quiz, + parses the CSV, and builds a mapping from student email prefixes + to their submitted GitHub usernames. + + Args: + quiz_id: The Canvas quiz ID containing GitHub username + submissions. + col_index: The zero-based column index in the generated CSV + that contains the GitHub username question response. + + Returns: + A dict mapping student email prefixes (SIS Login IDs) to + their submitted GitHub usernames. + """ header = {'Authorization': 'Bearer ' + self.API_KEY} quiz = self.course.get_quiz(quiz_id) if self.verbosity != 0: @@ -286,7 +478,25 @@ def check_github_usernames(self, send_undone_reminder=False, # send quiz undone reminder using canvas email quiz_url="", # include a quiz url in the conversation for student to quickly complete the quiz. ) -> dict: # {email: github username} of unreasonable GitHub id - "batch check GitHub username from student inputs." + """Batch validate GitHub usernames and optionally notify students. + + Checks each GitHub username against the GitHub API. Optionally + sends Canvas messages to students with invalid usernames and to + students who have not yet submitted the quiz. + + Args: + github_usernames: A dict mapping email prefixes to GitHub + usernames, typically from ``fetch_username_from_quiz``. + send_canvas_email: If True, send a Canvas notification to + students with invalid GitHub usernames. + send_undone_reminder: If True, send a Canvas reminder to + students who have not submitted the quiz. + quiz_url: URL of the quiz to include in reminder messages. + + Returns: + A dict of email prefixes to GitHub usernames that could not + be validated on GitHub. + """ unsuccessful = {} for email, github_username in github_usernames.items(): valid = self._check_single_github_username(email, github_username) @@ -340,7 +550,21 @@ def assign_canvas_group(self, group_members:[str], # list of group member's SIS Login in_group_category: str, # specify which group category the group belongs to ) -> (canvasapi.group.Group, [str]): # list of unsuccessful join - "Create new groups and assign group member into the class in the `self.group_category`" + """Create a Canvas group and assign members to it. + + Sets the group category, creates a new group, and adds the + specified students as members. + + Args: + group_name: Display name for the new group on Canvas. + group_members: List of student SIS Login IDs (email prefixes). + in_group_category: Name of the group category (group set) + the new group belongs to. + + Returns: + A tuple of (created group object, list of email prefixes + for students who could not be added). + """ self.set_group_category(in_group_category) group = self.create_group({"name": group_name}) unsuccessful_join = self.join_canvas_group(group, group_members) @@ -353,7 +577,19 @@ def create_conversation(self, subject:str, # subject of the conversation body:str, # The message to be sent ) -> canvasapi.conversation.Conversation: # created conversation - "Create a conversation with the target user" + """Send a Canvas message (conversation) to a student. + + Creates a new conversation in the context of the current course. + + Args: + recipients: Recipient Canvas user ID, or a course/group ID + prefixed with ``'course_'`` or ``'group_'``. + subject: Subject line of the conversation. + body: The message body text. + + Returns: + The created Canvas conversation object. + """ conv = self.canvas.create_conversation( [recipients], body=body, diff --git a/CanvasGroupy/github.py b/CanvasGroupy/github.py index 1b159a5..97251cb 100644 --- a/CanvasGroupy/github.py +++ b/CanvasGroupy/github.py @@ -20,11 +20,32 @@ class bcolors: UNDERLINE = '\033[4m' class GitHubGroup: + """Manage GitHub organization repositories, teams, and collaborators. + + Provides methods to authenticate with GitHub, create repositories + (blank or from templates), manage collaborators and teams, create + issues, and release feedback to student groups. + + Attributes: + github: Authenticated PyGithub client. + org: The target GitHub organization object. + verbosity: Controls output verbosity (0 = silent, 1 = print status). + """ + def __init__(self, credentials_fp="", # the file path to the credential json org="", # the organization name verbosity=1 # Controls the verbosity: 0=silent, 1=print status ): + """Initialize a GitHubGroup instance and optionally authenticate. + + Args: + credentials_fp: Path to the JSON credentials file containing + a ``"GitHub Token"`` key. + org: The GitHub organization name to target. + verbosity: Controls output verbosity (0 = silent, 1 = print + status). + """ self.github = None self.org = None self.verbosity = verbosity @@ -37,7 +58,20 @@ def __init__(self, def auth_github(self, credentials_fp: str # the personal access token generated at GitHub Settings ): - "Authenticate GitHub account with the provided credentials" + """Authenticate with GitHub using a credentials file. + + Reads the GitHub personal access token from the JSON credentials + file and initializes the PyGithub client. Verifies the token by + fetching the authenticated user's repositories. + + Args: + credentials_fp: Path to the JSON file containing a + ``"GitHub Token"`` key. + + Raises: + FileNotFoundError: If the credentials file does not exist. + github.GithubException: If the token is invalid. + """ with open(credentials_fp, "r") as f: token = json.load(f)["GitHub Token"] self.github = Github(token) @@ -50,7 +84,18 @@ def auth_github(self, def set_org(self, org: str # the target organization name ): - "Set the target organization for repo creation" + """Set the target GitHub organization. + + Retrieves the organization object by name and stores it for + subsequent repository and team operations. + + Args: + org: The GitHub organization login name. + + Raises: + github.UnknownObjectException: If the organization does not + exist. + """ self.org = self.github.get_organization(org) if self.verbosity != 0: print(f"Target Organization Set: {bcolors.OKGREEN} {self.org.login} {bcolors.ENDC}") @@ -62,7 +107,29 @@ def create_repo(self, description="", # description for the GitHub repository personal_account=False, # create repos in personal GitHub account ) -> github.Repository.Repository: - "Create a repository, either blank, or from a template" + """Create a repository, either blank or from a template. + + Creates a new repository under the target organization (or + personal account). If ``repo_template`` is provided, the new + repository is created from that template. + + Args: + repo_name: Name for the new repository. + repo_template: Full name of the template repository in + ``"owner/repo"`` format. If empty, a blank repo is + created. + private: Whether the repository should be private. + description: Description for the repository. + personal_account: If True, create under the authenticated + user's personal account instead of the organization. + + Returns: + The newly created GitHub repository object. + + Raises: + ValueError: If the organization is not set and + ``personal_account`` is True. + """ if self.org is None and personal_account: raise ValueError("Organization is not set") if personal_account: @@ -86,7 +153,22 @@ def create_repo(self, def get_repo(self, repo_full_name: str # full name of the target repository ) -> github.Repository.Repository: - "To get a repository by its name" + """Get a repository by its full name. + + Attempts to fetch the repository directly by full name. If that + fails, falls back to searching within the organization. + + Args: + repo_full_name: Full name of the repository (e.g., + ``"owner/repo"``). + + Returns: + The GitHub repository object. + + Raises: + github.UnknownObjectException: If the repository cannot be + found. + """ try: return self.github.get_repo(repo_full_name) except Exception: @@ -95,14 +177,37 @@ def get_repo(self, def get_org_repo(self, repo_full_name: str # full name of the target repository ) -> github.Repository.Repository: - "Get a repository within the target organization" + """Get a repository within the target organization. + + Args: + repo_full_name: Name of the repository within the + organization. + + Returns: + The GitHub repository object. + + Raises: + github.UnknownObjectException: If the repository is not + found in the organization. + """ return self.org.get_repo(repo_full_name) def get_team(self, team_slug:str # team slug of the team ) -> github.Team.Team: - "Get the team inside the target organization" + """Get a team by its slug within the target organization. + + Args: + team_slug: The URL-friendly slug of the team. + + Returns: + The GitHub team object. + + Raises: + ValueError: If the organization has not been set. + github.UnknownObjectException: If the team slug is not found. + """ if self.org is None: raise ValueError("The organization has not been set. Please set it via g.set_org") return self.org.get_team_by_slug(team_slug) @@ -112,7 +217,16 @@ def rename_files(self, og_filename: str, # old file name new_filename: str # new file name ): - "Rename the file by delete the old file and commit the new file" + """Rename a file in a repository by creating a copy and deleting the original. + + This performs a rename by committing the file content under the + new name and then deleting the old file in separate commits. + + Args: + repo: The repository containing the file. + og_filename: The current file path in the repository. + new_filename: The desired new file path. + """ file = repo.get_contents(og_filename) repo.create_file(new_filename, "rename files", file.decoded_content) repo.delete_file(og_filename, "delete old files", file.sha) @@ -126,7 +240,14 @@ def add_collaborator(self, collaborator:str, # GitHub username of the collaborator permission:str # `pull`, `push` or `admin` ): - "Add collaborator to the repository with specified permission" + """Add a collaborator to a repository with the specified permission. + + Args: + repo: The target GitHub repository. + collaborator: GitHub username of the collaborator to add. + permission: Permission level -- ``"pull"``, ``"push"``, or + ``"admin"``. + """ try: repo.add_to_collaborators(collaborator, permission) except Exception as e: @@ -140,13 +261,28 @@ def remove_collaborator(self, repo: github.Repository.Repository, # target repository collaborator:str, # GitHub username of the collaborator ): - "Remove collaborator privileges from the repository" + """Remove a collaborator from a repository. + + Args: + repo: The target GitHub repository. + collaborator: GitHub username of the collaborator to remove. + """ repo.remove_from_collaborators(collaborator) def resend_invitations(self, repo: github.Repository.Repository, # target repository ) -> [github.NamedUser.NamedUser]: # list of re-invited user - "Resent Invitation to invitee who did not accept the invitation" + """Resend pending collaboration invitations for a repository. + + Revokes each pending invitation and re-invites the user with + the same permissions, effectively resending the email. + + Args: + repo: The target GitHub repository. + + Returns: + A list of user objects whose invitations were resent. + """ pendings = list(repo.get_pending_invitations()) users = [p.invitee for p in pendings] if self.verbosity != 0: @@ -164,7 +300,15 @@ def resend_invitations(self, def resent_invitations_team_repos(self, team_slug: str # team slug (name) under the org ): - "For all repository under that team, Resent invitation to invitee who did not accept the invitation" + """Resend pending invitations for all repositories under a team. + + Iterates over every repository associated with the team and + resends any pending collaboration invitations. + + Args: + team_slug: The URL-friendly slug of the team whose repos + should have invitations resent. + """ team = self.get_team(team_slug) repos = team.get_repos() for repo in repos: @@ -181,7 +325,14 @@ def add_team(self, team_slug: str, # team slug (name) permission:str # `pull`, `push` or `admin` ): - "Add team to the repository with specified permission" + """Add a team to a repository with the specified permission. + + Args: + repo: The target GitHub repository. + team_slug: The URL-friendly slug of the team to add. + permission: Permission level -- ``"pull"``, ``"push"``, or + ``"admin"``. + """ team = self.get_team(team_slug) team.add_to_repos(repo) team.update_team_repository(repo, permission) @@ -195,7 +346,18 @@ def create_feedback_dir(self, template_fp: str, destination="feedback" # directory path of the template file. ): - "Create feedback directory on local machine" + """Create a local feedback directory populated from template files. + + Copies all files from the template directory into a + repo-specific subdirectory under the destination path. + + Args: + repo: The target repository (used for naming the + subdirectory). + template_fp: Path to the directory containing template files. + destination: Base directory where feedback subdirectories + are created. + """ os.makedirs(destination, exist_ok=True) os.makedirs(f"{destination}/{repo.name}", exist_ok=True) files = glob.glob(f"{template_fp}/*") @@ -215,7 +377,16 @@ def create_issue(self, title: str, # title of the issue, content: str # content of the issue ) -> github.Issue.Issue: # open issue - "Create GitHub issue to the target repository" + """Create a GitHub issue in the target repository. + + Args: + repo: The target GitHub repository. + title: Title of the issue. + content: Body content of the issue (supports Markdown). + + Returns: + The newly created GitHub issue object. + """ issue = repo.create_issue(title=title, body=content) if self.verbosity != 0: print(f"In the repo: {bcolors.OKGREEN}{repo.name}{bcolors.ENDC},") @@ -226,7 +397,19 @@ def create_issue_from_md(self, repo: github.Repository.Repository, # target repository, md_fp: str # file path of the feedback markdown file ) -> github.Issue.Issue: # open issue - "Create GitHub issue from markdown file." + """Create a GitHub issue from a markdown file. + + Reads the markdown file, uses the first line (without the ``#`` + prefix) as the issue title, and the full file content as the + issue body. + + Args: + repo: The target GitHub repository. + md_fp: File path to the feedback markdown file. + + Returns: + The newly created GitHub issue object. + """ md = "" with open(md_fp, "r") as f: md = f.read() @@ -238,7 +421,19 @@ def release_feedback(self, md_filename: str, # feedback markdown file name feedback_dir="feedback", # feedback directory contains the markdown files ): - "Release feedback via GitHub issue from all the feedbacks in the feedback directory" + """Release feedback via GitHub issues to all groups. + + Iterates over every subdirectory in the feedback directory, + treats each subdirectory name as a repository name within the + organization, and creates an issue from the specified markdown + file. + + Args: + md_filename: Name of the markdown file within each repo's + feedback subdirectory (e.g., ``"checkpoint.md"``). + feedback_dir: Path to the base feedback directory containing + per-repo subdirectories. + """ repo_names = os.listdir(feedback_dir) for repo_name in repo_names: if repo_name == ".DS_Store": @@ -262,7 +457,41 @@ def create_group_repo(self, feedback_dir=False, # whether to create a feedback directory for each repository created feedback_template_fp="", # the directory of the feedback template ) -> github.Repository.Repository: # created repository - "Create a Group Repository" + """Create a group repository with collaborators and team permissions. + + Creates a repository (optionally from a template), renames files + if specified, adds collaborators, assigns a team, and optionally + creates a local feedback directory. + + Args: + repo_name: Name for the new group repository. + collaborators: List of GitHub usernames to add as + collaborators. + permission: Permission level for collaborators -- + ``"pull"``, ``"push"``, or ``"admin"``. + rename_files: Dictionary mapping old filenames to new + filenames for renaming after creation. + repo_template: Full name of the template repository in + ``"owner/repo"`` format. If empty, a blank repo is + created. + private: Whether the repository should be private. + description: Description for the repository. + team_slug: Team slug to add to the repository. If empty, + no team is added. + team_permission: Permission level for the team -- + ``"pull"``, ``"push"``, or ``"admin"``. + feedback_dir: If True, create a local feedback directory + for this repository. + feedback_template_fp: Path to the feedback template + directory. Required when ``feedback_dir`` is True. + + Returns: + The newly created GitHub repository object. + + Raises: + ValueError: If ``feedback_dir`` is True but + ``feedback_template_fp`` is empty. + """ repo = self.create_repo(repo_name, repo_template, private, description) if self.verbosity != 0: print(f"Repo {bcolors.OKGREEN} {repo.name} {bcolors.ENDC} Created... Wait for 3 sec to updates") diff --git a/CanvasGroupy/grading.py b/CanvasGroupy/grading.py index 4f30595..32cf000 100644 --- a/CanvasGroupy/grading.py +++ b/CanvasGroupy/grading.py @@ -17,10 +17,29 @@ class bcolors: UNDERLINE = '\033[4m' class Grading: + """Orchestrate grading between GitHub and Canvas LMS. + + Bridges GitHub issue-based grading workflows with Canvas grade + posting. Parses scores from GitHub issue templates and pushes them + to the corresponding Canvas assignment for each group member. + + Attributes: + ghg: An authenticated GitHubGroup instance. + cg: An authenticated CanvasGroup instance. + """ + def __init__(self, ghg:GitHubGroup=None, # authenticated GitHub object cg:CanvasGroup=None, # authenticated canvas object ): + """Initialize a Grading instance with GitHub and Canvas clients. + + Args: + ghg: An authenticated GitHubGroup instance for GitHub + operations. + cg: An authenticated CanvasGroup instance for Canvas + operations. + """ self.ghg = ghg self.cg = cg @@ -28,14 +47,41 @@ def create_issue_from_md(self, repo:github.Repository.Repository, # target repository to create issue md_fp: str # file path of the feedback markdown file ) -> github.Issue.Issue: # open issue - "Create GitHub issue from markdown file." + """Create a GitHub issue from a markdown file. + + Delegates to the underlying GitHubGroup instance to read the + markdown file and create an issue in the target repository. + + Args: + repo: The target GitHub repository. + md_fp: File path to the feedback markdown file. + + Returns: + The newly created GitHub issue object. + """ return self.ghg.create_issue_from_md(repo, md_fp) def fetch_issue(self, repo:github.Repository.Repository, # target repository to fetch issue component:str, # the component of the project grading, let it be proposal/checkpoint/final. Need to match the issue's title ) -> github.Issue.Issue: - "Fetch the issue on GitHub repo and choose related one" + """Fetch a specific issue by matching the component name in its title. + + Searches all issues in the repository and returns the first one + whose title contains the component string (case-insensitive). + + Args: + repo: The target GitHub repository to search. + component: The grading component name (e.g., ``"proposal"``, + ``"checkpoint"``, ``"final"``). Must match a substring + of the issue title. + + Returns: + The matching GitHub issue object. + + Raises: + ValueError: If no issue title contains the component string. + """ for issue in list(repo.get_issues()): if component.lower() in issue.title.lower(): return issue @@ -45,7 +91,23 @@ def parse_score_from_issue(self, repo:github.Repository.Repository, # target repository to create issue component:str, # The component of the project grading, let it be proposal/checkpoint/final. Need to match the issue's title ) -> int: # the fetched score of that component - "parse score from the template issue" + """Parse the numeric score from a GitHub issue grading template. + + Fetches the issue matching the given component and scans its + body for a line containing ``"Score ="`` to extract the grade. + + Args: + repo: The target GitHub repository. + component: The grading component name (e.g., ``"proposal"``, + ``"checkpoint"``, ``"final"``). + + Returns: + The parsed integer score from the issue body. + + Raises: + ValueError: If the issue is not found or no valid + ``"Score ="`` line is present. + """ issue = self.fetch_issue(repo, component) body = issue.body score = 0 @@ -63,7 +125,26 @@ def update_canvas_score(self, issue:github.Issue.Issue=None, post=False, # whether to post score via api. for testing purposes ): - "Post score to canvas" + """Post a score to Canvas for all members of a group. + + Links the assignment, iterates over every member in the + specified group, and posts the score with an optional comment + linking to the GitHub issue. + + Args: + group_name: The Canvas group name whose members should + receive the grade. + assignment_id: The Canvas assignment ID for the grading + component. + score: The numeric score to post. + issue: Optional GitHub issue object. If provided, its URL + is included in the submission comment. + post: If True, actually post the grade via the Canvas API. + If False (default), only print what would be posted. + + Raises: + ValueError: If the CanvasGroup's group category is not set. + """ if self.cg.group_category is None: raise ValueError("CanvasGroup's group_category not set.") members = self.cg.group_to_emails[group_name] @@ -88,7 +169,21 @@ def check_graded(self, repo:github.Repository.Repository, # target repository to grade component:str, # The component of the project grading, let it be proposal/checkpoint/final. Need to match the issue's title ) -> bool: # Whether the repo is graded. - "Check whether a component for a group project is graded" + """Check if a project component has been graded. + + Parses the score from the GitHub issue template. If the score + is the Ellipsis sentinel (``...``), the component is considered + ungraded. + + Args: + repo: The target GitHub repository. + component: The grading component name (e.g., ``"proposal"``, + ``"checkpoint"``, ``"final"``). + + Returns: + True if the component has a numeric score, False if the + score is the Ellipsis sentinel. + """ score = self.parse_score_from_issue(repo, component) if score is ...: print(f"{bcolors.WARNING}{repo.name}'s {component} Not Graded. {bcolors.ENDC}") @@ -104,7 +199,25 @@ def grade_project(self, canvas_group_category:str=None, # canvas group category (set) post:bool=False, # whether to post score via api. For testing purposes ): - "grade github project components" + """Grade a project component end-to-end. + + Parses the score from the GitHub issue, maps the repository to + a Canvas group, checks whether the component is already graded, + and posts the score to Canvas if it has not been graded yet. + + Args: + repo: The target GitHub repository to grade. + component: The grading component name (e.g., ``"proposal"``, + ``"checkpoint"``, ``"final"``). + assignment_id: The Canvas assignment ID for this component. + canvas_group_name: Optional dict mapping GitHub repo names + to Canvas group names. If None, the repo name is used + as the group name. + canvas_group_category: Optional Canvas group category name. + If provided, it is set before grading. + post: If True, post the grade via the Canvas API. If False + (default), only print what would be posted. + """ # set the category if you haven't if canvas_group_category is not None: self.cg.set_group_category(canvas_group_category) From 1313f37c229df84407407257ff72b8754fff61ff Mon Sep 17 00:00:00 2001 From: scott-yj-yang Date: Fri, 27 Feb 2026 15:56:24 -0800 Subject: [PATCH 11/24] build: add MkDocs Material config and docs dependencies Co-Authored-By: Claude Opus 4.6 --- mkdocs.yml | 64 ++++++++ pyproject.toml | 4 + uv.lock | 402 ++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 463 insertions(+), 7 deletions(-) create mode 100644 mkdocs.yml diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..bb54657 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,64 @@ +site_name: CanvasGroupy +site_url: https://FleischerResearchLab.github.io/CanvasGroupy +repo_url: https://github.com/FleischerResearchLab/CanvasGroupy +repo_name: FleischerResearchLab/CanvasGroupy + +theme: + name: material + features: + - navigation.tabs + - navigation.sections + - navigation.top + - search.suggest + - content.code.copy + - content.code.annotate + palette: + - scheme: default + primary: indigo + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - scheme: slate + primary: indigo + toggle: + icon: material/brightness-4 + name: Switch to light mode + +plugins: + - search + - mkdocstrings: + handlers: + python: + options: + show_source: true + show_root_heading: true + heading_level: 2 + docstring_style: google + +markdown_extensions: + - admonition + - pymdownx.details + - pymdownx.superfences + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.inlinehilite + - pymdownx.snippets + - toc: + permalink: true + +nav: + - Home: index.md + - Getting Started: + - Installation: getting-started/installation.md + - Authentication: getting-started/authentication.md + - Quick Start: getting-started/quickstart.md + - Tutorials: + - Canvas Groups: tutorials/canvas-groups.md + - GitHub Repos: tutorials/github-repos.md + - Grading Workflow: tutorials/grading.md + - Feedback Templates: tutorials/feedback.md + - API Reference: + - AssignGroup: api/assign.md + - CanvasGroup: api/canvas.md + - GitHubGroup: api/github.md + - Grading: api/grading.md diff --git a/pyproject.toml b/pyproject.toml index 845f348..d5d8620 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,10 @@ dependencies = [ dev = [ "pytest", ] +docs = [ + "mkdocs-material", + "mkdocstrings[python]", +] [project.urls] Homepage = "https://github.com/FleischerResearchLab/CanvasGroupy" diff --git a/uv.lock b/uv.lock index 845e876..b033c1d 100644 --- a/uv.lock +++ b/uv.lock @@ -23,6 +23,29 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl", hash = "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205", size = 68797, upload-time = "2025-10-18T17:46:45.663Z" }, ] +[[package]] +name = "babel" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, +] + +[[package]] +name = "backrefs" +version = "6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/a6/e325ec73b638d3ede4421b5445d4a0b8b219481826cc079d510100af356c/backrefs-6.2.tar.gz", hash = "sha256:f44ff4d48808b243b6c0cdc6231e22195c32f77046018141556c66f8bab72a49", size = 7012303, upload-time = "2026-02-16T19:10:15.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/39/3765df263e08a4df37f4f43cb5aa3c6c17a4bdd42ecfe841e04c26037171/backrefs-6.2-py310-none-any.whl", hash = "sha256:0fdc7b012420b6b144410342caeb8adc54c6866cf12064abc9bb211302e496f8", size = 381075, upload-time = "2026-02-16T19:10:04.322Z" }, + { url = "https://files.pythonhosted.org/packages/0f/f0/35240571e1b67ffb19dafb29ab34150b6f59f93f717b041082cdb1bfceb1/backrefs-6.2-py311-none-any.whl", hash = "sha256:08aa7fae530c6b2361d7bdcbda1a7c454e330cc9dbcd03f5c23205e430e5c3be", size = 392874, upload-time = "2026-02-16T19:10:06.314Z" }, + { url = "https://files.pythonhosted.org/packages/e3/63/77e8c9745b4d227cce9f5e0a6f68041278c5f9b18588b35905f5f19c1beb/backrefs-6.2-py312-none-any.whl", hash = "sha256:c3f4b9cb2af8cda0d87ab4f57800b57b95428488477be164dd2b47be54db0c90", size = 398787, upload-time = "2026-02-16T19:10:08.274Z" }, + { url = "https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl", hash = "sha256:12df81596ab511f783b7d87c043ce26bc5b0288cf3bb03610fe76b8189282b2b", size = 400747, upload-time = "2026-02-16T19:10:09.791Z" }, + { url = "https://files.pythonhosted.org/packages/af/75/be12ba31a6eb20dccef2320cd8ccb3f7d9013b68ba4c70156259fee9e409/backrefs-6.2-py314-none-any.whl", hash = "sha256:e5f805ae09819caa1aa0623b4a83790e7028604aa2b8c73ba602c4454e665de7", size = 412602, upload-time = "2026-02-16T19:10:12.317Z" }, + { url = "https://files.pythonhosted.org/packages/21/f8/d02f650c47d05034dcd6f9c8cf94f39598b7a89c00ecda0ecb2911bc27e9/backrefs-6.2-py39-none-any.whl", hash = "sha256:664e33cd88c6840b7625b826ecf2555f32d491800900f5a541f772c485f7cda7", size = 381077, upload-time = "2026-02-16T19:10:13.74Z" }, +] + [[package]] name = "canvasapi" version = "3.4.0" @@ -39,11 +62,10 @@ wheels = [ [[package]] name = "canvasgroupy" -version = "0.0.3" +version = "0.1.0" source = { editable = "." } dependencies = [ { name = "canvasapi" }, - { name = "groupeng" }, { name = "numpy" }, { name = "pandas" }, { name = "pygithub" }, @@ -53,17 +75,22 @@ dependencies = [ dev = [ { name = "pytest" }, ] +docs = [ + { name = "mkdocs-material" }, + { name = "mkdocstrings", extra = ["python"] }, +] [package.metadata] requires-dist = [ { name = "canvasapi" }, - { name = "groupeng", git = "https://github.com/scott-yj-yang/groupeng.git?rev=dev" }, + { name = "mkdocs-material", marker = "extra == 'docs'" }, + { name = "mkdocstrings", extras = ["python"], marker = "extra == 'docs'" }, { name = "numpy" }, { name = "pandas" }, { name = "pygithub" }, { name = "pytest", marker = "extra == 'dev'" }, ] -provides-extras = ["dev"] +provides-extras = ["dev", "docs"] [[package]] name = "certifi" @@ -188,6 +215,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, ] +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -251,9 +290,24 @@ wheels = [ ] [[package]] -name = "groupeng" -version = "1.3" -source = { git = "https://github.com/scott-yj-yang/groupeng.git?rev=dev#75c674856c83c385f6f0cd70b501e87d932b11ba" } +name = "ghp-import" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, +] + +[[package]] +name = "griffelib" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl", hash = "sha256:01284878c966508b6d6f1dbff9b6fa607bc062d8261c5c7253cb285b06422a7f", size = 142004, upload-time = "2026-02-09T19:09:40.561Z" }, +] [[package]] name = "idna" @@ -273,6 +327,218 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "markdown" +version = "3.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "mergedeep" +version = "1.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" }, +] + +[[package]] +name = "mkdocs" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "ghp-import" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mergedeep" }, + { name = "mkdocs-get-deps" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "pyyaml" }, + { name = "pyyaml-env-tag" }, + { name = "watchdog" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" }, +] + +[[package]] +name = "mkdocs-autorefs" +version = "1.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mkdocs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/c0/f641843de3f612a6b48253f39244165acff36657a91cc903633d456ae1ac/mkdocs_autorefs-1.4.4.tar.gz", hash = "sha256:d54a284f27a7346b9c38f1f852177940c222da508e66edc816a0fa55fc6da197", size = 56588, upload-time = "2026-02-10T15:23:55.105Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl", hash = "sha256:834ef5408d827071ad1bc69e0f39704fa34c7fc05bc8e1c72b227dfdc5c76089", size = 25530, upload-time = "2026-02-10T15:23:53.817Z" }, +] + +[[package]] +name = "mkdocs-get-deps" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mergedeep" }, + { name = "platformdirs" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239, upload-time = "2023-11-20T17:51:09.981Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521, upload-time = "2023-11-20T17:51:08.587Z" }, +] + +[[package]] +name = "mkdocs-material" +version = "9.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel" }, + { name = "backrefs" }, + { name = "colorama" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "mkdocs" }, + { name = "mkdocs-material-extensions" }, + { name = "paginate" }, + { name = "pygments" }, + { name = "pymdown-extensions" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/b4/f900fcb8e6f510241e334ca401eddcb61ed880fb6572f7f32e4228472ca1/mkdocs_material-9.7.3.tar.gz", hash = "sha256:e5f0a18319699da7e78c35e4a8df7e93537a888660f61a86bd773a7134798f22", size = 4097748, upload-time = "2026-02-24T12:06:22.646Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/1b/16ad0193079bb8a15aa1d2620813a9cd15b18de150a4ea1b2c607fb4c74d/mkdocs_material-9.7.3-py3-none-any.whl", hash = "sha256:37ebf7b4788c992203faf2e71900be3c197c70a4be9b0d72aed537b08a91dd9d", size = 9305078, upload-time = "2026-02-24T12:06:19.155Z" }, +] + +[[package]] +name = "mkdocs-material-extensions" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847, upload-time = "2023-11-22T19:09:45.208Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" }, +] + +[[package]] +name = "mkdocstrings" +version = "1.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mkdocs" }, + { name = "mkdocs-autorefs" }, + { name = "pymdown-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/62/0dfc5719514115bf1781f44b1d7f2a0923fcc01e9c5d7990e48a05c9ae5d/mkdocstrings-1.0.3.tar.gz", hash = "sha256:ab670f55040722b49bb45865b2e93b824450fb4aef638b00d7acb493a9020434", size = 100946, upload-time = "2026-02-07T14:31:40.973Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl", hash = "sha256:0d66d18430c2201dc7fe85134277382baaa15e6b30979f3f3bdbabd6dbdb6046", size = 35523, upload-time = "2026-02-07T14:31:39.27Z" }, +] + +[package.optional-dependencies] +python = [ + { name = "mkdocstrings-python" }, +] + +[[package]] +name = "mkdocstrings-python" +version = "2.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "griffelib" }, + { name = "mkdocs-autorefs" }, + { name = "mkdocstrings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/29/33/c225eaf898634bdda489a6766fc35d1683c640bffe0e0acd10646b13536d/mkdocstrings_python-2.0.3.tar.gz", hash = "sha256:c518632751cc869439b31c9d3177678ad2bfa5c21b79b863956ad68fc92c13b8", size = 199083, upload-time = "2026-02-20T10:38:36.368Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl", hash = "sha256:0b83513478bdfd803ff05aa43e9b1fca9dd22bcd9471f09ca6257f009bc5ee12", size = 104779, upload-time = "2026-02-20T10:38:34.517Z" }, +] + [[package]] name = "numpy" version = "2.4.2" @@ -343,6 +609,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] +[[package]] +name = "paginate" +version = "0.5.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252, upload-time = "2024-08-25T14:17:24.139Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" }, +] + [[package]] name = "pandas" version = "3.0.1" @@ -395,6 +670,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/68/b0/34937815889fa982613775e4b97fddd13250f11012d769949c5465af2150/pandas-3.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:108dd1790337a494aa80e38def654ca3f0968cf4f362c85f44c15e471667102d", size = 9452085, upload-time = "2026-02-17T22:20:14.331Z" }, ] +[[package]] +name = "pathspec" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.9.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394, upload-time = "2026-02-16T03:56:10.574Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/31/05e764397056194206169869b50cf2fee4dbbbc71b344705b9c0d878d4d8/platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd", size = 21168, upload-time = "2026-02-16T03:56:08.891Z" }, +] + [[package]] name = "pluggy" version = "1.6.0" @@ -452,6 +745,19 @@ crypto = [ { name = "cryptography" }, ] +[[package]] +name = "pymdown-extensions" +version = "10.21" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/63/06673d1eb6d8f83c0ea1f677d770e12565fb516928b4109c9e2055656a9e/pymdown_extensions-10.21.tar.gz", hash = "sha256:39f4a020f40773f6b2ff31d2cd2546c2c04d0a6498c31d9c688d2be07e1767d5", size = 853363, upload-time = "2026-02-15T20:44:06.748Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl", hash = "sha256:91b879f9f864d49794c2d9534372b10150e6141096c3908a455e45ca72ad9d3f", size = 268877, upload-time = "2026-02-15T20:44:05.464Z" }, +] + [[package]] name = "pynacl" version = "1.6.2" @@ -524,6 +830,64 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, ] +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "pyyaml-env-tag" +version = "1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, +] + [[package]] name = "requests" version = "2.32.5" @@ -574,3 +938,27 @@ sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6 wheels = [ { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, ] + +[[package]] +name = "watchdog" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" }, + { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" }, + { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" }, + { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" }, + { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, +] From 6b5a4062da922c4a440a07879fe40d5ee2f0cba9 Mon Sep 17 00:00:00 2001 From: scott-yj-yang Date: Fri, 27 Feb 2026 15:58:26 -0800 Subject: [PATCH 12/24] docs: add MkDocs homepage and getting-started guides Co-Authored-By: Claude Opus 4.6 --- docs/getting-started/authentication.md | 46 ++++++++++++++++++++ docs/getting-started/installation.md | 28 +++++++++++++ docs/getting-started/quickstart.md | 58 ++++++++++++++++++++++++++ docs/index.md | 34 +++++++++++++++ 4 files changed, 166 insertions(+) create mode 100644 docs/getting-started/authentication.md create mode 100644 docs/getting-started/installation.md create mode 100644 docs/getting-started/quickstart.md create mode 100644 docs/index.md diff --git a/docs/getting-started/authentication.md b/docs/getting-started/authentication.md new file mode 100644 index 0000000..74eaaf1 --- /dev/null +++ b/docs/getting-started/authentication.md @@ -0,0 +1,46 @@ +# Authentication + +CanvasGroupy requires API tokens for both Canvas LMS and GitHub. These are stored in a JSON credentials file. + +## Credentials file + +Create a `credentials.json` file: + +```json +{ + "Canvas Token": "your-canvas-api-token", + "GitHub Token": "your-github-personal-access-token" +} +``` + +!!! warning "Keep your credentials safe" + Never commit `credentials.json` to version control. Add it to your `.gitignore`. + +## Canvas API Token + +1. Log in to your Canvas instance (e.g., `https://canvas.ucsd.edu`) +2. Go to **Account > Settings** +3. Scroll to **Approved Integrations** +4. Click **+ New Access Token** +5. Give it a purpose (e.g., "CanvasGroupy") and generate +6. Copy the token into your `credentials.json` + +## GitHub Personal Access Token + +1. Go to [GitHub Settings > Developer settings > Personal access tokens](https://github.com/settings/tokens) +2. Click **Generate new token (classic)** +3. Select scopes: `repo`, `admin:org`, `read:org` +4. Generate and copy the token into your `credentials.json` + +## Usage + +```python +from CanvasGroupy import CanvasGroup, GitHubGroup + +# Authenticate Canvas +cg = CanvasGroup(credentials_fp="credentials.json", + API_URL="https://canvas.ucsd.edu") + +# Authenticate GitHub +ghg = GitHubGroup(credentials_fp="credentials.json", org="MyOrg") +``` diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md new file mode 100644 index 0000000..d80efc5 --- /dev/null +++ b/docs/getting-started/installation.md @@ -0,0 +1,28 @@ +# Installation + +## Requirements + +- Python 3.12 or higher + +## Install from PyPI + +```bash +pip install CanvasGroupy +``` + +## Install from source + +```bash +git clone https://github.com/FleischerResearchLab/CanvasGroupy.git +cd CanvasGroupy +pip install -e . +``` + +## Dependencies + +CanvasGroupy depends on: + +- [canvasapi](https://github.com/ucfopen/canvasapi) — Python wrapper for Canvas LMS REST API +- [PyGithub](https://github.com/PyGithub/PyGithub) — Python wrapper for GitHub REST API +- [pandas](https://pandas.pydata.org/) — Data manipulation +- [numpy](https://numpy.org/) — Numerical computing diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md new file mode 100644 index 0000000..5366617 --- /dev/null +++ b/docs/getting-started/quickstart.md @@ -0,0 +1,58 @@ +# Quick Start + +This guide walks through the core workflow: loading groups, creating them on Canvas, and setting up GitHub repositories. + +## Prerequisites + +- [Install CanvasGroupy](installation.md) +- [Set up authentication](authentication.md) +- A CSV file with group assignments (columns: `group_name`, `student_id`) + +## Step 1: Prepare your group roster + +Create a CSV file `groups.csv`: + +```csv +group_name,student_id +Team_Alpha,student1 +Team_Alpha,student2 +Team_Alpha,student3 +Team_Beta,student4 +Team_Beta,student5 +Team_Beta,student6 +``` + +The `student_id` column should contain Canvas SIS Login IDs (email prefix before `@`). + +## Step 2: Create groups on Canvas + +```python +from CanvasGroupy import CanvasGroup, GitHubGroup, AssignGroup + +# Authenticate and select course +cg = CanvasGroup(credentials_fp="credentials.json", + API_URL="https://canvas.ucsd.edu", + course_id=12345) + +# Create a group category +cg.create_group_category({"name": "Project Groups"}) + +# Load groups and assign +ghg = GitHubGroup(credentials_fp="credentials.json", org="MyOrg") +ag = AssignGroup(ghg=ghg, cg=cg) +ag.load_groups("groups.csv") +ag.create_canvas_group(in_group_category="Project Groups") +``` + +## Step 3: Create GitHub repositories + +```python +# Fetch GitHub usernames from a Canvas quiz +ag.create_github_group(username_quiz_id=67890) +``` + +## Next steps + +- [Canvas Groups Tutorial](../tutorials/canvas-groups.md) — detailed Canvas group management +- [GitHub Repos Tutorial](../tutorials/github-repos.md) — repo templates, permissions, teams +- [Grading Workflow](../tutorials/grading.md) — automate grading between GitHub and Canvas diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..eeca46c --- /dev/null +++ b/docs/index.md @@ -0,0 +1,34 @@ +# CanvasGroupy + +> Automate project group management across Canvas LMS and GitHub. + +CanvasGroupy helps instructors manage project groups across **Canvas LMS** and **GitHub**. Create student groups on Canvas, set up GitHub repositories with the right permissions, and automate grading workflows — all from Python. + +## Features + +- **Canvas Group Management** — Create group categories, assign students to groups, send notifications +- **GitHub Repository Setup** — Create repos from templates, add collaborators, manage team permissions +- **Group Assignment** — Load group rosters from CSV or DataFrame and sync to Canvas + GitHub +- **Grading Workflow** — Parse scores from GitHub issue templates and post grades back to Canvas +- **Batch Feedback** — Release feedback to all groups via GitHub Issues from markdown templates + +## Quick Start + +```bash +pip install CanvasGroupy +``` + +```python +from CanvasGroupy import CanvasGroup, GitHubGroup, AssignGroup + +# Authenticate +cg = CanvasGroup(credentials_fp="credentials.json", course_id=12345) +ghg = GitHubGroup(credentials_fp="credentials.json", org="MyOrg") + +# Load groups and create on Canvas +ag = AssignGroup(ghg=ghg, cg=cg) +ag.load_groups("groups.csv") +ag.create_canvas_group(in_group_category="Project Groups") +``` + +See the [Getting Started](getting-started/installation.md) guide for full setup instructions. From aca794973e099acac4843b2978397b046fa22bad Mon Sep 17 00:00:00 2001 From: scott-yj-yang Date: Fri, 27 Feb 2026 15:59:17 -0800 Subject: [PATCH 13/24] docs: add tutorial pages for Canvas, GitHub, grading, and feedback Co-Authored-By: Claude Opus 4.6 --- docs/tutorials/canvas-groups.md | 56 ++++++++++++++++++++++++++++ docs/tutorials/feedback.md | 49 ++++++++++++++++++++++++ docs/tutorials/github-repos.md | 66 +++++++++++++++++++++++++++++++++ docs/tutorials/grading.md | 65 ++++++++++++++++++++++++++++++++ 4 files changed, 236 insertions(+) create mode 100644 docs/tutorials/canvas-groups.md create mode 100644 docs/tutorials/feedback.md create mode 100644 docs/tutorials/github-repos.md create mode 100644 docs/tutorials/grading.md diff --git a/docs/tutorials/canvas-groups.md b/docs/tutorials/canvas-groups.md new file mode 100644 index 0000000..13736e8 --- /dev/null +++ b/docs/tutorials/canvas-groups.md @@ -0,0 +1,56 @@ +# Canvas Group Management + +This tutorial covers creating and managing groups on Canvas LMS. + +## Initialize and authenticate + +```python +from CanvasGroupy import CanvasGroup + +cg = CanvasGroup( + credentials_fp="credentials.json", + API_URL="https://canvas.ucsd.edu", + course_id=12345, +) +``` + +## Create a group category + +Group categories (called "Group Sets" in Canvas UI) organize groups: + +```python +cg.create_group_category({"name": "Final Project Groups"}) +``` + +## Assign students to groups + +```python +cg.assign_canvas_group( + group_name="Team Alpha", + group_members=["student1", "student2", "student3"], + in_group_category="Final Project Groups", +) +``` + +## View existing groups + +```python +# List all group categories +categories = cg.get_group_categories() + +# Set and view groups in a category +cg.set_group_category("Final Project Groups") +groups = cg.get_groups() +# Returns: {"Team Alpha": ["student1", "student2", ...], ...} +``` + +## Send notifications + +```python +student_canvas_id = cg.email_to_canvas_id["student1"] +cg.create_conversation( + recipients=student_canvas_id, + subject="Welcome to your project group", + body="You have been assigned to Team Alpha.", +) +``` diff --git a/docs/tutorials/feedback.md b/docs/tutorials/feedback.md new file mode 100644 index 0000000..5f0913b --- /dev/null +++ b/docs/tutorials/feedback.md @@ -0,0 +1,49 @@ +# Feedback Templates + +CanvasGroupy supports releasing batch feedback to student groups via GitHub Issues created from markdown templates. + +## Template format + +Feedback templates are markdown files where the first line (starting with `# `) becomes the issue title. Example: + +```markdown +# Project Checkpoint Feedback + +Total: + +## Feedback + +| Category | Full Point | Your Score | Comment | +|-------------------|------------|------------|---------| +| Abstract | 0.5 | | | +| Background | 0.5 | | | +| Problem Statement | 0.5 | | | + +Score = ... +``` + +## Create feedback directories + +```python +repo = ghg.get_repo("MyOrg/team-alpha") +ghg.create_feedback_dir( + repo=repo, + template_fp="feedback_templates/", + destination="feedback", +) +``` + +This creates `feedback//` with copies of each template for grading. + +## Release feedback + +After filling in the feedback files: + +```python +ghg.release_feedback( + md_filename="checkpoint_feedback.md", + feedback_dir="feedback", +) +``` + +This creates a GitHub Issue in each group's repository from the corresponding feedback file. diff --git a/docs/tutorials/github-repos.md b/docs/tutorials/github-repos.md new file mode 100644 index 0000000..c8b4788 --- /dev/null +++ b/docs/tutorials/github-repos.md @@ -0,0 +1,66 @@ +# GitHub Repository Setup + +This tutorial covers creating and managing GitHub repositories for project groups. + +## Initialize and authenticate + +```python +from CanvasGroupy import GitHubGroup + +ghg = GitHubGroup(credentials_fp="credentials.json", org="MyOrg") +``` + +## Create a repository + +```python +# Empty repository +repo = ghg.create_repo("team-alpha-project", private=True) + +# From a template +repo = ghg.create_repo( + "team-alpha-project", + repo_template="MyOrg/project-template", + private=True, + description="Final project repository for Team Alpha", +) +``` + +## Create a group repository with collaborators + +```python +repo = ghg.create_group_repo( + repo_name="team-alpha", + collaborators=["github_user1", "github_user2"], + permission="write", + repo_template="MyOrg/project-template", + private=True, + description="Team Alpha Project Repository", + rename_files={ + "Checkpoint_groupXXX.ipynb": "Checkpoint_team-alpha.ipynb", + "FinalProject_groupXXX.ipynb": "FinalProject_team-alpha.ipynb", + }, + team_slug="instructors", + team_permission="admin", +) +``` + +## Manage collaborators + +```python +repo = ghg.get_repo("MyOrg/team-alpha") + +# Add +ghg.add_collaborator(repo, "new_student", "write") + +# Remove +ghg.remove_collaborator(repo, "dropped_student") + +# Resend pending invitations +ghg.resend_invitations(repo) +``` + +## Manage teams + +```python +ghg.add_team(repo, team_slug="graders", permission="push") +``` diff --git a/docs/tutorials/grading.md b/docs/tutorials/grading.md new file mode 100644 index 0000000..2c7d911 --- /dev/null +++ b/docs/tutorials/grading.md @@ -0,0 +1,65 @@ +# Grading Workflow + +Automate grading between GitHub Issues and Canvas assignments. + +## Overview + +The grading workflow: + +1. Instructors/TAs fill in score templates in GitHub Issues +2. CanvasGroupy parses scores from issue markdown +3. Scores are posted back to Canvas assignments + +## Setup + +```python +from CanvasGroupy import Grading, GitHubGroup, CanvasGroup + +ghg = GitHubGroup(credentials_fp="credentials.json", org="MyOrg") +cg = CanvasGroup( + credentials_fp="credentials.json", + API_URL="https://canvas.ucsd.edu", + course_id=12345, + group_category="Final Project Groups", +) +grading = Grading(ghg=ghg, cg=cg) +``` + +## Create feedback issues from templates + +```python +repo = ghg.get_repo("MyOrg/team-alpha") +grading.create_issue_from_md(repo, "feedback/checkpoint_feedback.md") +``` + +## Grade a project component + +```python +# Dry run (prints scores without posting) +grading.grade_project( + repo=repo, + component="checkpoint", + assignment_id=67890, + canvas_group_category="Final Project Groups", + post=False, +) + +# Post grades to Canvas +grading.grade_project( + repo=repo, + component="checkpoint", + assignment_id=67890, + canvas_group_category="Final Project Groups", + post=True, +) +``` + +## Score format in GitHub Issues + +In the issue body, scores are parsed from lines like: + +``` +Score = 8.5 +``` + +Lines with `[comment]` are ignored. The `Grading` class looks for the first `Score = ` line that isn't a comment. From 2ccc613b91ddb131646249e7d667a6d40593c316 Mon Sep 17 00:00:00 2001 From: scott-yj-yang Date: Fri, 27 Feb 2026 16:01:11 -0800 Subject: [PATCH 14/24] docs: add auto-generated API reference pages via mkdocstrings Co-Authored-By: Claude Opus 4.6 --- docs/api/assign.md | 3 +++ docs/api/canvas.md | 3 +++ docs/api/github.md | 3 +++ docs/api/grading.md | 3 +++ mkdocs.yml | 2 ++ 5 files changed, 14 insertions(+) create mode 100644 docs/api/assign.md create mode 100644 docs/api/canvas.md create mode 100644 docs/api/github.md create mode 100644 docs/api/grading.md diff --git a/docs/api/assign.md b/docs/api/assign.md new file mode 100644 index 0000000..babbfc4 --- /dev/null +++ b/docs/api/assign.md @@ -0,0 +1,3 @@ +# AssignGroup + +::: CanvasGroupy.assign.AssignGroup diff --git a/docs/api/canvas.md b/docs/api/canvas.md new file mode 100644 index 0000000..b6849ba --- /dev/null +++ b/docs/api/canvas.md @@ -0,0 +1,3 @@ +# CanvasGroup + +::: CanvasGroupy.canvas.CanvasGroup diff --git a/docs/api/github.md b/docs/api/github.md new file mode 100644 index 0000000..913b856 --- /dev/null +++ b/docs/api/github.md @@ -0,0 +1,3 @@ +# GitHubGroup + +::: CanvasGroupy.github.GitHubGroup diff --git a/docs/api/grading.md b/docs/api/grading.md new file mode 100644 index 0000000..9616f2d --- /dev/null +++ b/docs/api/grading.md @@ -0,0 +1,3 @@ +# Grading + +::: CanvasGroupy.grading.Grading diff --git a/mkdocs.yml b/mkdocs.yml index bb54657..42dfb1a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -34,6 +34,8 @@ plugins: show_root_heading: true heading_level: 2 docstring_style: google + docstring_options: + warn_missing_types: false markdown_extensions: - admonition From 14af1e882f70b82add0710502b17eac895d971f0 Mon Sep 17 00:00:00 2001 From: scott-yj-yang Date: Fri, 27 Feb 2026 16:02:22 -0800 Subject: [PATCH 15/24] ci: switch documentation deployment from Quarto to MkDocs Co-Authored-By: Claude Opus 4.6 --- .github/workflows/deploy.yaml | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index c18b1a5..5715564 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -1,4 +1,4 @@ -name: Deploy to GitHub Pages +name: Deploy Documentation permissions: contents: write @@ -14,15 +14,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: quarto-dev/quarto-actions/setup@v2 - name: Install uv - uses: astral-sh/setup-uv@v4 - - name: Install Python dependencies - run: uv sync --all-extras - - name: Render Quarto site - run: cd nbs && uv run quarto render + uses: astral-sh/setup-uv@v5 + - name: Install dependencies + run: uv sync --extra docs - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v4 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./nbs/_docs + run: uv run mkdocs gh-deploy --force From 61e6283ac054d4199983a834a8f71161415a3d6e Mon Sep 17 00:00:00 2001 From: scott-yj-yang Date: Fri, 27 Feb 2026 16:03:33 -0800 Subject: [PATCH 16/24] chore: remove nbs/, dev_nbs/ directories and Quarto config Co-Authored-By: Claude Opus 4.6 --- .gitignore | 4 +- CanvasGroupy/canvas.py | 2 +- dev_nbs/canvas_example.ipynb | 471 ------ dev_nbs/check_group.ipynb | 165 --- dev_nbs/github_example.ipynb | 266 ---- dev_nbs/imgs/quiz_example.png | Bin 86044 -> 0 bytes dev_nbs/imgs/raw_question.png | Bin 63676 -> 0 bytes nbs/.gitignore | 1 - nbs/_quarto.yml | 32 - nbs/api/01_GroupEng_assign.ipynb | 286 ---- nbs/api/02_github.ipynb | 985 ------------- nbs/api/03_canvas.ipynb | 617 -------- nbs/api/04_project_grading.ipynb | 166 --- nbs/api/index.qmd | 11 - nbs/credentials.json | 4 - nbs/data/195_class.csv | 6 - nbs/data/195_group_specification.groupeng | 23 - .../195_group_specification_classlist.csv | 7 - .../195_group_specification_details.csv | 9 - .../195_group_specification_groups.csv | 2 - .../195_group_specification_groups.txt | 7 - .../195_group_specification_statistics.txt | 23 - nbs/dir.txt | 1 - nbs/feedback_template/checkpoint_feedback.md | 40 - .../final_project_feedback.md | 37 - nbs/feedback_template/proposal_feedback.md | 47 - nbs/index.ipynb | 141 -- nbs/styles.css | 37 - nbs/tutorials/Authentications.ipynb | 84 -- ...reate GitHub Group from Canvas Group.ipynb | 1261 ----------------- nbs/tutorials/create template issues.ipynb | 108 -- test_nbs/grab_groups.ipynb | 136 -- test_nbs/grading.ipynb | 199 --- 33 files changed, 2 insertions(+), 5176 deletions(-) delete mode 100644 dev_nbs/canvas_example.ipynb delete mode 100644 dev_nbs/check_group.ipynb delete mode 100644 dev_nbs/github_example.ipynb delete mode 100644 dev_nbs/imgs/quiz_example.png delete mode 100644 dev_nbs/imgs/raw_question.png delete mode 100644 nbs/.gitignore delete mode 100644 nbs/_quarto.yml delete mode 100644 nbs/api/01_GroupEng_assign.ipynb delete mode 100644 nbs/api/02_github.ipynb delete mode 100644 nbs/api/03_canvas.ipynb delete mode 100644 nbs/api/04_project_grading.ipynb delete mode 100644 nbs/api/index.qmd delete mode 100644 nbs/credentials.json delete mode 100644 nbs/data/195_class.csv delete mode 100644 nbs/data/195_group_specification.groupeng delete mode 100644 nbs/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_classlist.csv delete mode 100644 nbs/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_details.csv delete mode 100644 nbs/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_groups.csv delete mode 100644 nbs/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_groups.txt delete mode 100644 nbs/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_statistics.txt delete mode 100644 nbs/dir.txt delete mode 100644 nbs/feedback_template/checkpoint_feedback.md delete mode 100644 nbs/feedback_template/final_project_feedback.md delete mode 100644 nbs/feedback_template/proposal_feedback.md delete mode 100644 nbs/index.ipynb delete mode 100644 nbs/styles.css delete mode 100644 nbs/tutorials/Authentications.ipynb delete mode 100644 nbs/tutorials/Create GitHub Group from Canvas Group.ipynb delete mode 100644 nbs/tutorials/create template issues.ipynb delete mode 100644 test_nbs/grab_groups.ipynb delete mode 100644 test_nbs/grading.ipynb diff --git a/.gitignore b/.gitignore index 375860d..98a3b02 100644 --- a/.gitignore +++ b/.gitignore @@ -130,10 +130,8 @@ dmypy.json # additional files .DS_Store -nbs/groups_example* -nbs/api/github_username.csv .idea -# nbdev build artifacts (removed) +# Legacy build artifacts _proc/ _docs/ diff --git a/CanvasGroupy/canvas.py b/CanvasGroupy/canvas.py index e97a2d5..6b3effc 100644 --- a/CanvasGroupy/canvas.py +++ b/CanvasGroupy/canvas.py @@ -40,7 +40,7 @@ class CanvasGroup(): """ def __init__(self, - credentials_fp = "", # credential file path. [Template of the credentials.json](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/nbs/credentials.json) + credentials_fp = "", # credential file path. See docs/getting-started/authentication.md for the template. API_URL="https://canvas.ucsd.edu", # the domain name of canvas course_id="", # Course ID, can be found in the course url group_category="", # target group category (set) of interests diff --git a/dev_nbs/canvas_example.ipynb b/dev_nbs/canvas_example.ipynb deleted file mode 100644 index 058660d..0000000 --- a/dev_nbs/canvas_example.ipynb +++ /dev/null @@ -1,471 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "aa249893", - "metadata": {}, - "source": [ - "# Canvas Group Example\n", - "\n", - "> Examples of a workflow of creating canvas groups." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "52894ace", - "metadata": { - "ExecuteTime": { - "end_time": "2023-05-17T05:39:11.082932Z", - "start_time": "2023-05-17T05:39:10.972733Z" - } - }, - "outputs": [], - "source": [ - "#| hide\n", - "from nbdev.showdoc import *" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "323925c9", - "metadata": { - "ExecuteTime": { - "end_time": "2023-05-17T05:39:13.837810Z", - "start_time": "2023-05-17T05:39:12.544347Z" - } - }, - "outputs": [], - "source": [ - "# import module\n", - "from CanvasGroupy.canvas import CanvasGroup" - ] - }, - { - "cell_type": "markdown", - "id": "9d7876db", - "metadata": {}, - "source": [ - "# Canvas Authentication" - ] - }, - { - "cell_type": "markdown", - "id": "3794dbf8", - "metadata": {}, - "source": [ - "All the credentials should be stored at a json file in the following format." - ] - }, - { - "cell_type": "markdown", - "id": "906998cf", - "metadata": {}, - "source": [ - "If `API_KEY` and/or `course_id` was passed into the initializer, it will call the following methods to load the relevant information about your canvas and your canvas course." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "4cc5123f", - "metadata": { - "ExecuteTime": { - "end_time": "2023-05-17T05:40:10.769511Z", - "start_time": "2023-05-17T05:40:01.458538Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001B[92mAuthorization Successful!\u001B[0m\n", - "Course Set: \u001B[92m COGS 118A - Supvr/Mach Learning Algorithms - Fleischer [SP23] \u001B[0m\n", - "Getting List of Users... This might take a while...\n", - "Users Fetch Complete! The course has \u001B[94m160\u001B[0m students.\n" - ] - } - ], - "source": [ - "credentials_fp = \"../credentials.json\"\n", - "credentials_fp = \"../../credentials.json\" #| hide_line\n", - "\n", - "# instansiate a new Canvas Group Object \n", - "# if your class size is large, it will take around 2 minutes to grab all student info.\n", - "API_URL = \"https://canvas.ucsd.edu/\"\n", - "\n", - "# your canvas course id, found on the top of canvas url\n", - "course_id = ...\n", - "course_id = 45059 #| hide_line\n", - "cg = CanvasGroup(credentials_fp,\n", - " API_URL,\n", - " course_id=course_id,\n", - " verbosity=1)" - ] - }, - { - "cell_type": "markdown", - "id": "3ff7a6d8", - "metadata": {}, - "source": [ - "# Create / Assign Group in One Call" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9bbb6379", - "metadata": {}, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#LNone){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.assign_canvas_group\n", - "\n", - "> CanvasGroup.assign_canvas_group (group_name:str,\n", - "> group_members:[],\n", - "> in_group_category:str)\n", - "\n", - "Create new groups and assign group member into the class in the `self.group_category`\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| group_name | str | group name, display on canvas |\n", - "| group_members | [] | list of group member's SIS Login |\n", - "| in_group_category | str | specify which group category the group belongs to |\n", - "| **Returns** | **(, [])** | **list of unsuccessful join** |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#LNone){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.assign_canvas_group\n", - "\n", - "> CanvasGroup.assign_canvas_group (group_name:str,\n", - "> group_members:[],\n", - "> in_group_category:str)\n", - "\n", - "Create new groups and assign group member into the class in the `self.group_category`\n", - "\n", - "| | **Type** | **Details** |\n", - "| -- | -------- | ----------- |\n", - "| group_name | str | group name, display on canvas |\n", - "| group_members | [] | list of group member's SIS Login |\n", - "| in_group_category | str | specify which group category the group belongs to |\n", - "| **Returns** | **(, [])** | **list of unsuccessful join** |" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "show_doc(CanvasGroup.assign_canvas_group)" - ] - }, - { - "cell_type": "markdown", - "id": "45747c0f", - "metadata": {}, - "source": [ - "In this way, we could directly create canvas group by specifying the group name, members info, and the group category directly." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3453345d", - "metadata": {}, - "outputs": [], - "source": [ - "cat = cg.create_group_category(\"Test\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c5f5a9ce", - "metadata": {}, - "outputs": [ - { - "ename": "KeyError", - "evalue": "'Test did not found in the group categories. Try to create one with CanvasGroup.create_group_category'", - "output_type": "error", - "traceback": [ - "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", - "\u001B[0;31mKeyError\u001B[0m Traceback (most recent call last)", - "File \u001B[0;32m~/Desktop/SP23_Working/Group_Eng/CanvasGroupy/CanvasGroupy/canvas.py:102\u001B[0m, in \u001B[0;36mCanvasGroup.set_group_category\u001B[0;34m(self, category_name)\u001B[0m\n\u001B[1;32m 101\u001B[0m \u001B[38;5;28;01mtry\u001B[39;00m:\n\u001B[0;32m--> 102\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mgroup_category \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mgroup_categories\u001B[49m\u001B[43m[\u001B[49m\u001B[43mcategory_name\u001B[49m\u001B[43m]\u001B[49m\n\u001B[1;32m 103\u001B[0m \u001B[38;5;28;01mexcept\u001B[39;00m \u001B[38;5;167;01mKeyError\u001B[39;00m:\n", - "\u001B[0;31mKeyError\u001B[0m: 'Test'", - "\nDuring handling of the above exception, another exception occurred:\n", - "\u001B[0;31mKeyError\u001B[0m Traceback (most recent call last)", - "Input \u001B[0;32mIn [5]\u001B[0m, in \u001B[0;36m\u001B[0;34m()\u001B[0m\n\u001B[1;32m 1\u001B[0m member1 \u001B[38;5;241m=\u001B[39m \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124memail\u001B[39m\u001B[38;5;124m\"\u001B[39m\n\u001B[1;32m 2\u001B[0m member1 \u001B[38;5;241m=\u001B[39m \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mgrader-cogs118a-01\u001B[39m\u001B[38;5;124m\"\u001B[39m \u001B[38;5;66;03m#| hide_line\u001B[39;00m\n\u001B[0;32m----> 3\u001B[0m group, unsuccessful \u001B[38;5;241m=\u001B[39m \u001B[43mcg\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43massign_canvas_group\u001B[49m\u001B[43m(\u001B[49m\n\u001B[1;32m 4\u001B[0m \u001B[43m \u001B[49m\u001B[43mgroup_name\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[38;5;124;43mTestGroup000\u001B[39;49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\n\u001B[1;32m 5\u001B[0m \u001B[43m \u001B[49m\u001B[43mgroup_members\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43m[\u001B[49m\u001B[43mmember1\u001B[49m\u001B[43m]\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\n\u001B[1;32m 6\u001B[0m \u001B[43m \u001B[49m\u001B[43min_group_category\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[38;5;124;43mTest\u001B[39;49m\u001B[38;5;124;43m\"\u001B[39;49m\n\u001B[1;32m 7\u001B[0m \u001B[43m)\u001B[49m\n\u001B[1;32m 9\u001B[0m \u001B[38;5;28;01massert\u001B[39;00m \u001B[38;5;28mlen\u001B[39m(unsuccessful)\u001B[38;5;241m==\u001B[39m\u001B[38;5;241m0\u001B[39m\n", - "File \u001B[0;32m~/Desktop/SP23_Working/Group_Eng/CanvasGroupy/CanvasGroupy/canvas.py:278\u001B[0m, in \u001B[0;36mCanvasGroup.assign_canvas_group\u001B[0;34m(self, group_name, group_members, in_group_category)\u001B[0m\n\u001B[1;32m 272\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21massign_canvas_group\u001B[39m(\u001B[38;5;28mself\u001B[39m,\n\u001B[1;32m 273\u001B[0m group_name: \u001B[38;5;28mstr\u001B[39m, \u001B[38;5;66;03m# group name, display on canvas\u001B[39;00m\n\u001B[1;32m 274\u001B[0m group_members:[\u001B[38;5;28mstr\u001B[39m], \u001B[38;5;66;03m# list of group member's SIS Login\u001B[39;00m\n\u001B[1;32m 275\u001B[0m in_group_category: \u001B[38;5;28mstr\u001B[39m, \u001B[38;5;66;03m# specify which group category the group belongs to\u001B[39;00m\n\u001B[1;32m 276\u001B[0m ) \u001B[38;5;241m-\u001B[39m\u001B[38;5;241m>\u001B[39m (canvasapi\u001B[38;5;241m.\u001B[39mgroup\u001B[38;5;241m.\u001B[39mGroup, [\u001B[38;5;28mstr\u001B[39m]): \u001B[38;5;66;03m# list of unsuccessful join\u001B[39;00m\n\u001B[1;32m 277\u001B[0m \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mCreate new groups and assign group member into the class in the `self.group_category`\u001B[39m\u001B[38;5;124m\"\u001B[39m\n\u001B[0;32m--> 278\u001B[0m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mset_group_category\u001B[49m\u001B[43m(\u001B[49m\u001B[43min_group_category\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 279\u001B[0m group \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mcreate_group({\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mname\u001B[39m\u001B[38;5;124m\"\u001B[39m: group_name})\n\u001B[1;32m 280\u001B[0m unsuccessful_join \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mjoin_canvas_group(group, group_members)\n", - "File \u001B[0;32m~/Desktop/SP23_Working/Group_Eng/CanvasGroupy/CanvasGroupy/canvas.py:104\u001B[0m, in \u001B[0;36mCanvasGroup.set_group_category\u001B[0;34m(self, category_name)\u001B[0m\n\u001B[1;32m 102\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mgroup_category \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mgroup_categories[category_name]\n\u001B[1;32m 103\u001B[0m \u001B[38;5;28;01mexcept\u001B[39;00m \u001B[38;5;167;01mKeyError\u001B[39;00m:\n\u001B[0;32m--> 104\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m \u001B[38;5;167;01mKeyError\u001B[39;00m(\u001B[38;5;124mf\u001B[39m\u001B[38;5;124m\"\u001B[39m\u001B[38;5;132;01m{\u001B[39;00mcategory_name\u001B[38;5;132;01m}\u001B[39;00m\u001B[38;5;124m did not found in the group categories. \u001B[39m\u001B[38;5;124m\"\u001B[39m\n\u001B[1;32m 105\u001B[0m \u001B[38;5;124mf\u001B[39m\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mTry to create one with CanvasGroup.create_group_category\u001B[39m\u001B[38;5;124m\"\u001B[39m)\n\u001B[1;32m 106\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mgroup_category\n", - "\u001B[0;31mKeyError\u001B[0m: 'Test did not found in the group categories. Try to create one with CanvasGroup.create_group_category'" - ] - } - ], - "source": [ - "cat = cg.create_group_category(\"Test\")\n", - "member1 = \"email\"\n", - "member1 = \"grader-cogs118a-01\" #| hide_line\n", - "group, unsuccessful = cg.assign_canvas_group(\n", - " group_name=\"TestGroup000\", \n", - " group_members=[member1], \n", - " in_group_category=\"Test\"\n", - ")\n", - "\n", - "assert len(unsuccessful)==0" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bbd463ec", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Group(_requester=, is_public=False, id=122853, name=TestGroup000, created_at=2023-04-27T23:28:55Z, created_at_date=2023-04-27 23:28:55+00:00, max_membership=None, join_level=invitation_only, group_category_id=16281, description=None, members_count=1, storage_quota_mb=1024, storage_quota_mb_date=1024-01-01 00:00:00+00:00, context_type=Course, course_id=45059, avatar_url=None, role=None, leader=None, has_submission=False, concluded=False)" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#| hide\n", - "group.delete()" - ] - }, - { - "cell_type": "markdown", - "id": "e1f9f60c", - "metadata": {}, - "source": [ - "Note that it will throw a Key Error if the target `in_group_category` did not exist in the course." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "181b1be6", - "metadata": {}, - "outputs": [ - { - "ename": "KeyError", - "evalue": "'Group Project 2 did not found in the group categories. Try to create one with CanvasGroup.create_group_category'", - "output_type": "error", - "traceback": [ - "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", - "\u001B[0;31mKeyError\u001B[0m Traceback (most recent call last)", - "Cell \u001B[0;32mIn[51], line 73\u001B[0m, in \u001B[0;36mCanvasGroup.set_group_category\u001B[0;34m(self, category_name)\u001B[0m\n\u001B[1;32m 72\u001B[0m \u001B[38;5;28;01mtry\u001B[39;00m:\n\u001B[0;32m---> 73\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mgroup_category \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mgroup_categories\u001B[49m\u001B[43m[\u001B[49m\u001B[43mcategory_name\u001B[49m\u001B[43m]\u001B[49m\n\u001B[1;32m 74\u001B[0m \u001B[38;5;28;01mexcept\u001B[39;00m \u001B[38;5;167;01mKeyError\u001B[39;00m:\n", - "\u001B[0;31mKeyError\u001B[0m: 'Group Project 2'", - "\nDuring handling of the above exception, another exception occurred:\n", - "\u001B[0;31mKeyError\u001B[0m Traceback (most recent call last)", - "Cell \u001B[0;32mIn[58], line 1\u001B[0m\n\u001B[0;32m----> 1\u001B[0m \u001B[43mcg\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43massign_canvas_group\u001B[49m\u001B[43m(\u001B[49m\n\u001B[1;32m 2\u001B[0m \u001B[43m \u001B[49m\u001B[43mgroup_name\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[38;5;124;43mFailed\u001B[39;49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[43m,\u001B[49m\n\u001B[1;32m 3\u001B[0m \u001B[43m \u001B[49m\u001B[43mgroup_members\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43m[\u001B[49m\u001B[43mmember1\u001B[49m\u001B[43m]\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 4\u001B[0m \u001B[43m \u001B[49m\u001B[43min_group_category\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[38;5;124;43mGroup Project 2\u001B[39;49m\u001B[38;5;124;43m\"\u001B[39;49m\n\u001B[1;32m 5\u001B[0m \u001B[43m)\u001B[49m\n", - "Cell \u001B[0;32mIn[51], line 187\u001B[0m, in \u001B[0;36mCanvasGroup.assign_canvas_group\u001B[0;34m(self, group_name, group_members, in_group_category)\u001B[0m\n\u001B[1;32m 181\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21massign_canvas_group\u001B[39m(\u001B[38;5;28mself\u001B[39m,\n\u001B[1;32m 182\u001B[0m group_name: \u001B[38;5;28mstr\u001B[39m, \u001B[38;5;66;03m# group name, display on canvas\u001B[39;00m\n\u001B[1;32m 183\u001B[0m group_members:[\u001B[38;5;28mstr\u001B[39m], \u001B[38;5;66;03m# list of group member's SIS Login\u001B[39;00m\n\u001B[1;32m 184\u001B[0m in_group_category: \u001B[38;5;28mstr\u001B[39m, \u001B[38;5;66;03m# specify which group category the group belongs to\u001B[39;00m\n\u001B[1;32m 185\u001B[0m ) \u001B[38;5;241m-\u001B[39m\u001B[38;5;241m>\u001B[39m (canvasapi\u001B[38;5;241m.\u001B[39mgroup\u001B[38;5;241m.\u001B[39mGroup, [\u001B[38;5;28mstr\u001B[39m]): \u001B[38;5;66;03m# list of unsuccessful join\u001B[39;00m\n\u001B[1;32m 186\u001B[0m \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mCreate new groups and assign group member into the class in the `self.group_category`\u001B[39m\u001B[38;5;124m\"\u001B[39m\n\u001B[0;32m--> 187\u001B[0m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mset_group_category\u001B[49m\u001B[43m(\u001B[49m\u001B[43min_group_category\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 188\u001B[0m group \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mcreate_group({\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mname\u001B[39m\u001B[38;5;124m\"\u001B[39m: group_name})\n\u001B[1;32m 189\u001B[0m unsuccessful_join \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mjoin_canvas_group(group, group_members)\n", - "Cell \u001B[0;32mIn[51], line 75\u001B[0m, in \u001B[0;36mCanvasGroup.set_group_category\u001B[0;34m(self, category_name)\u001B[0m\n\u001B[1;32m 73\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mgroup_category \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mgroup_categories[category_name]\n\u001B[1;32m 74\u001B[0m \u001B[38;5;28;01mexcept\u001B[39;00m \u001B[38;5;167;01mKeyError\u001B[39;00m:\n\u001B[0;32m---> 75\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m \u001B[38;5;167;01mKeyError\u001B[39;00m(\u001B[38;5;124mf\u001B[39m\u001B[38;5;124m\"\u001B[39m\u001B[38;5;132;01m{\u001B[39;00mcategory_name\u001B[38;5;132;01m}\u001B[39;00m\u001B[38;5;124m did not found in the group categories. \u001B[39m\u001B[38;5;124m\"\u001B[39m\n\u001B[1;32m 76\u001B[0m \u001B[38;5;124mf\u001B[39m\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mTry to create one with CanvasGroup.create_group_category\u001B[39m\u001B[38;5;124m\"\u001B[39m)\n\u001B[1;32m 77\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mgroup_category\n", - "\u001B[0;31mKeyError\u001B[0m: 'Group Project 2 did not found in the group categories. Try to create one with CanvasGroup.create_group_category'" - ] - } - ], - "source": [ - "cg.assign_canvas_group(\n", - " group_name=\"Failed\",\n", - " group_members=[member1],\n", - " in_group_category=\"Group Project 2\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "bc630b44", - "metadata": {}, - "source": [ - "## Fetch Students' GitHub username" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3a4a5e52", - "metadata": {}, - "outputs": [ - { - "data": { - "text/markdown": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L150){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.fetch_username_from_quiz\n", - "\n", - "> CanvasGroup.fetch_username_from_quiz (quiz_id:int,\n", - "> csv_name='github_id.csv',\n", - "> col_index=7)\n", - "\n", - "Fetch the GitHub user name from the canvas quiz\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| quiz_id | int | | quiz id of the username quiz |\n", - "| csv_name | str | github_id.csv | csv output name. |\n", - "| col_index | int | 7 | canvas quiz generated csv's question field column index |\n", - "| **Returns** | **dict** | | **{SIS Login ID: github} dictionary** |" - ], - "text/plain": [ - "---\n", - "\n", - "[source](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/CanvasGroupy/canvas.py#L150){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", - "\n", - "### CanvasGroup.fetch_username_from_quiz\n", - "\n", - "> CanvasGroup.fetch_username_from_quiz (quiz_id:int,\n", - "> csv_name='github_id.csv',\n", - "> col_index=7)\n", - "\n", - "Fetch the GitHub user name from the canvas quiz\n", - "\n", - "| | **Type** | **Default** | **Details** |\n", - "| -- | -------- | ----------- | ----------- |\n", - "| quiz_id | int | | quiz id of the username quiz |\n", - "| csv_name | str | github_id.csv | csv output name. |\n", - "| col_index | int | 7 | canvas quiz generated csv's question field column index |\n", - "| **Returns** | **dict** | | **{SIS Login ID: github} dictionary** |" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "show_doc(CanvasGroup.fetch_username_from_quiz)" - ] - }, - { - "cell_type": "markdown", - "id": "3afdedeb", - "metadata": {}, - "source": [ - "An example of the quiz question looks like the followings\n", - "\n", - "![quiz_screenshot](imgs/quiz_example.png) \n", - "\n", - "On canvas, this is a _fill in the blank_ question with the following raw format: \n", - "\n", - "![quiz_raw](imgs/raw_question.png)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2eb5a58c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Quiz: \u001B[92mGitHub ID\u001B[0m fetch! \n", - "Generating Student Analaysis...\n", - "[====================] 100%\n", - "\u001B[92mReport Generated!\u001B[0m\n", - "The Question asked is \u001B[94m1389031: What is your GitHub ID? Absolutely No Typo Please.\u001B[0m. \n", - "Make sure this is the correct question where you asked student for their GitHub id.\n", - "If you need to change the index of columns, change the col_index argument of this call.\n" - ] - } - ], - "source": [ - "quiz_id = 139061\n", - "github_ids = cg.fetch_username_from_quiz(\n", - " quiz_id=quiz_id\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9a73f573", - "metadata": {}, - "outputs": [ - { - "ename": "NameError", - "evalue": "name 'show_doc' is not defined", - "output_type": "error", - "traceback": [ - "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", - "\u001B[0;31mNameError\u001B[0m Traceback (most recent call last)", - "Input \u001B[0;32mIn [1]\u001B[0m, in \u001B[0;36m\u001B[0;34m()\u001B[0m\n\u001B[0;32m----> 1\u001B[0m \u001B[43mshow_doc\u001B[49m(CanvasGroup\u001B[38;5;241m.\u001B[39mcheck_github_usernames)\n", - "\u001B[0;31mNameError\u001B[0m: name 'show_doc' is not defined" - ] - } - ], - "source": [ - "show_doc(CanvasGroup.check_github_usernames)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5b7c2d99", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "User: \u001B[93m64987902\u001B[0m Not Found on GitHub\n", - "User: \u001B[93m122977555\u001B[0m Not Found on GitHub\n", - "User: \u001B[93m10285923\u001B[0m Not Found on GitHub\n" - ] - }, - { - "data": { - "text/plain": [ - "{'gng': '64987902', 'y3guo': '122977555', 'nyanekch': '10285923'}" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cg.check_github_usernames(github_name,\n", - " True,\n", - " True,\n", - " \"https://canvas.ucsd.edu/courses/45059/quizzes/139061\")" - ] - }, - { - "cell_type": "markdown", - "id": "dd12991b", - "metadata": {}, - "source": [ - "Since some students did not put their github username correctly." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "python3", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/dev_nbs/check_group.ipynb b/dev_nbs/check_group.ipynb deleted file mode 100644 index 9957351..0000000 --- a/dev_nbs/check_group.ipynb +++ /dev/null @@ -1,165 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": true, - "ExecuteTime": { - "start_time": "2023-05-17T10:42:29.468758Z", - "end_time": "2023-05-17T10:42:29.861761Z" - } - }, - "outputs": [], - "source": [ - "from CanvasGroupy import *" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001B[92mAuthorization Successful!\u001B[0m\n", - "Course Set: \u001B[92m COGS 118A - Supvr/Mach Learning Algorithms - Fleischer [SP23] \u001B[0m\n", - "Getting List of Users... This might take a while...\n", - "Users Fetch Complete! The course has \u001B[94m160\u001B[0m students.\n" - ] - } - ], - "source": [ - "canvas = CanvasGroup(\"../../credentials.json\",\n", - " course_id=45059\n", - " )" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "start_time": "2023-05-17T10:43:13.025144Z", - "end_time": "2023-05-17T10:43:17.051486Z" - } - } - }, - { - "cell_type": "code", - "execution_count": 6, - "outputs": [], - "source": [ - "groups = canvas.get_groups(\"Final Project\")" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "start_time": "2023-05-17T10:43:30.089084Z", - "end_time": "2023-05-17T10:43:41.328822Z" - } - } - }, - { - "cell_type": "code", - "execution_count": 7, - "outputs": [ - { - "data": { - "text/plain": "{'Group001-SP23': ['h5he', 'zmao', 'xiw013', 'j6wen', 'j5zhu'],\n 'Group002-SP23': ['cmcmanig', 'jup006', 'ssuthar', 'd3yu'],\n 'Group003-SP23': ['yic055', 'yuz191', 'xiz068'],\n 'Group004-SP23': ['dac020', 'nilu', 'tyap', 'g6zhu'],\n 'Group005-SP23': ['kechen', 'a8chu', 'cdelira', 'wolee', 'arshukla'],\n 'Group006-SP23': ['ax008707', 'ax008724', 'ax008777'],\n 'Group007-SP23': ['yuchi', 'j3dong', 'y3ge', 'x6he', 'xiz031'],\n 'Group008-SP23': ['zifeng', 'ax008740', 'jul121', 'yuy047'],\n 'Group009-SP23': ['ax008573', 'ckavanagh', 'v1lu', 'vvishnus'],\n 'Group010-SP23': ['jwc002', 'tjamal', 'jsliang', 'tdn003'],\n 'Group011-SP23': ['khchuang', 'emdavis', 'jejiang', 'nrejai'],\n 'Group012-SP23': ['afleschn', 'rlharsono', 'jjsanchez', 'asengupt'],\n 'Group013-SP23': ['kehu', 'jnhuang', 'shperry', 'alvalenc'],\n 'Group014-SP23': ['gsroberts', 'cvillafa', 'shw089', 'yiz095'],\n 'Group015-SP23': ['aanna', 'sdsilva', 'asivayog', 'nyanekch'],\n 'Group016-SP23': ['yuche', 'y3guo', 'e1hu', 'zhl023', 'qil012'],\n 'Group017-SP23': ['s3chowdhury', 'llennema', 'psodhi', 'svirk'],\n 'Group018-SP23': ['ajcagle', 'ahewig', 's2malik', 'mnodini', 'musman'],\n 'Group019-SP23': ['sasingh', 'nsit', 'dsun'],\n 'Group020-SP23': ['akaji', 'm1manzan', 'j3mendez', 'mpareek', 'atrapena'],\n 'Group021-SP23': ['jkrentse', 'jmlai', 'zel012', 'sserafin'],\n 'Group022-SP23': ['ax008708', 'anc024', 'gng', 'reyang'],\n 'Group023-SP23': ['raguinakang', 'phelcl', 'vjayanan', 'crochez', 'kpstern'],\n 'Group024-SP23': ['nabansal', 'ccaban', 'mvfang', 'asim'],\n 'Group025-SP23': ['nschaefe', 'hshaikh', 'jww001', 'bjyan'],\n 'Group026-SP23': ['e1dong', 'aaolivas', 'h5park', 'yuz821'],\n 'Group027-SP23': ['jddeleon', 'ndnguyen', 'ttp007', 'jzs002'],\n 'Group028-SP23': ['shc007', 'yuh045', 'ktnakai', 'jonza'],\n 'Group029-SP23': ['lmitbo', 'jsalce', 'lskerrett', 'jubamadu'],\n 'Group030-SP23': ['ruc003', 'shh035', 'btn003', 'knino'],\n 'Group031-SP23': ['n6garcia', 'ken010', 'mmpak'],\n 'Group032-SP23': ['zachao', 'smurase', 'j1xu', 'z5zhang'],\n 'Group033-SP23': ['sbodhisartha', 'ndeepak', 'arlu', 'trucker', 'jaxu'],\n 'Group034-SP23': ['kabalaji', 'rchaklas', 'vspillai', 'jmvillal'],\n 'Group035-SP23': ['cantoniohernandez', 'rpuranam', 'rsedano', 'zshao'],\n 'Group036-SP23': ['malkhalifah', 'djjani', 'rkohli', 'smtrived'],\n 'Group037-SP23': ['nazpeitia', 'g2hong', 'gkweon'],\n 'Group038-SP23': ['qil016', 'msherrick', 'yiw085', 'r4zhou'],\n 'Group039-SP23': ['cgutierrezgodoy', 'z8jiang', 'dar005', 'jtaolan']}" - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "groups" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "start_time": "2023-05-17T10:43:41.328356Z", - "end_time": "2023-05-17T10:43:41.339791Z" - } - } - }, - { - "cell_type": "code", - "execution_count": 9, - "outputs": [ - { - "data": { - "text/plain": "['shc007', 'yuh045', 'ktnakai', 'jonza']" - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "groups[\"Group028-SP23\"]" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "start_time": "2023-05-17T11:02:31.003593Z", - "end_time": "2023-05-17T11:02:31.006615Z" - } - } - }, - { - "cell_type": "code", - "execution_count": 8, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Group028-SP23\n" - ] - } - ], - "source": [ - "for name, member in groups.items():\n", - " if \"shc007\" in member:\n", - " print(name)" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "start_time": "2023-05-17T10:44:13.317255Z", - "end_time": "2023-05-17T10:44:13.322522Z" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false - } - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 2 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.6" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/dev_nbs/github_example.ipynb b/dev_nbs/github_example.ipynb deleted file mode 100644 index f6be787..0000000 --- a/dev_nbs/github_example.ipynb +++ /dev/null @@ -1,266 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "ef7578d4", - "metadata": {}, - "source": [ - "# GitHub Group Example\n", - "\n", - "> A step by step example of using the GitHub Group Module" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "d54b9d44", - "metadata": { - "ExecuteTime": { - "end_time": "2023-05-18T00:09:54.444030Z", - "start_time": "2023-05-18T00:09:53.309030Z" - } - }, - "outputs": [], - "source": [ - "from CanvasGroupy import *" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "dcc397e6", - "metadata": { - "ExecuteTime": { - "end_time": "2023-05-18T00:09:55.557700Z", - "start_time": "2023-05-18T00:09:54.444794Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Successfully Authenticated. GitHub account: \u001B[92m scott-yj-yang \u001B[0m\n", - "Target Organization Set: \u001B[92m COGS118A \u001B[0m\n" - ] - } - ], - "source": [ - "gh = GitHubGroup(\"../../credentials.json\", \"COGS118A\", verbosity=1)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "555062fa", - "metadata": { - "ExecuteTime": { - "end_time": "2023-05-18T00:10:28.654836Z", - "start_time": "2023-05-18T00:10:28.652879Z" - } - }, - "outputs": [], - "source": [ - "# gh.resent_invitations_team_repos(team_slug=\"Instructors_Sp23\")" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "cc3b5ad5", - "metadata": { - "ExecuteTime": { - "end_time": "2023-05-18T00:10:33.504364Z", - "start_time": "2023-05-18T00:10:29.929687Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001B[92mAuthorization Successful!\u001B[0m\n", - "Course Set: \u001B[92m COGS 118A - Supvr/Mach Learning Algorithms - Fleischer [SP23] \u001B[0m\n", - "Getting List of Users... This might take a while...\n", - "Users Fetch Complete! The course has \u001B[94m160\u001B[0m students.\n" - ] - } - ], - "source": [ - "cg = CanvasGroup(\"../../credentials.json\", course_id=45059)" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Group Category: \u001B[92mFinal Project\u001B[0m Set!\n" - ] - }, - { - "data": { - "text/plain": "GroupCategory(_requester=, id=16277, name=Final Project, role=None, self_signup=enabled, group_limit=5, auto_leader=None, created_at=2023-04-27T20:55:14Z, created_at_date=2023-04-27 20:55:14+00:00, context_type=Course, course_id=45059, protected=False, allows_multiple_memberships=False, is_member=False)" - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cg.set_group_category(\"Final Project\")" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2023-05-18T00:14:14.205617Z", - "start_time": "2023-05-18T00:14:03.468067Z" - } - } - }, - { - "cell_type": "code", - "execution_count": 6, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Quiz: \u001B[92mGitHub Username\u001B[0m fetch! \n", - "Generating Student Analaysis...\n", - "[====================] 100%\n", - "\u001B[92mReport Generated!\u001B[0m\n", - "The Question asked is \u001B[94m1389031: What is your GitHub Username? Absolutely No Typo Please.\u001B[0m. \n", - "Make sure this is the correct question where you asked student for their GitHub id.\n", - "If you need to change the index of columns, change the col_index argument of this call.\n" - ] - } - ], - "source": [ - "username=cg.fetch_username_from_quiz(139061)" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2023-05-18T00:10:43.969544Z", - "start_time": "2023-05-18T00:10:33.507213Z" - } - } - }, - { - "cell_type": "code", - "execution_count": 27, - "outputs": [ - { - "data": { - "text/plain": "'aaolivas'" - }, - "execution_count": 27, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "username[\"aaolivas\"]" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2023-05-18T00:16:21.413298Z", - "start_time": "2023-05-18T00:16:21.408926Z" - } - } - }, - { - "cell_type": "code", - "execution_count": 29, - "outputs": [], - "source": [ - "repo = gh.get_repo(\"Group1-SP23-Testing\")" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2023-05-18T00:24:25.496637Z", - "start_time": "2023-05-18T00:24:25.115913Z" - } - } - }, - { - "cell_type": "code", - "execution_count": 35, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "In the repo: \u001B[92mGroup1-SP23-Testing\u001B[0m,\n", - "Issue \u001B[92m Project Proposal Feedback\u001B[0m Created!\n" - ] - }, - { - "data": { - "text/plain": "Issue(title=\" Project Proposal Feedback\", number=1)" - }, - "execution_count": 35, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "gh.create_issue_from_md(repo, \"../nbs/feedback_template/proposal_feedback.md\")" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2023-05-18T00:26:25.794825Z", - "start_time": "2023-05-18T00:26:25.040492Z" - } - } - }, - { - "cell_type": "code", - "execution_count": 33, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "/Users/scottyang/Desktop/SP23_Working/Group_Eng/CanvasGroupy/dev_nbs\r\n" - ] - } - ], - "source": [ - "!pwd" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2023-05-18T00:25:51.785999Z", - "start_time": "2023-05-18T00:25:51.487891Z" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false - } - } - ], - "metadata": { - "kernelspec": { - "display_name": "python3", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/dev_nbs/imgs/quiz_example.png b/dev_nbs/imgs/quiz_example.png deleted file mode 100644 index 66f4bb397d4868c3bd54dbb94b2f054a583fdcc8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 86044 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�n7coGn9AmDrOrk@QH0+MCGEL`yfhe7SAU|9 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 diff --git a/dev_nbs/imgs/raw_question.png b/dev_nbs/imgs/raw_question.png deleted file mode 100644 index 5b096ec9e814a76deb2dcc75c9692326945942e6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63676 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 diff --git a/nbs/.gitignore b/nbs/.gitignore deleted file mode 100644 index 075b254..0000000 --- a/nbs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/.quarto/ diff --git a/nbs/_quarto.yml b/nbs/_quarto.yml deleted file mode 100644 index 8254f0a..0000000 --- a/nbs/_quarto.yml +++ /dev/null @@ -1,32 +0,0 @@ -project: - type: website - output-dir: _docs - -format: - html: - theme: cosmo - css: styles.css - toc: true - -website: - title: "CanvasGroupy" - site-url: "https://FleischerResearchLab.github.io/CanvasGroupy" - description: "Canvas Grouping Python Script" - repo-branch: main - repo-url: "https://github.com/FleischerResearchLab/CanvasGroupy" - twitter-card: true - open-graph: true - repo-actions: [issue] - navbar: - background: primary - search: true - sidebar: - style: floating - contents: - - auto: "/index.ipynb" - - section: Tutorials - contents: tutorials/* - - section: API - contents: api/* - - section: Project Feedback Template - contents: feedback_template/* diff --git a/nbs/api/01_GroupEng_assign.ipynb b/nbs/api/01_GroupEng_assign.ipynb deleted file mode 100644 index f9a5ecb..0000000 --- a/nbs/api/01_GroupEng_assign.ipynb +++ /dev/null @@ -1,286 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# GroupEngAssign\n", - "\n", - "> Invoke Package Group Eng to Assign Students in Groups" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "import pandas as pd\n", - "import shutil" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import GroupEng\n", - "import canvasapi\n", - "import github\n", - "from CanvasGroupy import GitHubGroup, CanvasGroup" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class AssignGroup:\n", - " def __init__(self,\n", - " ghg: GitHubGroup, # authenticated GitHub object\n", - " cg: CanvasGroup, # authenticated canvas object\n", - " groupeng_config=\"\", # Directory for the GroupEng config yml file\n", - " ):\n", - " \"Initializer for Assign Group\"\n", - " self.status = None\n", - " self.out_dir = None\n", - " self.prefix = None\n", - " self.cg = cg\n", - " self.ghg = ghg\n", - " # Initialize if appropriate parameters are defined\n", - " if groupeng_config != \"\":\n", - " self.assign_groups(groupeng_config)\n", - "\n", - " def assign_groups(self,\n", - " groupeng_config:str, # Directory for the GroupEng config yml file\n", - " assign_canvas_group=False, # directly assign canvas groups\n", - " create_gh_repo=False, # directly create GitHub repos\n", - " username_quiz_id=-1, # username quiz id from canvas course\n", - " in_group_category=\"\", # specify which group category the group belongs to\n", - " suffix=\"\", # suffix to the group name\n", - " ) -> (bool, str): # Status and output directory of the compiled file.\n", - " status, out_dir = GroupEng.run(groupeng_config)\n", - " self.status, self.out_dir = status, out_dir\n", - " file = os.path.split(groupeng_config)[1]\n", - " self.prefix = os.path.splitext(file)[0]\n", - " if assign_canvas_group:\n", - " if self.cg.group_category is None and in_group_category == \"\":\n", - " raise ValueError(\"Have to specify in_group_category to create canvas group\")\n", - " self.create_canvas_group(in_group_category, suffix)\n", - " if create_gh_repo:\n", - " if username_quiz_id == -1:\n", - " raise ValueError(\"Have to specify the canvas username quiz id\")\n", - " self.create_github_group(username_quiz_id)\n", - " return status, out_dir\n", - "\n", - " def create_canvas_group(self,\n", - " in_group_category=\"\", # specify which group category the group belongs to\n", - " suffix=\"\", # suffix to the group name\n", - " ):\n", - " \"Create canvas groups based on the generated group configuration\"\n", - " if self.out_dir is None:\n", - " raise ValueError(\"The group configuration has not been set. Please assign group via assign_groups\")\n", - " if self.cg.group_category is None:\n", - " raise ValueError(\"The group category has not been set.\")\n", - " if in_group_category == \"\":\n", - " in_group_category = self.cg.group_category.name\n", - " # load the generated configuration file\n", - " groups_generated_fp = os.path.join(self.out_dir, f\"{self.prefix}_groups.csv\")\n", - " with open(groups_generated_fp, \"r\") as f:\n", - " groups = f.read().splitlines()\n", - " # create canvas groups for each.\n", - " for group in groups:\n", - " group = group.replace(\" \", \"\").split(\",\")\n", - " group_name, group_members = group[0], group[1:]\n", - " self.cg.assign_canvas_group(\n", - " group_name=f\"{group_name}{suffix}\",\n", - " group_members=group_members,\n", - " in_group_category=in_group_category\n", - " )\n", - "\n", - " def create_github_group(self,\n", - " username_quiz_id:int # username quiz id from canvas course\n", - " ):\n", - " github_usernames = self.cg.fetch_username_from_quiz(username_quiz_id)\n", - " self.cg.set_group_category(cg.group_category.name)\n", - " groups = self.cg.group_to_emails\n", - " repos = []\n", - " for group_name, members in groups.items():\n", - " group_git_usernames = []\n", - " for email in members:\n", - " try:\n", - " # try to get the git username for each student.\n", - " # not all students completed their quiz.\n", - " group_git_usernames.append(github_usernames[email])\n", - " except KeyError:\n", - " print(f\"{email}'s GitHub Username not found\")\n", - " repo = self.ghg.create_group_repo(\n", - " repo_name=group_name,\n", - " collaborators=group_git_usernames,\n", - " permission=\"write\",\n", - " repo_template=\"COGS118A/group_template\",\n", - " rename_files={\n", - " \"Checkpoint_groupXXX.ipynb\": f\"Checkpoint_{group_name}.ipynb\",\n", - " \"FinalProject_groupXXX.ipynb\": f\"FinalProject_{group_name}.ipynb\",\n", - " \"Proposal_groupXXX.ipynb\": f\"Proposal_{group_name}.ipynb\"\n", - " },\n", - " private=True,\n", - " description=f\"COGS118A Final Project {group_name} Repository\",\n", - " team_slug=\"Instructors_Sp23\",\n", - " team_permission=\"admin\"\n", - " )\n", - " print(\"\")\n", - " repos.append(repo)\n", - " return repos\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## API" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Successfully Authenticated. GitHub account: \u001b[92m scott-yj-yang \u001b[0m\n", - "Target Organization Set: \u001b[92m COGS118A \u001b[0m\n", - "\u001b[92mAuthorization Successful!\u001b[0m\n", - "Course Set: \u001b[92m COGS 195 - Instructional Apprenticeship - Fleischer [SP23] \u001b[0m\n", - "Getting List of Users... This might take a while...\n", - "Users Fetch Complete! The course has \u001b[94m5\u001b[0m students.\n" - ] - } - ], - "source": [ - "# Create authenticated objects\n", - "ghg = GitHubGroup(\"../../../credentials.json\",\n", - " \"COGS118A\"\n", - " )\n", - "cg = CanvasGroup(\"../../../credentials.json\",\n", - " course_id=45532,\n", - " )\n", - "# create assign group object\n", - "ag = AssignGroup(ghg, cg)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": "GroupCategory(_requester=, id=16456, name=Project 1, role=None, self_signup=None, group_limit=None, auto_leader=None, created_at=2023-05-17T20:38:56Z, created_at_date=2023-05-17 20:38:56+00:00, context_type=Course, course_id=45532, groups_count=0, unassigned_users_count=5, protected=False, allows_multiple_memberships=False, is_member=False)" - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# create a group category to hold students\n", - "cg.create_group_category({\"name\": \"Project 1\"})" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "['H', 'B', '-']\n", - "['B', 'H', '-']\n", - "['-', 'H', 'B']\n", - "['B', 'H', '-']\n", - "['B', '-', 'H']\n", - "['-', 'B', 'H']\n", - "[None, 3.9, 3.1]\n", - "[3.9, 3.1, None]\n", - "[3.4, 2.5, 2.1]\n", - "[3.9, None, 3.1]\n", - "[3.4, 2.1, 2.5]\n", - "[3.4, 2.1, 2.5]\n", - "In Group Set: \u001b[94mProject 1\u001b[0m,\n", - "Group \u001b[92mGroup1-SP23-Testing\u001b[0m Created!\n", - "Member \u001b[92mdol005\u001b[0m Joined group \u001b[92mGroup1-SP23-Testing\u001b[0m\n", - "Member \u001b[92mxiw013\u001b[0m Joined group \u001b[92mGroup1-SP23-Testing\u001b[0m\n", - "In Group Set: \u001b[94mProject 1\u001b[0m,\n", - "Group \u001b[92mGroup2-SP23-Testing\u001b[0m Created!\n", - "Member \u001b[92mjiz088\u001b[0m Joined group \u001b[92mGroup2-SP23-Testing\u001b[0m\n", - "Member \u001b[92mjiz100\u001b[0m Joined group \u001b[92mGroup2-SP23-Testing\u001b[0m\n", - "Member \u001b[92mnmackler\u001b[0m Joined group \u001b[92mGroup2-SP23-Testing\u001b[0m\n", - "Quiz: \u001b[92mGitHub Username\u001b[0m fetch! \n", - "Generating Student Analaysis...\n", - "[====================] 100%\n", - "\u001b[92mReport Generated!\u001b[0m\n", - "The Question asked is \u001b[94m1399692: In plain text, what is your GitHub Username? Absolutely no typo, no extra space, no hyperlink please.\u001b[0m. \n", - "Make sure this is the correct question where you asked student for their GitHub id.\n", - "If you need to change the index of columns, change the col_index argument of this call.\n", - "dol005's GitHub Username not found\n", - "xiw013's GitHub Username not found\n", - "Repo \u001b[92m Group1-SP23-Testing \u001b[0m Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from \u001b[96m Checkpoint_groupXXX.ipynb \u001b[0m to \u001b[92m Checkpoint_Group1-SP23-Testing.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m FinalProject_groupXXX.ipynb \u001b[0m to \u001b[92m FinalProject_Group1-SP23-Testing.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m Proposal_groupXXX.ipynb \u001b[0m to \u001b[92m Proposal_Group1-SP23-Testing.ipynb \u001b[0m\n", - "Team \u001b[92m Instructors_Sp23 \u001b[0m added to \u001b[92m Group1-SP23-Testing \u001b[0m with permission \u001b[92m admin \u001b[0m\n", - "Group Repo: \u001b[92m Group1-SP23-Testing \u001b[0m successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group1-SP23-Testing\n", - "\n", - "jiz100's GitHub Username not found\n", - "jiz088's GitHub Username not found\n", - "Repo \u001b[92m Group2-SP23-Testing \u001b[0m Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from \u001b[96m Checkpoint_groupXXX.ipynb \u001b[0m to \u001b[92m Checkpoint_Group2-SP23-Testing.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m FinalProject_groupXXX.ipynb \u001b[0m to \u001b[92m FinalProject_Group2-SP23-Testing.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m Proposal_groupXXX.ipynb \u001b[0m to \u001b[92m Proposal_Group2-SP23-Testing.ipynb \u001b[0m\n", - "Added Collaborator: \u001b[92m nmackler \u001b[0m to: \u001b[92m Group2-SP23-Testing \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Team \u001b[92m Instructors_Sp23 \u001b[0m added to \u001b[92m Group2-SP23-Testing \u001b[0m with permission \u001b[92m admin \u001b[0m\n", - "Group Repo: \u001b[92m Group2-SP23-Testing \u001b[0m successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group2-SP23-Testing\n", - "\n" - ] - } - ], - "source": [ - "# assign, create both Canvas and GitHub Group in one call\n", - "status, out_dir = ag.assign_groups(\"../data/195_group_specification.groupeng\",\n", - " assign_canvas_group=True,\n", - " create_gh_repo=True,\n", - " username_quiz_id=139925,\n", - " in_group_category=\"Project 1\",\n", - " suffix=\"-SP23-Testing\"\n", - " )" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The false means that at least one requirement is not satisfied. We can take a look at the file that was generated." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "python3", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/nbs/api/02_github.ipynb b/nbs/api/02_github.ipynb deleted file mode 100644 index b2437a4..0000000 --- a/nbs/api/02_github.ipynb +++ /dev/null @@ -1,985 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# GitHub Group Creation\n", - "\n", - "> Create GitHub group based on the provided group information" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from github import Github\n", - "import github\n", - "import json\n", - "import time\n", - "import os\n", - "import glob\n", - "from pprint import pprint" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class bcolors:\n", - " HEADER = '\\033[95m'\n", - " OKBLUE = '\\033[94m'\n", - " OKCYAN = '\\033[96m'\n", - " OKGREEN = '\\033[92m'\n", - " WARNING = '\\033[93m'\n", - " FAIL = '\\033[91m'\n", - " ENDC = '\\033[0m'\n", - " BOLD = '\\033[1m'\n", - " UNDERLINE = '\\033[4m'" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class GitHubGroup:\n", - " def __init__(self,\n", - " credentials_fp=\"\", # the file path to the credential json\n", - " org=\"\", # the organization name\n", - " verbosity=1 # Controls the verbosity: 0=silent, 1=print status\n", - " ):\n", - " self.github = None\n", - " self.org = None\n", - " self.verbosity = verbosity\n", - " \n", - " if credentials_fp != \"\":\n", - " self.auth_github(credentials_fp)\n", - " if org != \"\":\n", - " self.set_org(org)\n", - "\n", - " def auth_github(self,\n", - " credentials_fp: str # the personal access token generated at GitHub Settings\n", - " ):\n", - " \"Authenticate GitHub account with the provided credentials\"\n", - " with open(credentials_fp, \"r\") as f:\n", - " token = json.load(f)[\"GitHub Token\"]\n", - " self.github = Github(token)\n", - " # check authorization\n", - " _ = self.github.get_user().get_repos()[0]\n", - " if self.verbosity != 0:\n", - " print(f\"Successfully Authenticated. \"\n", - " f\"GitHub account: {bcolors.OKGREEN} {self.github.get_user().login} {bcolors.ENDC}\")\n", - " \n", - " def set_org(self,\n", - " org: str # the target organization name\n", - " ):\n", - " \"Set the target organization for repo creation\"\n", - " self.org = self.github.get_organization(org)\n", - " if self.verbosity != 0:\n", - " print(f\"Target Organization Set: {bcolors.OKGREEN} {self.org.login} {bcolors.ENDC}\")\n", - "\n", - " def create_repo(self,\n", - " repo_name: str, # repository name\n", - " repo_template=\"\", # template repository that new repo will use. If empty string, an empty repo will be created. Put in the format of \"/\"\n", - " private=True, # visibility of the created repository\n", - " description=\"\", # description for the GitHub repository\n", - " personal_account=False, # create repos in personal GitHub account\n", - " ) -> github.Repository.Repository:\n", - " \"Create a repository, either blank, or from a template\"\n", - " if self.org is None and personal_account:\n", - " raise ValueError(\"Organization is not set\")\n", - " if personal_account:\n", - " parent = self.github.get_user()\n", - " else:\n", - " parent = self.org\n", - " if repo_template == \"\":\n", - " return parent.create_repo(\n", - " name=repo_name,\n", - " private=private,\n", - " description=description\n", - " )\n", - " # create from template\n", - " return parent.create_repo_from_template(\n", - " name=repo_name,\n", - " repo=self.get_repo(repo_template),\n", - " private=private,\n", - " description=description,\n", - " )\n", - " \n", - " def get_repo(self,\n", - " repo_full_name: str # full name of the target repository\n", - " ) -> github.Repository.Repository:\n", - " \"To get a repository by its name\"\n", - " try:\n", - " return self.github.get_repo(repo_full_name)\n", - " except Exception:\n", - " return self.org.get_repo(repo_full_name)\n", - " \n", - " def get_org_repo(self,\n", - " repo_full_name: str # full name of the target repository\n", - " ) -> github.Repository.Repository:\n", - " \"Get a repository within the target organization\"\n", - " return self.org.get_repo(repo_full_name)\n", - "\n", - " \n", - " def get_team(self,\n", - " team_slug:str # team slug of the team\n", - " ) -> github.Team.Team:\n", - " \"Get the team inside the target organization\"\n", - " if self.org is None:\n", - " raise ValueError(\"The organization has not been set. Please set it via g.set_org\")\n", - " return self.org.get_team_by_slug(team_slug)\n", - " \n", - " def rename_files(self,\n", - " repo: github.Repository.Repository, # the repository that we want to rename file\n", - " og_filename: str, # old file name\n", - " new_filename: str # new file name\n", - " ):\n", - " \"Rename the file by delete the old file and commit the new file\"\n", - " file = repo.get_contents(og_filename)\n", - " repo.create_file(new_filename, \"rename files\", file.decoded_content)\n", - " repo.delete_file(og_filename, \"delete old files\", file.sha)\n", - " if self.verbosity != 0:\n", - " print(f\"File Successfully Renamed from \"\n", - " f\" {bcolors.OKCYAN} {og_filename} {bcolors.ENDC} \"\n", - " f\" to {bcolors.OKGREEN} {new_filename} {bcolors.ENDC}\")\n", - " \n", - " def add_collaborator(self,\n", - " repo: github.Repository.Repository, # target repository\n", - " collaborator:str, # GitHub username of the collaborator\n", - " permission:str # `pull`, `push` or `admin`\n", - " ):\n", - " \"Add collaborator to the repository with specified permission\"\n", - " try:\n", - " repo.add_to_collaborators(collaborator, permission)\n", - " except Exception as e:\n", - " print(f\"{bcolors.WARNING}Add Failed for {collaborator}{bcolors.ENDC}\")\n", - " if self.verbosity != 0:\n", - " print(f\"Added Collaborator: {bcolors.OKGREEN} {collaborator} {bcolors.ENDC}\"\n", - " f\" to: {bcolors.OKGREEN} {repo.name} {bcolors.ENDC} with \"\n", - " f\"permission: {bcolors.OKGREEN} {permission} {bcolors.ENDC}\")\n", - " \n", - " def remove_collaborator(self,\n", - " repo: github.Repository.Repository, # target repository\n", - " collaborator:str, # GitHub username of the collaborator\n", - " ):\n", - " \"Remove collaborator privileges from the repository\"\n", - " repo.remove_from_collaborators(collaborator)\n", - " \n", - " def resend_invitations(self,\n", - " repo: github.Repository.Repository, # target repository\n", - " ) -> [github.NamedUser.NamedUser]: # list of re-invited user\n", - " \"Resent Invitation to invitee who did not accept the invitation\"\n", - " pendings = list(repo.get_pending_invitations())\n", - " users = [p.invitee for p in pendings]\n", - " if self.verbosity != 0:\n", - " print(\"The list of pending invitation:\")\n", - " pprint(users)\n", - " for p in pendings:\n", - " repo.remove_invitation(p.id)\n", - " if self.verbosity != 0:\n", - " print(f\"{bcolors.WARNING}{bcolors.UNDERLINE}{p.invitee.login}{bcolors.ENDC} {bcolors.FAIL}Invite Revoked {bcolors.ENDC}\")\n", - " self.add_collaborator(repo, p.invitee.login, p.permissions)\n", - " if self.verbosity != 0:\n", - " print(f\"{bcolors.OKGREEN} Invite Resent to {p.invitee.login} {bcolors.ENDC}\")\n", - " return users\n", - "\n", - " def resent_invitations_team_repos(self,\n", - " team_slug: str # team slug (name) under the org\n", - " ):\n", - " \"For all repository under that team, Resent invitation to invitee who did not accept the invitation\"\n", - " team = self.get_team(team_slug)\n", - " repos = team.get_repos()\n", - " for repo in repos:\n", - " if self.verbosity != 0:\n", - " print(f\"Repository {bcolors.OKCYAN} {repo.name} {bcolors.ENDC}:\")\n", - " try:\n", - " _ = self.resend_invitations(repo)\n", - " except Exception as e:\n", - " print(f\"{bcolors.WARNING}Make sure to have proper rights to the target repo{bcolors.ENDC}\\n\")\n", - " print(e)\n", - " \n", - " def add_team(self,\n", - " repo: github.Repository.Repository, # target repository\n", - " team_slug: str, # team slug (name)\n", - " permission:str # `pull`, `push` or `admin`\n", - " ):\n", - " \"Add team to the repository with specified permission\"\n", - " team = self.get_team(team_slug)\n", - " team.add_to_repos(repo)\n", - " team.update_team_repository(repo, permission)\n", - " if self.verbosity != 0:\n", - " print(f\"Team {bcolors.OKGREEN} {team.name} {bcolors.ENDC} \"\n", - " f\"added to {bcolors.OKGREEN} {repo.name} {bcolors.ENDC} \"\n", - " f\"with permission {bcolors.OKGREEN} {permission} {bcolors.ENDC}\")\n", - "\n", - " def create_feedback_dir(self,\n", - " repo: github.Repository.Repository, # target repository\n", - " template_fp: str,\n", - " destination=\"feedback\" # directory path of the template file.\n", - " ):\n", - " \"Create feedback directory on local machine\"\n", - " os.makedirs(destination, exist_ok=True)\n", - " os.makedirs(f\"{destination}/{repo.name}\", exist_ok=True)\n", - " files = glob.glob(f\"{template_fp}/*\")\n", - " for file in files:\n", - " head = os.path.split(file)[1]\n", - " with open(file, \"r\") as f:\n", - " file = f.read()\n", - " with open(f\"{destination}/{repo.name}/{head}\", \"w+\") as f:\n", - " f.write(file)\n", - " if self.verbosity != 0:\n", - " print(f\"File {bcolors.OKGREEN}{head}{bcolors.ENDC} \"\n", - " f\"created at {bcolors.OKGREEN}{destination}/{repo.name}{bcolors.ENDC}\"\n", - " )\n", - "\n", - " def create_issue(self,\n", - " repo: github.Repository.Repository, # target repository\n", - " title: str, # title of the issue,\n", - " content: str # content of the issue\n", - " ) -> github.Issue.Issue: # open issue\n", - " \"Create GitHub issue to the target repository\"\n", - " issue = repo.create_issue(title=title, body=content)\n", - " if self.verbosity != 0:\n", - " print(f\"In the repo: {bcolors.OKGREEN}{repo.name}{bcolors.ENDC},\")\n", - " print(f\"Issue {bcolors.OKGREEN}{title}{bcolors.ENDC} Created!\")\n", - " return issue\n", - " \n", - " def create_issue_from_md(self,\n", - " repo: github.Repository.Repository, # target repository,\n", - " md_fp: str # file path of the feedback markdown file\n", - " ) -> github.Issue.Issue: # open issue\n", - " \"Create GitHub issue from markdown file.\"\n", - " md = \"\"\n", - " with open(md_fp, \"r\") as f:\n", - " md = f.read()\n", - " title = md.split(\"\\n\")[0][2:]\n", - " content = md\n", - " return self.create_issue(repo, title, content)\n", - " \n", - " def release_feedback(self,\n", - " md_filename: str, # feedback markdown file name\n", - " feedback_dir=\"feedback\", # feedback directory contains the markdown files\n", - " ):\n", - " \"Release feedback via GitHub issue from all the feedbacks in the feedback directory\"\n", - " repo_names = os.listdir(feedback_dir)\n", - " for repo_name in repo_names:\n", - " if repo_name == \".DS_Store\":\n", - " continue\n", - " try:\n", - " repo = self.org.get_repo(repo_name)\n", - " except Exception:\n", - " print(f\"Repo: {bcolors.WARNING}{repo_name} NOT FOUND!{bcolors.ENDC}\")\n", - " self.create_issue_from_md(repo, os.path.join(feedback_dir, repo_name, md_filename))\n", - " \n", - " def create_group_repo(self,\n", - " repo_name: str, # group repository name\n", - " collaborators: [str], # list of collaborators GitHub id\n", - " permission: str, # the permission of collaborators. `pull`, `push` or `admin`\n", - " rename_files=dict(), # dictionary of files renames {:}\n", - " repo_template=\"\", # If empty string, an empty repo will be created. Put in the format of \"/\"\n", - " private=True, # visibility of the created repository\n", - " description=\"\", # description for the GitHub repository\n", - " team_slug=\"\", # team slug, add to this repo\n", - " team_permission=\"\", # team permission to this repository `pull`, `push` or `admin`\n", - " feedback_dir=False, # whether to create a feedback directory for each repository created\n", - " feedback_template_fp=\"\", # the directory of the feedback template\n", - " ) -> github.Repository.Repository: # created repository\n", - " \"Create a Group Repository\"\n", - " repo = self.create_repo(repo_name, repo_template, private, description)\n", - " if self.verbosity != 0:\n", - " print(f\"Repo {bcolors.OKGREEN} {repo.name} {bcolors.ENDC} Created... Wait for 3 sec to updates\")\n", - " time.sleep(3)\n", - " for og_name, new_name in rename_files.items():\n", - " self.rename_files(repo, og_name, new_name)\n", - " for collaborator in collaborators:\n", - " self.add_collaborator(repo, collaborator, permission)\n", - " if team_slug != \"\":\n", - " self.add_team(repo, team_slug, team_permission)\n", - " if self.verbosity != 0:\n", - " print(f\"Group Repo: {bcolors.OKGREEN} {repo_name} {bcolors.ENDC} successfuly created!\")\n", - " print(f\"Repo URL: https://github.com/{self.org.login}/{repo_name}\")\n", - " if feedback_dir:\n", - " if feedback_template_fp == \"\":\n", - " raise ValueError(\"You have to specify the template files.\")\n", - " self.create_feedback_dir(repo, template_fp=feedback_template_fp)\n", - " return repo\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# GitHub Authentication\n", - "\n", - "View this [document](https://docs.google.com/document/d/1RvZnOX6nh0bXn6Zh4U-Yxazukuez5oGrtcP8mN-Roco/edit?usp=sharing) for how to set up your GitHub Personal Access Token. (TODO: be sure to specify scopes)\n", - "\n", - "Store the token information in a json file." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{\n", - " \"GitHub Token\": \"token\",\n", - " \"Canvas Token\": \"token\"\n", - "}\n", - "Successfully Authenticated. GitHub account: \u001b[92m scott-yj-yang \u001b[0m\n", - "Target Organization Set: \u001b[92m COGS118A \u001b[0m\n" - ] - } - ], - "source": [ - "credentials_fp = \"credentials.json\"\n", - "with open(credentials_fp, \"r\") as f:\n", - " # take a look at the token\n", - " print(f.read())\n", - "credentials_fp = \"../../credentials.json\" #| hide_line\n", - "g = GitHubGroup(credentials_fp=credentials_fp, org=\"COGS118A\", verbosity=1)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Optionally, you can instansiate a GitHubGroup object and authenticate yourself by calling the following method." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Successfully Authenticated. GitHub account: \u001b[92m scott-yj-yang \u001b[0m\n" - ] - } - ], - "source": [ - "g = GitHubGroup(verbosity=1) # you can set verbosity to 1 to see the current progress\n", - "g.auth_github(credentials_fp)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# GitHub Organization Settings\n", - "\n", - "Usually, you want to create students repositories under a course GitHub organization. To set the target organization, you can call the following function." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Target Organization Set: \u001b[92m COGS118A \u001b[0m\n" - ] - } - ], - "source": [ - "g.set_org(\"COGS118A\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Create GitHub Group in one Call" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Repo \u001b[92m API_test_repo \u001b[0m Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from \u001b[96m Checkpoint_groupXXX.ipynb \u001b[0m to \u001b[92m Checkpoint_group001.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m FinalProject_groupXXX.ipynb \u001b[0m to \u001b[92m FinalProject_group001.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m Proposal_groupXXX.ipynb \u001b[0m to \u001b[92m Proposal_group001.ipynb \u001b[0m\n", - "Added Collaborator: \u001b[92m jasongfleischer \u001b[0m to: \u001b[92m API_test_repo \u001b[0m with permission: \u001b[92m admin \u001b[0m\n", - "Team \u001b[92m Instructors_Sp23 \u001b[0m added to \u001b[92m API_test_repo \u001b[0m with permission \u001b[92m admin \u001b[0m\n", - "Group Repo: \u001b[92m API_test_repo \u001b[0m successfuly created!\n" - ] - } - ], - "source": [ - "repo = g.create_group_repo(\n", - " repo_name=\"API_test_repo\",\n", - " collaborators=[\"jasongfleischer\"],\n", - " permission=\"admin\",\n", - " repo_template=\"COGS118A/group_template\",\n", - " rename_files={\n", - " \"Checkpoint_groupXXX.ipynb\": \"Checkpoint_group001.ipynb\",\n", - " \"FinalProject_groupXXX.ipynb\": \"FinalProject_group001.ipynb\",\n", - " \"Proposal_groupXXX.ipynb\": \"Proposal_group001.ipynb\"\n", - " },\n", - " private=False,\n", - " description=\"Test Creation of Group Repo for COGS118A final project group\",\n", - " team_slug=\"Instructors_Sp23\",\n", - " team_permission=\"admin\"\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# take down the repo that we just created\n", - "repo.delete()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Lower Level Methods\n", - "\n", - "Belows are the components that faciliate the `GitHubGroup.create_group_repo`. If errors were prompted during group creation scripts, or simply you want more flexibility, you can call those components individually.\n", - "\n", - "## Create GitHub Repository\n", - "\n", - "_Note:_ `GtiHubGroup.create_group_repo` call this method to create a GitHub repository\n", - "\n", - "`personal_account` argument controls the location of the repository creation. If set to `False` (default), it will create repository in the target organization. If set to `True`, the new repository will be created in the personal GitHub account." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "_Note:_ `GtiHubGroup.create_group_repo` call this method to create a GitHub repository\n", - "\n", - "`personal_account` argument controls the location of the repository creation. If set to `False` (default), it will create repository in the target organization. If set to `True`, the new repository will be created in the personal GitHub account." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Repository(full_name=\"COGS118A/test-repo-organizational\")\n" - ] - } - ], - "source": [ - "# create a repo under the org\n", - "repo = g.create_repo(\n", - " \"test-repo-organizational\",\n", - " private=True\n", - ")\n", - "print(repo)\n", - "\n", - "repo.delete() #| hide_line" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As you can see from the full name `COGS118A/test-repo`, it is created under the organization of `COGS118A`.\n", - "\n", - "Alternatively, I can also create a new repository under my personal account." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Repository(full_name=\"scott-yj-yang/test-repo-personal\")\n" - ] - } - ], - "source": [ - "# create a repo under the my personal account\n", - "repo = g.create_repo(\n", - " \"test-repo-personal\",\n", - " private=True,\n", - " personal_account=True\n", - ")\n", - "print(repo)\n", - "\n", - "repo.delete() #| hide_line" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Repository(full_name=\"scott-yj-yang/test-repo-personal\")\n" - ] - } - ], - "source": [ - "# create a repo under the my personal account\n", - "repo = g.create_repo(\n", - " repo_name=\"test-repo-personal\",\n", - " private=True,\n", - " personal_account=True\n", - ")\n", - "print(repo)\n", - "\n", - "repo.delete() #| hide_line" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create Repository from Template\n", - "\n", - "You can also create a repository with a template repository. To do that, specify the full name of the template repository to the `repo_template` parameter. From the output, we can see that the repository `test-repo-from-template` is created with the template files. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Repository(full_name=\"COGS118A/test-repo-from-template\")\n", - "\n", - "This Repository contains... \n", - "\n", - "[ContentFile(path=\".gitignore\"),\n", - " ContentFile(path=\"Checkpoint_groupXXX.ipynb\"),\n", - " ContentFile(path=\"FinalProject_groupXXX.ipynb\"),\n", - " ContentFile(path=\"Proposal_groupXXX.ipynb\"),\n", - " ContentFile(path=\"README.md\")]\n" - ] - } - ], - "source": [ - "# create a repo from a template\n", - "repo = g.create_repo(\n", - " repo_name=\"test-repo-from-template\",\n", - " repo_template=\"COGS118A/group_template\",\n", - " private=True\n", - ")\n", - "print(repo)\n", - "\n", - "# wait 3 sec for repository creation.\n", - "time.sleep(3)\n", - "\n", - "print(\"\\nThis Repository contains... \\n\")\n", - "pprint(repo.get_contents(\".\"))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Rename Files in the Repository\n", - "\n", - "Usually, the template file names are generics and is not specific to a group. Under the context of group project, we want to rename each notebook files with thier group numbers. For example, for Group 1, we want to rename the file `Checkpoint_groupXXX.ipynb` to `Checkpoint_group001.ipynb`. To do that, we can use the following method." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "_Note:_ This method simply delete the old files and create new files with the updated file name. Therefore, 2 commits for each file is expected. (1 for delete, 1 for re-upload). For example, if I want to rename 5 files, I will have 10 commits need to do in total." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "File Successfully Renamed from \u001b[96m Checkpoint_groupXXX.ipynb \u001b[0m to \u001b[92m Checkpoint_group001.ipynb \u001b[0m\n", - "\n", - "This Repository contains... \n", - "\n", - "[ContentFile(path=\".gitignore\"),\n", - " ContentFile(path=\"Checkpoint_group001.ipynb\"),\n", - " ContentFile(path=\"FinalProject_groupXXX.ipynb\"),\n", - " ContentFile(path=\"Proposal_groupXXX.ipynb\"),\n", - " ContentFile(path=\"README.md\")]\n" - ] - } - ], - "source": [ - "g.rename_files(\n", - " repo=repo,\n", - " og_filename=\"Checkpoint_groupXXX.ipynb\",\n", - " new_filename=\"Checkpoint_group001.ipynb\"\n", - ")\n", - "# take a look at new files\n", - "print(\"\\nThis Repository contains... \\n\")\n", - "pprint(repo.get_contents(\".\"))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Notice that the files were renamed as expected." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Collaborators and Teams Access Repository\n", - "\n", - "Once the repository was created, we need to give the student team members proper permission to write to the repository and instructional team to be the admin of the repository. Those two functionalities are achieve by the following two methods." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Added Collaborator: \u001b[92m Andrina-iris \u001b[0m to: \u001b[92m test-repo-from-template \u001b[0m with permission: \u001b[92m write \u001b[0m\n" - ] - } - ], - "source": [ - "# add collaborator to the repository with push permission\n", - "g.add_collaborator(\n", - " repo=repo,\n", - " collaborator=\"Andrina-iris\",\n", - " permission=\"write\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In almost every quarter, at least 10 students forgot to accept the invitation to join the group repository. Historially, our instructional team handle those student's GitHub account on a case-by-case basis. However, with this module, it is possible to resent all pending and expired invitations to those students with one call." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Students will receive an email from GitHub with the freshly made, unexpired invitation to their group repository." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The list of pending invitation:\n", - "[NamedUser(login=\"Andrina-iris\")]\n", - "\u001b[93m Andrina-iris Invite Revoked Andrina-iris \u001b[0m\n", - "Added Collaborator: \u001b[92m Andrina-iris \u001b[0m to: \u001b[92m test-repo-from-template \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to Andrina-iris \u001b[0m\n" - ] - }, - { - "data": { - "text/plain": [ - "[NamedUser(login=\"Andrina-iris\")]" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "g.resend_invitations(repo)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Additionally, course staffs should be in a team in the GitHub Organization in order to manage student repositories." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Team \u001b[92m Instructors_Sp23 \u001b[0m added to \u001b[92m test-repo-from-template \u001b[0m with permission \u001b[92m admin \u001b[0m\n" - ] - } - ], - "source": [ - "g.add_team(\n", - " repo=repo,\n", - " team_slug=\"Instructors_Sp23\",\n", - " permission=\"admin\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If you have all the students repositories under the same team, you can use the following method to resent all pending invitations from all the repositories under that team." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Repository \u001b[96m AssignmentNotebooksSource_SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[]\n", - "Repository \u001b[96m AssignmentNotebooks_SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[]\n", - "Repository \u001b[96m DiscussionSectionNotebooks \u001b[0m:\n", - "The list of pending invitation:\n", - "[]\n", - "Repository \u001b[96m Dockerfiles \u001b[0m:\n", - "The list of pending invitation:\n", - "[]\n", - "Repository \u001b[96m Lectures \u001b[0m:\n", - "The list of pending invitation:\n", - "[]\n", - "Repository \u001b[96m Notebooks \u001b[0m:\n", - "The list of pending invitation:\n", - "[]\n", - "Repository \u001b[96m test-repo-from-template \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"Andrina-iris\")]\n", - "\u001b[93m Andrina-iris Invite Revoked Andrina-iris \u001b[0m\n", - "Added Collaborator: \u001b[92m Andrina-iris \u001b[0m to: \u001b[92m test-repo-from-template \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to Andrina-iris \u001b[0m\n" - ] - } - ], - "source": [ - "g.resent_invitations_team_repos(team_slug=\"Instructors_Sp23\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Project Feedback and GitHub Issues\n", - "\n", - "Thanks for the group nature of the created repository, we can also use the created GitHub repository to create group project feedback to students via GitHub issues." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can create a directory under the instructors' computer. Each directory will have a folder for each group repository with the template file. The template files are usually the rubrics for the project grading.\n", - "\n", - "TODO: add file superlink to the repo to see the examples." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "File \u001b[92mcheckpoint_feedback.md\u001b[0m created at \u001b[92mfeedback/test-repo-from-template\u001b[0m\n", - "File \u001b[92mproposal_feedback.md\u001b[0m created at \u001b[92mfeedback/test-repo-from-template\u001b[0m\n", - "File \u001b[92mfinal_project_feedback.md\u001b[0m created at \u001b[92mfeedback/test-repo-from-template\u001b[0m\n" - ] - }, - { - "data": { - "text/plain": [ - "['checkpoint_feedback.md', 'proposal_feedback.md', 'final_project_feedback.md']" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "g.create_feedback_dir(repo, \"feedback_template\")\n", - "# take a look at the generated tempalte files.\n", - "os.listdir(f\"feedback/{repo.name}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "After created the project feedback, instructional team can modify the markdown rubrics and provide feedback to students via GitHub issue." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "In the repo: \u001b[92mtest-repo-from-template\u001b[0m,\n", - "Issue \u001b[92mTest Issue\u001b[0m Created!\n" - ] - }, - { - "data": { - "text/plain": [ - "Issue(title=\"Test Issue\", number=1)" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# create a test issue\n", - "issue = g.create_issue(repo, \"Test Issue\", \"This is just a test issue.\")\n", - "issue" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Alternatively, you can create issue from markdown files, where it contains all the comments and rubrics for this project. The first line of the markdown file will be the title of the github issue." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "In the repo: \u001b[92mtest-repo-from-template\u001b[0m,\n", - "Issue \u001b[92m Project Proposal Feedback\u001b[0m Created!\n" - ] - }, - { - "data": { - "text/plain": [ - "Issue(title=\" Project Proposal Feedback\", number=2)" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "issue = g.create_issue_from_md(repo, \"feedback_template/proposal_feedback.md\")\n", - "issue" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Release Feedback in Batch\n", - "\n", - "During projct grading, we will handle numerous groups at once. Once the instructor team finish modifying the markdown file for each group, we can release feedback to each of the project repository, as long as they have the same file name. For example, we have finish grading the final project, in the file name of `feedback//final_project_feedback.md`, and they are ready to be publish, we can call the following function to create issue in batch." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "In the repo: \u001b[92mtest-repo-from-template\u001b[0m,\n", - "Issue \u001b[92m Final Project Feedback\u001b[0m Created!\n" - ] - } - ], - "source": [ - "g.release_feedback(\"final_project_feedback.md\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "repo.delete()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "python3", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/nbs/api/03_canvas.ipynb b/nbs/api/03_canvas.ipynb deleted file mode 100644 index e2941e2..0000000 --- a/nbs/api/03_canvas.ipynb +++ /dev/null @@ -1,617 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Canvas Group Creation\n", - "\n", - "> Create canvas group via Canvas API" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from canvasapi import Canvas\n", - "from github import Github\n", - "import canvasapi\n", - "import json\n", - "import requests\n", - "import time\n", - "import sys\n", - "import numpy as np\n", - "import pandas as pd\n", - "from io import StringIO" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class bcolors:\n", - " HEADER = '\\033[95m'\n", - " OKBLUE = '\\033[94m'\n", - " OKCYAN = '\\033[96m'\n", - " OKGREEN = '\\033[92m'\n", - " WARNING = '\\033[93m'\n", - " FAIL = '\\033[91m'\n", - " ENDC = '\\033[0m'\n", - " BOLD = '\\033[1m'\n", - " UNDERLINE = '\\033[4m'" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class CanvasGroup():\n", - " def __init__(self,\n", - " credentials_fp = \"\", # credential file path. [Template of the credentials.json](https://github.com/FleischerResearchLab/CanvasGroupy/blob/main/nbs/credentials.json)\n", - " API_URL=\"https://canvas.ucsd.edu\", # the domain name of canvas\n", - " course_id=\"\", # Course ID, can be found in the course url\n", - " group_category=\"\", # target group category (set) of interests\n", - " verbosity=1 # Controls the verbosity: 0 = Silent, 1 = print all messages\n", - " ):\n", - " \"Initialize Canvas Group within a Group Set and its appropriate memberships\"\n", - " self.API_URL = API_URL\n", - " self.canvas = None\n", - " self.course = None\n", - " self.group_category = None\n", - " self.group_categories = None\n", - " self.groups = None\n", - " self.users = None\n", - " self.email_to_canvas_id = None\n", - " self.canvas_id_to_email = None\n", - " self.email_to_name = None\n", - " self.API_KEY = None\n", - " self.github = None\n", - " self.credentials_fp = None\n", - " self.groups = None\n", - " self.group_to_emails = None\n", - " self.assignment = None\n", - " self.verbosity = verbosity\n", - " \n", - " # initialize by the input parameter\n", - " if credentials_fp != \"\":\n", - " self.auth_canvas(credentials_fp)\n", - " if course_id != \"\":\n", - " self.set_course(course_id)\n", - " self.get_group_categories()\n", - " if group_category != \"\":\n", - " self.set_group_category(group_category)\n", - " \n", - " def auth_canvas(self,\n", - " credentials_fp: str # the Authenticator key generated from canvas\n", - " ):\n", - " \"Authorize the canvas module with API_KEY\"\n", - " self.credentials_fp = credentials_fp\n", - " with open(credentials_fp, \"r\") as f:\n", - " credentials = json.load(f)\n", - " self.API_KEY = credentials[\"Canvas Token\"]\n", - " self.canvas = Canvas(self.API_URL, self.API_KEY)\n", - " # test authorization\n", - " _ = self.canvas.get_activity_stream_summary()\n", - " if self.verbosity != 0:\n", - " print(f\"{bcolors.OKGREEN}Authorization Successful!{bcolors.ENDC}\")\n", - " \n", - " def set_course(self, \n", - " course_id: int # the course id of the target course\n", - " ):\n", - " \"Set the target course by the course ID\"\n", - " self.course = self.canvas.get_course(course_id)\n", - " if self.verbosity != 0:\n", - " print(f\"Course Set: {bcolors.OKGREEN} {self.course.name} {bcolors.ENDC}\")\n", - " print(f\"Getting List of Users... This might take a while...\")\n", - " self.users = list(self.course.get_users(enrollment_type=['student']))\n", - " if self.verbosity != 0:\n", - " print(f\"Users Fetch Complete! The course has {bcolors.OKBLUE}{len(self.users)}{bcolors.ENDC} students.\")\n", - " self.email_to_canvas_id = {}\n", - " self.canvas_id_to_email = {}\n", - " self.email_to_name = {}\n", - " for u in self.users:\n", - " try:\n", - " self.email_to_canvas_id[u.email.split(\"@\")[0]] = u.id\n", - " self.canvas_id_to_email[u.id] = u.email.split(\"@\")[0]\n", - " self.email_to_name[u.email.split(\"@\")[0]] = u.short_name\n", - " except Exception:\n", - " if self.verbosity != 0:\n", - " print(f\"{bcolors.WARNING}Failed to Parse email and id\"\n", - " f\" for {bcolors.UNDERLINE}{u.short_name}{bcolors.ENDC}{bcolors.ENDC}\")\n", - "\n", - " def link_assignment(self,\n", - " assignment_id: int # assignment id, found at the url of assignmnet tab\n", - " ) -> canvasapi.assignment.Assignment: # target assignment\n", - " \"Link the target assignment on canvas\"\n", - " assignment = self.course.get_assignment(assignment_id)\n", - " if self.verbosity != 0:\n", - " print(f\"Assignment {bcolors.OKGREEN+assignment.name+bcolors.ENDC} Link!\")\n", - " self.assignment = assignment\n", - " return assignment\n", - "\n", - " def post_grade(self,\n", - " student_id: int, # canvas student id of student. found in self.email_to_canvas_id\n", - " grade: float, # grade of that assignment\n", - " text_comment=\"\", # text comment of the submission. Can feed\n", - " force=False, # whether force to post grade for all students. If False (default), it will skip post for the same score.\n", - " ) -> canvasapi.submission.Submission: # created submission\n", - " \"Post grade and comment to canvas to the target assignment\"\n", - " submission = self.assignment.get_submission(student_id)\n", - " if not force and submission.score == grade:\n", - " if self.verbosity != 0:\n", - " print(f\"Grade for {bcolors.OKGREEN+self.canvas_id_to_email[student_id]+bcolors.ENDC} did not change.\\n\"\n", - " f\"{bcolors.OKCYAN}Skipped{bcolors.ENDC}.\\n\"\n", - " )\n", - " return\n", - " edited = submission.edit(\n", - " submission={\n", - " 'posted_grade': grade\n", - " }, comment={\n", - " 'text_comment': text_comment\n", - " }\n", - " )\n", - " if self.verbosity != 0:\n", - " print(f\"Grade for {bcolors.OKGREEN+self.canvas_id_to_email[student_id]+bcolors.ENDC} Posted!\")\n", - " return edited\n", - "\n", - " def get_email_by_name(self,\n", - " name_fussy: str # search by first name or last name of a student\n", - " ) -> str: # email of a search student\n", - " name_fussy = name_fussy.lower()\n", - " for email, name in self.email_to_name.items():\n", - " if name_fussy in name.lower():\n", - " return email\n", - " raise ValueError(f\"Name {name_fussy} Not Found.\")\n", - " \n", - " \n", - " def set_group_category(self,\n", - " category_name: str # the target group category\n", - " ) -> canvasapi.group.GroupCategory: # target group category object\n", - " _ = self.get_group_categories()\n", - " try:\n", - " self.group_category = self.group_categories[category_name]\n", - " except KeyError:\n", - " raise KeyError(f\"{category_name} did not found in the group categories. \"\n", - " f\"Try to create one with CanvasGroup.create_group_category\")\n", - " if self.verbosity != 0:\n", - " print(f\"Setting Group Category... \")\n", - " self.groups = list(self.group_category.get_groups())\n", - " self.group_to_emails = {\n", - " group.name: [\n", - " u.login_id for u in list(group.get_users())\n", - " ] for group in self.groups}\n", - " if self.verbosity != 0:\n", - " print(f\"Group Category: {bcolors.OKGREEN+category_name+bcolors.ENDC} Set!\")\n", - " return self.group_category\n", - " \n", - " def get_groups(self,\n", - " category_name=\"\" # the target group category. If not provided, will look for self.group_category\n", - " ) -> dict: # {group_name: [student_emails]}\n", - " if category_name != \"\":\n", - " self.set_group_category(category_name)\n", - " return self.group_to_emails\n", - " if self.group_category is None:\n", - " raise ValueError(\"Group Category is not set\")\n", - " return self.group_to_emails\n", - " \n", - " def get_course(self):\n", - " return self.course\n", - " \n", - " def get_group_categories(self) -> dict: # return a name / group category object\n", - " \"Grab all existing group categories (group set) in this course\"\n", - " categories = list(self.course.get_group_categories())\n", - " self.group_categories = {cat.name: cat for cat in categories}\n", - " return {cat.name: cat for cat in categories}\n", - " \n", - " def create_group_category(self,\n", - " params: dict # the parameter of canvas group category API @ [this link](https://canvas.instructure.com/doc/api/group_categories.html#method.group_categories.create)\n", - " ) -> canvasapi.group.GroupCategory: # the generated group category object\n", - " \"Create group category (group set) in this course\"\n", - " self.group_category = self.course.create_group_category(**params)\n", - " return self.group_category\n", - " \n", - " def create_group(self,\n", - " params: dict, #the parameter of canvas group create API at [this link](https://canvas.instructure.com/doc/api/groups.html#method.groups.create)\n", - " ) -> canvasapi.group.Group: # the generated target group object\n", - " \"Create canvas group under the target group category\"\n", - " if self.group_category is None:\n", - " raise ValueError(\"Have you specified or create a group category (group set)?\")\n", - " group = self.group_category.create_group(**params)\n", - " if self.verbosity != 0:\n", - " print(f\"In Group Set: {bcolors.OKBLUE+self.group_category.name+bcolors.ENDC},\")\n", - " print(f\"Group {bcolors.OKGREEN+params['name']+bcolors.ENDC} Created!\")\n", - " return group\n", - " \n", - " def join_canvas_group(self,\n", - " group: canvasapi.group.Group, # the group that students will join\n", - " group_members:[str], # list of group member's SIS Login (email prefix, before the @.)\n", - " ) -> [str]: # list of unsuccessful join\n", - " \"Add membership access of each group member into the group\"\n", - " unsuccessful_join = []\n", - " for group_member in group_members:\n", - " try:\n", - " canvas_id = self.email_to_canvas_id[group_member]\n", - " group.create_membership(canvas_id)\n", - " if self.verbosity != 0:\n", - " print(f\"Member {bcolors.OKGREEN}{group_member}{bcolors.ENDC} Joined group {bcolors.OKGREEN}{group.name}{bcolors.ENDC}\")\n", - " except KeyError as e:\n", - " unsuccessful_join.append(group_member)\n", - " print(f\"Error adding student {bcolors.WARNING+group_member+bcolors.ENDC} \\n into group {group.name}\")\n", - " print(e)\n", - " return unsuccessful_join\n", - " \n", - " def fetch_username_from_quiz(self,\n", - " quiz_id: int, # quiz id of the username quiz\n", - " col_index=7, # canvas quiz generated csv's question field column index\n", - " ) -> dict: # {SIS Login ID: github username} dictionary\n", - " \"Fetch the GitHub user name from the canvas quiz\"\n", - " header = {'Authorization': 'Bearer ' + self.API_KEY}\n", - " quiz = self.course.get_quiz(quiz_id)\n", - " if self.verbosity != 0:\n", - " print(f\"Quiz: {bcolors.OKGREEN+quiz.title+bcolors.ENDC} \"\n", - " f\"fetch! \\nGenerating Student Analaysis...\"\n", - " )\n", - " report = quiz.create_report(\"student_analysis\")\n", - " progress_url = report.progress_url\n", - " completed = False\n", - " while not completed:\n", - " status = requests.get(progress_url, headers = header).json()\n", - " if self.verbosity != 0:\n", - " self._progress(status[\"completion\"])\n", - " time.sleep(0.1)\n", - " if status[\"completion\"] == 100:\n", - " completed = True\n", - " if self.verbosity != 0:\n", - " print(f\"\\n{bcolors.OKGREEN}Report Generated!{bcolors.ENDC}\")\n", - " # use requests to download the file \n", - " file_url = quiz.create_report(\"student_analysis\").file[\"url\"]\n", - " response = requests.get(file_url, headers=header)\n", - " file = StringIO(response.content.decode())\n", - " # use pandas to parse the response csv\n", - " df = pd.read_csv(file, delimiter=\",\")\n", - " col = list(df.columns)\n", - " # rename column\n", - " if self.verbosity != 0:\n", - " print(f\"The Question asked is {bcolors.OKBLUE}{col[col_index]}{bcolors.ENDC}. \\n\"\n", - " f\"Make sure this is the correct question where you asked student for their GitHub id.\\n\"\n", - " f\"If you need to change the index of columns, change the col_index argument of this call.\"\n", - " )\n", - " col[col_index] = \"GitHub Username\"\n", - " df.columns = col\n", - " small = df[[\"id\", \"GitHub Username\"]].copy()\n", - " small[\"email\"] = small[\"id\"].apply(lambda x: self.canvas_id_to_email[x])\n", - " small = small[[\"email\", \"GitHub Username\"]].set_index(\"email\")\n", - " return small.to_dict()[\"GitHub Username\"]\n", - " \n", - " def _check_single_github_username(self,\n", - " email:str, # Student email\n", - " github_username:str, # student input we want to test\n", - " ) -> bool: # whether the username is valid\n", - " \"Check a single GitHub username on GitHub\"\n", - " if self.credentials_fp is None:\n", - " raise ValueError(\"Credentials not set. Set it via self.auth_canvas\")\n", - " if self.github is None:\n", - " # if the GitHub object has not been initialized\n", - " with open(self.credentials_fp, \"r\") as f:\n", - " credentials = json.load(f)\n", - " github_token = credentials[\"GitHub Token\"]\n", - " self.github = Github(github_token)\n", - " try:\n", - " self.github.get_user(github_username)\n", - " except Exception as e:\n", - " print(f\"User: {bcolors.WARNING+github_username+bcolors.ENDC} Not Found on GitHub\")\n", - " return False\n", - " return True\n", - "\n", - " def check_github_usernames(self,\n", - " github_usernames:dict, # {email: github username} of student inputs, generated from self.fetch_username_from_quiz\n", - " send_canvas_email=False, # whether send a reminder for students who have an invalid GitHub username\n", - " send_undone_reminder=False, # send quiz undone reminder using canvas email\n", - " quiz_url=\"\", # include a quiz url in the conversation for student to quickly complete the quiz.\n", - " ) -> dict: # {email: github username} of unreasonable GitHub id\n", - " \"batch check GitHub username from student inputs.\"\n", - " unsuccessful = {}\n", - " for email, github_username in github_usernames.items():\n", - " valid = self._check_single_github_username(email, github_username)\n", - " if not valid:\n", - " unsuccessful[email] = github_username\n", - " if send_canvas_email:\n", - " self.create_conversation(\n", - " self.email_to_canvas_id[email],\n", - " subject=\"Unidentifiable GitHub Username\",\n", - " body=(f\"Hi {email}, \\n Your GitHub Username: {github_username} \"\n", - " f\"is unidentifiable on github.com. \\n Please complete the quiz GitHub Username Quiz again.\\n\"\n", - " f\"{quiz_url} \\n\"\n", - " f\"Thank You.\"\n", - " )\n", - " )\n", - " if self.verbosity != 0:\n", - " print(f\"{bcolors.OKGREEN}Notification Sent!{bcolors.ENDC}\")\n", - " if send_undone_reminder:\n", - " submitted = github_usernames.keys()\n", - " for email in self.email_to_canvas_id.keys():\n", - " if email not in submitted:\n", - " if self.verbosity != 0:\n", - " print(f\"Student {bcolors.WARNING}{email}{bcolors.ENDC} did not\"\n", - " f\" submit their github username.\"\n", - " )\n", - " # means student did not submit the quiz\n", - " if send_canvas_email:\n", - " self.create_conversation(\n", - " self.email_to_canvas_id[email],\n", - " subject=\"GitHub Username Quiz Not Completed\",\n", - " body=(f\"Hi {email}, \\n You did not complete the GitHub Quiz.\"\n", - " f\"\\n Please complete the quiz GitHub Username Quiz ASAP\\n\"\n", - " f\"{quiz_url} \\n\"\n", - " f\"Thank You.\"\n", - " )\n", - " )\n", - " if self.verbosity != 0:\n", - " print(f\"{bcolors.OKGREEN}Notification Sent!{bcolors.ENDC}\")\n", - " return unsuccessful\n", - " \n", - " def _progress(self,\n", - " percentage:int # percentage of the progress\n", - " ):\n", - " sys.stdout.write('\\r')\n", - " # the exact output you're looking for:\n", - " sys.stdout.write(\"[%-20s] %d%%\" % ('='*int(percentage//5), percentage))\n", - " sys.stdout.flush()\n", - " \n", - " def assign_canvas_group(self,\n", - " group_name: str, # group name, display on canvas\n", - " group_members:[str], # list of group member's SIS Login\n", - " in_group_category: str, # specify which group category the group belongs to\n", - " ) -> (canvasapi.group.Group, [str]): # list of unsuccessful join\n", - " \"Create new groups and assign group member into the class in the `self.group_category`\"\n", - " self.set_group_category(in_group_category)\n", - " group = self.create_group({\"name\": group_name})\n", - " unsuccessful_join = self.join_canvas_group(group, group_members)\n", - " if self.verbosity != 0:\n", - " print(f\"Group {bcolors.OKGREEN+group_name+bcolors.ENDC} created!\")\n", - " return group, unsuccessful_join\n", - " \n", - " def create_conversation(self,\n", - " recipients:int, # recipient ids. These may be user ids or course/group ids prefixed with ‘course_’ or ‘group_’ respectively.\n", - " subject:str, # subject of the conversation\n", - " body:str, # The message to be sent\n", - " ) -> canvasapi.conversation.Conversation: # created conversation\n", - " \"Create a conversation with the target user\"\n", - " conv = self.canvas.create_conversation(\n", - " [recipients],\n", - " body=body,\n", - " subject=subject,\n", - " context_code=f\"course_{self.course.id}\",\n", - " )\n", - " return conv\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "# Lower Level Methods" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Alternatively, you can manually set them after you created the `CanvasGroup` object" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "test_eq(course_id, cg.course.id)\n", - "assert isinstance(cg.canvas, canvasapi.canvas.Canvas)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The following tutorial and examples demonstrates how to create and set a Group Category within a course context." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create / Set Target Group Category (Set)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['Final Project', 'Student Groups', 'Test']" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# list all current group category\n", - "list(cg.get_group_categories().keys())" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "params = {\n", - " \"name\": \"TEST-GroupProject\",\n", - " \"group_limit\": 5\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# create a new category\n", - "group_category = cg.create_group_category(params)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['Final Project', 'Student Groups', 'Test', 'TEST-GroupProject']" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Check whether we successfully create a new group\n", - "list(cg.get_group_categories().keys())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When a group category is already created, we cannot create another group with the same name. To switch the group category destination of group creation, use the `set_group_category` methods." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "group_category = cg.set_group_category(\"TEST-GroupProject\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create a Group Inside the Target Group Category" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "In Group Set: \u001b[94mTEST-GroupProject\u001b[0m,\n", - "Group \u001b[92mTEST-GROUP1\u001b[0m Created!\n", - "TEST-GROUP1 (122854)\n" - ] - } - ], - "source": [ - "params = {\n", - " \"name\": \"TEST-GROUP1\",\n", - " \"join_level\": \"invitation_only\"\n", - "}\n", - "group1 = cg.create_group(params)\n", - "print(group1)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Assign Student to the Group" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "member1 = \"email\"\n", - "member1 = \"grader-cogs118a-01\" #| hide_line\n", - "\n", - "cg.join_canvas_group(group1, [member1])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'name': 'TEST-GroupProject',\n", - " 'self_signup': None,\n", - " 'auto_leader': None,\n", - " 'id': 16283,\n", - " 'role': None,\n", - " 'group_limit': 5,\n", - " 'created_at': '2023-04-27T23:29:36Z',\n", - " 'context_type': 'Course',\n", - " 'course_id': 45059,\n", - " 'protected': False,\n", - " 'allows_multiple_memberships': False,\n", - " 'is_member': False}" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# tear down the example project\n", - "cg.get_group_categories()[\"TEST-GroupProject\"].delete()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "python3", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/nbs/api/04_project_grading.ipynb b/nbs/api/04_project_grading.ipynb deleted file mode 100644 index bbea9d6..0000000 --- a/nbs/api/04_project_grading.ipynb +++ /dev/null @@ -1,166 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "38120b0d", - "metadata": {}, - "source": [ - "# Project Grading\n", - "\n", - "> Manage grading rubrics and grade posting on the groups grade." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3b542387", - "metadata": {}, - "outputs": [], - "source": [ - "from CanvasGroupy import *\n", - "import github\n", - "import canvasapi\n", - "from ast import literal_eval" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class bcolors:\n", - " HEADER = '\\033[95m'\n", - " OKBLUE = '\\033[94m'\n", - " OKCYAN = '\\033[96m'\n", - " OKGREEN = '\\033[92m'\n", - " WARNING = '\\033[93m'\n", - " FAIL = '\\033[91m'\n", - " ENDC = '\\033[0m'\n", - " BOLD = '\\033[1m'\n", - " UNDERLINE = '\\033[4m'" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class Grading:\n", - " def __init__(self,\n", - " ghg:GitHubGroup=None, # authenticated GitHub object\n", - " cg:CanvasGroup=None, # authenticated canvas object\n", - " ):\n", - " self.ghg = ghg\n", - " self.cg = cg\n", - "\n", - " def create_issue_from_md(self,\n", - " repo:github.Repository.Repository, # target repository to create issue\n", - " md_fp: str # file path of the feedback markdown file\n", - " ) -> github.Issue.Issue: # open issue\n", - " \"Create GitHub issue from markdown file.\"\n", - " return self.ghg.create_issue_from_md(repo, md_fp)\n", - "\n", - " def fetch_issue(self,\n", - " repo:github.Repository.Repository, # target repository to fetch issue\n", - " component:str, # the component of the project grading, let it be proposal/checkpoint/final. Need to match the issue's title\n", - " ) -> github.Issue.Issue:\n", - " \"Fetch the issue on GitHub repo and choose related one\"\n", - " for issue in list(repo.get_issues()):\n", - " if component.lower() in issue.title.lower():\n", - " return issue\n", - " raise ValueError(f\"Issue related to {component} did not found.\")\n", - "\n", - " def parse_score_from_issue(self,\n", - " repo:github.Repository.Repository, # target repository to create issue\n", - " component:str, # The component of the project grading, let it be proposal/checkpoint/final. Need to match the issue's title\n", - " ) -> int: # the fetched score of that component\n", - " \"parse score from the template issue\"\n", - " issue = self.fetch_issue(repo, component)\n", - " body = issue.body\n", - " score = 0\n", - " for line in body.split(\"\\n\"):\n", - " if \"Score =\" in line and \"[comment]\" not in line:\n", - " score = literal_eval(line.split(\"=\")[1])\n", - " return score\n", - " raise ValueError(f\"Score Parse Error. please check the score format on github. \\n\"\n", - " f\"Issue URL: {issue.url}\")\n", - "\n", - " def update_canvas_score(self,\n", - " group_name:str, # target group name on a canvas group\n", - " assignment_id, # assignment id of the related component\n", - " score:float, # score of that component\n", - " issue:github.Issue.Issue=None,\n", - " post=False, # whether to post score via api. for testing purposes\n", - " ):\n", - " \"Post score to canvas\"\n", - " if self.cg.group_category is None:\n", - " raise ValueError(\"CanvasGroup's group_category not set.\")\n", - " members = self.cg.group_to_emails[group_name]\n", - " self.cg.link_assignment(assignment_id)\n", - " for member in members:\n", - " student_id = self.cg.email_to_canvas_id[member]\n", - " text_comment = f\"Group: {group_name}\"\n", - " if issue is not None:\n", - " text_comment += f\"\\nView at {issue.url.replace('https://api.github.com/repos', 'https://github.com')}\"\n", - " if post:\n", - " self.cg.post_grade(\n", - " student_id=student_id,\n", - " grade=score,\n", - " text_comment=text_comment\n", - " )\n", - " else:\n", - " print(f\"{bcolors.WARNING}Post Disable{bcolors.ENDC}\")\n", - " print(f\"For student: {member}, the score is {score}\")\n", - " print(f\"Comments: {text_comment}\")\n", - "\n", - " def check_graded(self,\n", - " repo:github.Repository.Repository, # target repository to grade\n", - " component:str, # The component of the project grading, let it be proposal/checkpoint/final. Need to match the issue's title\n", - " ) -> bool: # Whether the repo is graded.\n", - " \"Check whether a component for a group project is graded\"\n", - " score = self.parse_score_from_issue(repo, component)\n", - " if score is ...:\n", - " print(f\"{bcolors.WARNING}{repo.name}'s {component} Not Graded. {bcolors.ENDC}\")\n", - " return False\n", - " print(f\"{bcolors.OKGREEN}{repo.name}'s {component} Graded. {bcolors.ENDC}\")\n", - " return True\n", - "\n", - " def grade_project(self,\n", - " repo:github.Repository.Repository, # target repository to grade\n", - " component:str, # The component of the project grading, let it be proposal/checkpoint/final. Need to match the issue's title\n", - " assignment_id:int, # assignment id that link to that component of the project\n", - " canvas_group_name:dict=None, # mapping from GitHub repo name to Group name. If not specified, the repository name will be used.\n", - " canvas_group_category:str=None, # canvas group category (set)\n", - " post:bool=False, # whether to post score via api. For testing purposes\n", - " ):\n", - " \"grade github project components\"\n", - " # set the category if you haven't\n", - " if canvas_group_category is not None:\n", - " self.cg.set_group_category(canvas_group_category)\n", - " score = self.parse_score_from_issue(repo, component)\n", - " # create mapping from GitHub repo name to canvas group name\n", - " if canvas_group_name is not None:\n", - " group_name = canvas_group_name[repo.name]\n", - " else:\n", - " group_name = repo.name\n", - " graded = self.check_graded(repo, component)\n", - " if graded:\n", - " return\n", - " issue = self.fetch_issue(repo, component)\n", - " self.update_canvas_score(group_name, assignment_id, score, issue, post)\n", - "\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "python3", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/nbs/api/index.qmd b/nbs/api/index.qmd deleted file mode 100644 index b5f8c1e..0000000 --- a/nbs/api/index.qmd +++ /dev/null @@ -1,11 +0,0 @@ ---- -order: 2 -title: api -listing: - fields: [title, description] - type: table - sort-ui: false - filter-ui: false ---- - -This section contains API details for each of CanvasGroupy python submodules. This reference documentation is mainly useful for people looking to customise or build on top of CanvasGroupy, or wanting detailed information about how this module works. diff --git a/nbs/credentials.json b/nbs/credentials.json deleted file mode 100644 index f8644d0..0000000 --- a/nbs/credentials.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "GitHub Token": "token", - "Canvas Token": "token" -} \ No newline at end of file diff --git a/nbs/data/195_class.csv b/nbs/data/195_class.csv deleted file mode 100644 index 0e45446..0000000 --- a/nbs/data/195_class.csv +++ /dev/null @@ -1,6 +0,0 @@ -ID,GPA,Gender,Ethnicity,Major,Math Skills,Programming Skills,Statistic Skills -dol005,3.9,M,-,COGS,5,5,3 -nmackler,3.4,F,B,COGS,2,3,2 -xiw013,3.1,M,-,DSC,3,1,5 -jiz100,2.5,M,H,MATH,2,2,1 -jiz088,2.1,F,-,DSC,4,4,4 \ No newline at end of file diff --git a/nbs/data/195_group_specification.groupeng b/nbs/data/195_group_specification.groupeng deleted file mode 100644 index 441948f..0000000 --- a/nbs/data/195_group_specification.groupeng +++ /dev/null @@ -1,23 +0,0 @@ -classlist : 195_class.csv - -student_identifier : ID - -group_size : 3- - -- cluster : Gender - values : F - -- cluster : Ethnicity - values : (B = H) - -- distribute : Major - values : COGS, DSC, MATH - -- distribute : Math Skills - values : 5, 4 -- distribute : Programming Skills - value : 5, 4 -- distribute : Statistic Skills - value : 5, 4 - -- balance : GPA \ No newline at end of file diff --git a/nbs/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_classlist.csv b/nbs/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_classlist.csv deleted file mode 100644 index 4cc795b..0000000 --- a/nbs/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_classlist.csv +++ /dev/null @@ -1,7 +0,0 @@ -ID,GPA,Gender,Ethnicity,Major,Math Skills,Programming Skills,Statistic Skills,Group Number -nmackler,3.4,F,B,COGS,2,3,2,1 -jiz100,2.5,M,H,MATH,2,2,1,1 -jiz088,2.1,F,-,DSC,4,4,4,1 -dol005,3.9,M,-,COGS,5,5,3,2 -xiw013,3.1,M,-,DSC,3,1,5,2 -phantom,None,None,None,None,None,None,None,2 diff --git a/nbs/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_details.csv b/nbs/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_details.csv deleted file mode 100644 index 4432d5c..0000000 --- a/nbs/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_details.csv +++ /dev/null @@ -1,9 +0,0 @@ -ID,GPA,Gender,Ethnicity,Major,Math Skills,Programming Skills,Statistic Skills,Group Number,,group GPA mean,Rules Broken -nmackler,3.4,F,B,COGS,2,3,2,1 -jiz100,2.5,M,H,MATH,2,2,1,1 -jiz088,2.1,F,-,DSC,4,4,4,1 -summary,,,,,,,,,,2.6666666666666665,Cluster: Ethnicity,Balance: GPA - -dol005,3.9,M,-,COGS,5,5,3,2 -xiw013,3.1,M,-,DSC,3,1,5,2 -phantom,None,None,None,None,None,None,None,2 diff --git a/nbs/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_groups.csv b/nbs/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_groups.csv deleted file mode 100644 index 31e9161..0000000 --- a/nbs/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_groups.csv +++ /dev/null @@ -1,2 +0,0 @@ -Group 1, jiz088, jiz100, nmackler -Group 2, dol005, xiw013 diff --git a/nbs/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_groups.txt b/nbs/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_groups.txt deleted file mode 100644 index 3b83a96..0000000 --- a/nbs/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_groups.txt +++ /dev/null @@ -1,7 +0,0 @@ -Group 1 -jiz088 -jiz100 -nmackler -Group 2 -dol005 -xiw013 diff --git a/nbs/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_statistics.txt b/nbs/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_statistics.txt deleted file mode 100644 index 2115a6e..0000000 --- a/nbs/data/groups_195_group_specification_2023-05-17_10-23-31/195_group_specification_statistics.txt +++ /dev/null @@ -1,23 +0,0 @@ -Ran GroupEng on: ../data/195_group_specification.groupeng with students from 195_class.csv - -Made 2 groups - -0 groups failed: - -1 groups failed: - -0 groups failed: - -0 groups failed: - -0 groups failed: - -0 groups failed: - -2 groups failed:: Class GPA Mean: 3.00, Class GPA Std Dev: 0.64, Std Dev of Group GPA Means: 0.42 - -Group Summaries ---------------- -Group 1: Failed , Failed -Group 2: Failed - diff --git a/nbs/dir.txt b/nbs/dir.txt deleted file mode 100644 index c604f64..0000000 --- a/nbs/dir.txt +++ /dev/null @@ -1 +0,0 @@ -groups_example_2023-04-18_13-28-02 \ No newline at end of file diff --git a/nbs/feedback_template/checkpoint_feedback.md b/nbs/feedback_template/checkpoint_feedback.md deleted file mode 100644 index 2a56fc0..0000000 --- a/nbs/feedback_template/checkpoint_feedback.md +++ /dev/null @@ -1,40 +0,0 @@ -# Project Checkpoint Feedback - -Total: - -## Feedback - -| Category | Full Point | Your Score | Comment | -|---------------------|------------|------------|---------| -| Abstract | 0.5 | | | -| Background | 0.5 | | | -| Problem Statement | 0.5 | | | -| Data | 1 | | | -| Proposed Solution | 1 | | | -| Metrics | 1 | | | -| Preliminary Results | 1 | | | -| Ethics & Privacy | 1 | | | -| Team expectations | 0.25 | | | -| Timeline | 0.25 | | | - -## Rubric - - - -| Category | Full Point | Explanation | -|---------------------|------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Abstract | 0.5 | Abstract is informative, succinct, and clear. It offers specific details about the educational issue, variables (data), context, proposed methods, and measurement of performance/success of the study. | -| Background | 0.5 | Use a minimum of 2 or 3 citations Include a general introduction to your topic Narrative integrates critical and logical details from the peer-reviewed theoretical and research literature. Each key research component is grounded to the literature. Attention is given to different perspectives, threats to validity, and opinion vs. evidence. | -| Problem Statement | 0.5 | Presents a well-defined and significant research problem Include at least one ML-relevant potential solution. Articulates clear, reasonable research questions given the purpose, design, and methods of the project. All variables and controls have been appropriately defined. Proposals are clearly supported from the research and theoretical literature. All elements are mutually supportive. | -| Data | 1 | Multiple data sources for each aspect of the project. All data sources are fully described and referenced. Data is appropriate to the question/goal and large enough data points >1k observations and >5 variable The details of the descriptions also make it clear how they support the needs of the project. | -| Proposed Solution | 1 | The elements of the process were described succinctly and with clarity about how they are connected to each other Included description how the solution will be tested. | -| Metrics | 1 | The metrics are described clearly and succinctly. Their appropriateness for addressing the research problem is clearly described. Provided the mathematical representations of metrics | -| Preliminary Results | 1 | Analyzing the suitability of a dataset or algorithm for prediction/solving your problem Performing feature selection or hand-designing features from the raw data. Describe the features available/created and/or show the code for selection/creation Dataset actually clean and usable after feature selection is carried out Showing the performance of a base model/hyper-parameter setting. Solve the task with one "default" algorithm and characterize the performance level of that base model. At least one of the three: Learning curves or validation curves for a particular model Tables/graphs showing the performance of different models/hyper-parameters | -| Ethics & Privacy | 1 | Thoughtful discussion of ethical concerns included. Ethical concerns consider the whole data science process (question asked, data collected, data being used, the bias in data, analysis, post-analysis, etc.). How your group handled bias/ethical concerns clearly described | -| Team expectations | 0.25 | The list clearly was the subject of a thoughtful approach and already indicates a well-working team | -| Timeline | 0.25 | The timeline was clearly the subject of a thoughtful approach and indicates that the team has a detailed plan that seems appropriate and completable in the allotted time. | - - - -## Comments - diff --git a/nbs/feedback_template/final_project_feedback.md b/nbs/feedback_template/final_project_feedback.md deleted file mode 100644 index d832f0a..0000000 --- a/nbs/feedback_template/final_project_feedback.md +++ /dev/null @@ -1,37 +0,0 @@ -# Final Project Feedback - -## Feedback - -| Category | Full Point | Your Score | Comment | -|-------------------------|------------|------------|---------| -| Title & Abstract | 1 | | | -| Background | 1 | | | -| Problem Statement | 1 | | | -| Data | 1 | | | -| Proposed Solution | 1.25 | | | -| Metrics | 1.25 | | | -| Results | 1.25 | | | -| Interpreting the result | 1.25 | | | -| Limitations | 1.25 | | | -| Ethics & Privacy | 0.75 | | | -| Conclusion | 1 | | | - -## Rubric - -| Category | Full Point | Expectation | -|-------------------------|------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Title & Abstract | 1 | Title & abstract is informative, succinct, and clear.
    It offers specific details about the educational issue, variables (data), context, proposed methods, and measurement of performance/success of the study. | -| Background | 1 | Use a minimum of 2 or 3 citations
    Include a general introduction to your topic
    Narrative integrates critical and logical details from the peer-reviewed theoretical and research literature.
    Each key research component is grounded to the literature. Attention is given to different perspectives, threats to validity, and opinion vs. evidence. | -| Problem Statement | 1 | Presents a well-defined and significant research problem
    Articulates clear, reasonable research questions given the purpose, design, and methods of the project.
    All variables and controls have been appropriately defined. Proposals are clearly supported from the research and theoretical literature. All elements are mutually supportive. | -| Data | 1 | Multiple data sources for each aspect of the project. All data sources are fully described and referenced.
    Data is appropriate to the question/goal and large enough data points >1k observations and >5 variable
    The details of the descriptions of the data also make it clear how they support the needs of the project.
    Details of data storage and cleaning | -| Proposed Solution | 1.25 | The elements of the process were described succinctly and with clarity about how they are connected to each other
    Included description how the solution will be tested. (0.5 pt) | -| Metrics | 1.25 | The metrics are described clearly and succinctly.
    Their appropriateness for addressing the research problem is clearly described. | -| Results | 1.25 | Does a good model/hyper-parameter selection using more than one model and hyperparameter in hyperparameter search.
    Include the detailed code and analysis results of the main points
    Performs multiple secondary analysis such as learning curves, heat maps looking at where in the parameter space things are good/bad, uses statistical testing | -| Interpreting the result | 1.25 | Think clearly about the results and obtain one main point and 2-4 secondary points (2-5 sentences per point).
    Highlight HOW the results support those points. Understand what they are doing in the previous “Results” section | -| Limitations | 1.25 | Has a sense of what to do next, and has good explorations of the limitations. | -| Ethics & Privacy | 0.75 | Thoughtful discussion of ethical concerns included.
    Ethical concerns consider the whole data science process (question asked, data collected, data being used, the bias in data, analysis, post-analysis, etc.).
    How your group handled bias/ethical concerns clearly described | -| Conclusion | 1 | Clearly recapitulates the results and provides context, perhaps including the literature of the field. | - -## Comments - - diff --git a/nbs/feedback_template/proposal_feedback.md b/nbs/feedback_template/proposal_feedback.md deleted file mode 100644 index 424ef7d..0000000 --- a/nbs/feedback_template/proposal_feedback.md +++ /dev/null @@ -1,47 +0,0 @@ -# Project Proposal Feedback - -## Score (out of 9) - -[comment]: # (Please put the score in the following line. For example, a score of 8 should be `Score = 8` strictly) -Score = ... - -## Feedback: - -| | **Quality** | **Reasons** | -|-----------------------|-------------|-------------| -| **Abstract** | | | -| **Background** | | | -| **Problem Statement** | | | -| **Data** | | | -| **Proposed Solution** | | | -| **Evalution Metrics** | | | -| **Ethics & Privacy** | | | -| **Team expectations** | | | -| **Project Timeline Proposal** | | | - - -## Rubric - -| | Unsatisfactory | Developing | Proficient | Excellent | -|-----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| **Abstract** | Abstract is confusing or fails to offer important details about the issue, variables, context, or methods of the project. | Abstract lacks relevance or fails to offer pertinent details about the issue, variables, context, or methods of the project. | Abstract is relevant, offering details about the research project. | Abstract is informative, succinct, and clear. It offers specific details about the educational issue, variables, context, and proposed methods of the study. | -| **Problem Statement** | Research issue remains unclear. The research purpose, questions, hypotheses, definitions or variables, and controls are still largely undefined, or when they are poorly formed, ambiguous, or not logically connected to the description of the problem. Unclear whether the research problem is quantifiable, measurable, and replicable. | Research issue is identified, but the statement is too broad or fails to establish the importance of the problem. The research purpose, questions, hypotheses, definitions or variables, and controls are poorly formed, ambiguous, or not logically connected to the description of the problem. The limited description of whether the research problem is quantifiable, measurable, and replicable. | Identifies a relevant research issue. Research questions are succinctly stated, connected to the research issue, and supported by the literature. Variables and controls have been identified and described. Clear reasoning and description on that the research problem is quantifiable, measurable, and replicable | Presents a significant research problem. Articulates clear, reasonable research questions given the purpose, design, and methods of the project. All variables and controls have been appropriately defined. Clear and significant reasoning on the quantifiability, measurability, and replicability of the research problem. All elements are mutually supportive. | -| **Background** | Did not have at least 2 reliable and relevant sources. Or relevant sources were not used in relevant ways | A key component was not connected to the research literature. Selected literature was from unreliable sources. Literary supports were vague or ambiguous. | Key research components were connected to relevant, reliable theoretical and research literature. | Narrative integrates critical and logical details from the peer-reviewed theoretical and research literature. Each key research component is grounded in the literature. Attention is given to different perspectives, threats to validity, and opinion vs. evidence. | -| **Proposed Solution** | Lacks most details; vague or interpretable in different ways. Or seems completely unrealistic and inapplicable to the project domain. | Limited descriptions of the rationales and theories behind the solution provided and on how the solution will be tested. Limited relevance to the input dataset and problem to be solved. | Sufficient details on algorithmic description or theoretical properties; clear definition of how the solution will be tested, reproduced, and on the benchmark used. | Highly clear and succinct description of the rationales and theories behind the solution; thorough and comprehensive consideration of how the solution will be applied and tested; valid approach on how to reproduce the solution and effective benchmark to test the solution; a strong connection to the problem proposed. | -| **Data** | Did not have references to relevant data sources for this problem. Did not describe the data obtained at those sources | A key data source was not referenced or described in a satisfactory level of detail | All relevant data sources were referenced and described in terms of their key variables and size | Multiple data sources for each aspect of the project, All data sources are fully described and referenced. The details of the descriptions also make it clear how they support the needs of the project. | -| **Evaluation Metrics** | Did not propose any metric for evaluating the model or very little effort in this section. | Evaluation metrics proposed with limited relevance or inappropriate metrics; ambiguous description of the metrics to be used. | Thoughtful and meaningful evaluation metrics with sufficient considerations and descriptions of the model to be evaluated.| Effective and comprehensive evaluation metrics with thorough and detailed descriptions.| -| **Ethics** | No effort or just says we have no ethical concerns | Minimal ethical section; probably just talks about data privacy and no unintended consequences discussion. Ethical concerns raised seem irrelevant. | Ethical concerns described are appropriate and described sufficiently | Ethical concerns are described clearly and succinctly. This was clearly a thoughtful and nuanced approach to the issues | -| **Team expectations** | Lack of expectations | The list of expectations feels incomplete and perfunctory | It feels like the list of expectations is complete and seems appropriate | The list clearly was the subject of a thoughtful approach and already indicates a well-working team | -| **Timeline** | Lack of timeline. Or the timeline is completely unrealistic | The timeline feels incomplete and perfunctory. The timeline feels either too fast or too slow for the progress you expect a group can make | It feels like the timeline is complete and appropriate. it can likely be completed as is in the available amount of time | The timeline was clearly the subject of a thoughtful approach and indicates that the team has a detailed plan that seems appropriate and completable in the allotted time. | - -Scoring: Out of 9 points - -- Each Developing => -0.75 pts -- Each Unsatisfactory/Missing => -1.5 pts - - until the score is 0 - -If students address the detailed feedback in a future checkpoint, they will earn these points back. - -## Comments - - diff --git a/nbs/index.ipynb b/nbs/index.ipynb deleted file mode 100644 index db68232..0000000 --- a/nbs/index.ipynb +++ /dev/null @@ -1,141 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#| hide\n", - "import GroupEng\n", - "from CanvasGroupy.core import *\n", - "from CanvasGroupy.assign import *" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# CanvasGroupy\n", - "\n", - "> Canvas Grouping Python Script" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "View our documentation [at this link](https://fleischerresearchlab.github.io/CanvasGroupy/)\n", - "\n", - "\n", - "This module will use `GroupEng` to create canvas and github groups." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Install" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "```sh\n", - "pip install CanvasGroupy\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## How to use" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Please visit [GroupEng Official Website](https://groupeng.org/) to see the documnetation of how to use GroupEng." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "['-', '-', 'B', '-', '-']\n", - "['B', '-', 'H', '-', '-']\n", - "['H', '-', '-', '-', '-']\n", - "['-', 'H', '-', 'B', 'H']\n", - "['-', '-', '-', 'B', '-']\n", - "['-', '-', 'H', '-', '-']\n", - "['nanotech', 'renewable energy', 'nanotech', 'nanotech', 'nanotech']\n", - "['automotive', 'automotive', 'robotics', 'automotive', 'renewable energy']\n", - "['automotive', 'statistics', 'automotive', 'renewable energy', 'renewable energy']\n", - "['automotive', 'automotive', 'statistics', 'renewable energy', 'renewable energy']\n", - "['automotive', 'automotive', 'renewable energy', 'statistics', 'renewable energy']\n", - "['renewable energy', 'automotive', 'automotive', 'statistics', 'renewable energy']\n", - "['CS', 'EE', 'Mech E', 'CS', 'EE']\n", - "['CS', 'EE', 'Mech E', 'Mech E', 'EE']\n", - "['CS', 'Civ E', 'EE', 'Mech E', 'Mech E']\n", - "['EE', 'Civ E', 'Civ E', 'EE', 'Mech E']\n", - "['EE', 'Mech E', 'CS', 'EE', 'EE']\n", - "['Civ E', 'Mech E', 'CS', 'Mech E', 'EE']\n", - "['y', 'y', 'y', 'y', 'y']\n", - "['y', 'y', 'y', 'y', 'y']\n", - "['y', 'y', 'y', 'y', 'y']\n", - "['y', 'y', 'y', 'y', 'y']\n", - "['y', 'y', 'y', 'y', 'y']\n", - "['-', '-', 'y', '-', 'y']\n", - "['y', 'y', 'y', 'y', 'y']\n", - "['y', 'y', 'y', 'y', 'y']\n", - "['y', 'y', 'y', 'y', 'y']\n", - "['y', 'y', 'y', 'y', 'y']\n", - "['y', 'y', 'y', 'y', 'y']\n", - "['y', '-', '-', 'y', 'y']\n", - "['y', 'y', 'y', '-', '-']\n", - "['y', 'y', '-', 'y', '-']\n", - "['-', '-', '-', 'y', 'y']\n", - "['y', '-', 'y', '-', 'y']\n", - "['-', '-', 'y', '-', 'y']\n", - "[3.2579174234, 3.5995299693, 3.2756432963, 4.220160605, 2.5723254477]\n", - "[3.2579174234, 3.2756432963, 2.5723254477, 3.5995299693, 4.220160605]\n", - "[4.220160605, 3.5995299693, 3.2756432963, 3.2579174234, 2.5723254477]\n", - "[3.2756432963, 4.220160605, 3.2579174234, 2.5723254477, 3.5995299693]\n", - "[3.5995299693, 4.220160605, 3.2756432963, 3.2579174234, 2.5723254477]\n", - "[3.5995299693, 3.2756432963, 2.5723254477, 3.2579174234, 4.220160605]\n" - ] - }, - { - "data": { - "text/plain": [ - "(False, 'groups_example_2023-04-18_13-00-35')" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "assign_groups(\"example/sample_group_specification.groupeng\")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "python3", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/nbs/styles.css b/nbs/styles.css deleted file mode 100644 index 66ccc49..0000000 --- a/nbs/styles.css +++ /dev/null @@ -1,37 +0,0 @@ -.cell { - margin-bottom: 1rem; -} - -.cell > .sourceCode { - margin-bottom: 0; -} - -.cell-output > pre { - margin-bottom: 0; -} - -.cell-output > pre, .cell-output > .sourceCode > pre, .cell-output-stdout > pre { - margin-left: 0.8rem; - margin-top: 0; - background: none; - border-left: 2px solid lightsalmon; - border-top-left-radius: 0; - border-top-right-radius: 0; -} - -.cell-output > .sourceCode { - border: none; -} - -.cell-output > .sourceCode { - background: none; - margin-top: 0; -} - -div.description { - padding-left: 2px; - padding-top: 5px; - font-style: italic; - font-size: 135%; - opacity: 70%; -} diff --git a/nbs/tutorials/Authentications.ipynb b/nbs/tutorials/Authentications.ipynb deleted file mode 100644 index 1f914a0..0000000 --- a/nbs/tutorials/Authentications.ipynb +++ /dev/null @@ -1,84 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "e1bbbf5b", - "metadata": {}, - "outputs": [], - "source": [ - "import json" - ] - }, - { - "cell_type": "markdown", - "id": "c74fa591", - "metadata": {}, - "source": [ - "# GitHub / Canvas Authentications\n", - "\n", - "> In this example, we demonstrated the required credentials information needed to execute this program.\n", - "- order: 1" - ] - }, - { - "cell_type": "markdown", - "id": "95230c79", - "metadata": {}, - "source": [ - "To use this software, which essentially is a wrapper package under a API wrapper, you need to provide the program with the appropriate credentials file. We will use the provided credential to authenticate toward both Canvas and GitHub, in order to fetch and manipulate appropriate information.\n", - "\n", - "**Note:** The program itself won't either store nor stole your credentials information. This program will stay on your personal computer and will only use your credentials when needed. For more information about the implementation details, please refer to the GitHub repository to view the source code." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d4619816", - "metadata": {}, - "outputs": [], - "source": [ - "from CanvasGroupy.canvas import CanvasGroup\n", - "from CanvasGroupy.github import GitHubGroup" - ] - }, - { - "cell_type": "markdown", - "id": "38fd8b05", - "metadata": {}, - "source": [ - "The credentials are stored in the following format. You can download a credential template at the following link. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b6e480e4", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'GitHub Token': 'token', 'Canvas Token': 'token'}\n" - ] - } - ], - "source": [ - "#| echo: false\n", - "with open(\"../credentials.json\", \"r\") as f:\n", - " credentials = json.load(f)\n", - "print(credentials)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "python3", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/nbs/tutorials/Create GitHub Group from Canvas Group.ipynb b/nbs/tutorials/Create GitHub Group from Canvas Group.ipynb deleted file mode 100644 index 8e1c850..0000000 --- a/nbs/tutorials/Create GitHub Group from Canvas Group.ipynb +++ /dev/null @@ -1,1261 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "1e4859d1", - "metadata": {}, - "outputs": [], - "source": [ - "#| hide\n", - "from nbdev.doclinks import NbdevLookup" - ] - }, - { - "cell_type": "markdown", - "id": "83bc103b", - "metadata": {}, - "source": [ - "# Example Workflow - Create GitHub Group from Canvas Group\n", - "\n", - "> In this example, we created GitHub group repositories to student groups based on the group information on canvas.\n", - "- order: 2" - ] - }, - { - "cell_type": "markdown", - "id": "19f57906", - "metadata": {}, - "source": [ - " This example workflow was used in Spring 2023, for COGS 118A at UC San Diego. his workflow demonstrated a component of the `CanvasGroupy`, where we already have group information based on the canvas group. In addition, students' GitHub usernames were collected via a canvas quiz, where we fetched, validated, and stored the GitHub Username directly. This workflow was run after the fact that all students were successfully assigned a group, and all students have _correctly_ completed their GitHub Username quiz." - ] - }, - { - "cell_type": "markdown", - "id": "98820a9d", - "metadata": {}, - "source": [ - "As usual, to execute those API calls, you will have to provide the system with necessary credentials. You can find more information based on the _???TODO_ tutorial." - ] - }, - { - "cell_type": "markdown", - "id": "855dfd7c", - "metadata": {}, - "source": [ - "**Note:** The output of some cells are long, and the output might affect your reading experience. I recommend you to use the hyperlink on the right hand side bar to skip to the next section if needed." - ] - }, - { - "cell_type": "markdown", - "id": "912d8cc8", - "metadata": {}, - "source": [ - "## Canvas Get Groups / GitHub Username\n", - "\n", - "We need to get the group member information at Canvas. This is achieved by pulling the people list on the group category page." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "13856869", - "metadata": {}, - "outputs": [], - "source": [ - "from CanvasGroupy.canvas import CanvasGroup\n", - "from CanvasGroupy.github import GitHubGroup" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "eafdcbc3", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[92mAuthorization Successful!\u001b[0m\n", - "Course Set: \u001b[92m COGS 118A - Supvr/Mach Learning Algorithms - Fleischer [SP23] \u001b[0m\n", - "Getting List of Users... This might take a while...\n", - "Users Fetch Complete! The course has \u001b[94m161\u001b[0m students.\n" - ] - } - ], - "source": [ - "cg = CanvasGroup(\"Group_Eng/credentials.json\", course_id=45059)" - ] - }, - { - "cell_type": "markdown", - "id": "17468662", - "metadata": {}, - "source": [ - "### Fetch GitHub Username from Quiz\n", - "\n", - "See more info at `CanvasGroup.fetch_username_from_quiz`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c1103f9a", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Quiz: \u001b[92mGitHub Username\u001b[0m fetch! \n", - "Generating Student Analaysis...\n", - "[====================] 100%\n", - "\u001b[92mReport Generated!\u001b[0m\n", - "The Question asked is \u001b[94m1389031: What is your GitHub Username? Absolutely No Typo Please.\u001b[0m. \n", - "Make sure this is the correct question where you asked student for their GitHub id.\n", - "If you need to change the index of columns, change the col_index argument of this call.\n" - ] - } - ], - "source": [ - "github_usernames = cg.fetch_username_from_quiz(quiz_id=139061)" - ] - }, - { - "cell_type": "markdown", - "id": "580ba3fc", - "metadata": {}, - "source": [ - "## Check GitHub Username Validity\n", - "\n", - "We use GitHub API to search for a target user. See more info at `CanvasGroup.check_github_usernames`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d34a0188", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Student \u001b[93max008708\u001b[0m did not submit their github username.\n", - "\u001b[92mNotification Sent!\u001b[0m\n", - "Student \u001b[93ma8chu\u001b[0m did not submit their github username.\n", - "\u001b[92mNotification Sent!\u001b[0m\n", - "Student \u001b[93mj3dong\u001b[0m did not submit their github username.\n", - "\u001b[92mNotification Sent!\u001b[0m\n", - "Student \u001b[93mn6garcia\u001b[0m did not submit their github username.\n", - "\u001b[92mNotification Sent!\u001b[0m\n", - "Student \u001b[93mkehu\u001b[0m did not submit their github username.\n", - "\u001b[92mNotification Sent!\u001b[0m\n", - "Student \u001b[93mqil016\u001b[0m did not submit their github username.\n", - "\u001b[92mNotification Sent!\u001b[0m\n", - "Student \u001b[93mttp007\u001b[0m did not submit their github username.\n", - "\u001b[92mNotification Sent!\u001b[0m\n", - "Student \u001b[93mzshao\u001b[0m did not submit their github username.\n", - "\u001b[92mNotification Sent!\u001b[0m\n" - ] - }, - { - "data": { - "text/plain": [ - "{}" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cg.check_github_usernames(github_usernames,\n", - " send_canvas_email=True,\n", - " send_undone_reminder=True,\n", - " quiz_url=\"https://canvas.ucsd.edu/courses/45059/quizzes/139061\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "6d90cf81", - "metadata": {}, - "source": [ - "## Get Group Member Information" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0a7889e9", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'Group001-SP23': ['h5he', 'zmao', 'xiw013', 'j6wen', 'j5zhu'],\n", - " 'Group002-SP23': ['cmcmanig', 'jup006', 'ssuthar', 'd3yu'],\n", - " 'Group003-SP23': ['yic055', 'yuz191', 'xiz068'],\n", - " 'Group004-SP23': ['dac020', 'nilu', 'tyap', 'g6zhu'],\n", - " 'Group005-SP23': ['kechen', 'a8chu', 'cdelira', 'wolee', 'arshukla'],\n", - " 'Group006-SP23': ['ax008707', 'ax008724', 'ax008777'],\n", - " 'Group007-SP23': ['yuchi', 'j3dong', 'y3ge', 'x6he', 'xiz031'],\n", - " 'Group008-SP23': ['zifeng', 'ax008740', 'jul121', 'yuy047'],\n", - " 'Group009-SP23': ['ax008573', 'ckavanagh', 'v1lu', 'vvishnus'],\n", - " 'Group010-SP23': ['jwc002', 'tjamal', 'jsliang', 'tdn003'],\n", - " 'Group011-SP23': ['khchuang', 'emdavis', 'jejiang', 'nrejai'],\n", - " 'Group012-SP23': ['afleschn', 'rlharsono', 'jjsanchez', 'asengupt'],\n", - " 'Group013-SP23': ['kehu', 'jnhuang', 'shperry', 'alvalenc'],\n", - " 'Group014-SP23': ['gsroberts', 'cvillafa', 'shw089', 'yiz095'],\n", - " 'Group015-SP23': ['aanna', 'sdsilva', 'asivayog', 'nyanekch'],\n", - " 'Group016-SP23': ['yuche', 'y3guo', 'e1hu', 'zhl023', 'qil012'],\n", - " 'Group017-SP23': ['s3chowdhury', 'llennema', 'psodhi', 'svirk'],\n", - " 'Group018-SP23': ['ajcagle', 'ahewig', 's2malik', 'mnodini', 'musman'],\n", - " 'Group019-SP23': ['sasingh', 'nsit', 'dsun'],\n", - " 'Group020-SP23': ['akaji', 'm1manzan', 'j3mendez', 'mpareek', 'atrapena'],\n", - " 'Group021-SP23': ['jkrentse', 'jmlai', 'zel012', 'sserafin'],\n", - " 'Group022-SP23': ['ax008708', 'anc024', 'gng', 'reyang'],\n", - " 'Group023-SP23': ['raguinakang', 'phelcl', 'vjayanan', 'crochez', 'kpstern'],\n", - " 'Group024-SP23': ['nabansal', 'ccaban', 'mvfang', 'asim'],\n", - " 'Group025-SP23': ['nschaefe', 'hshaikh', 'jww001', 'bjyan'],\n", - " 'Group026-SP23': ['e1dong', 'aaolivas', 'h5park', 'yuz821'],\n", - " 'Group027-SP23': ['jddeleon', 'ndnguyen', 'ttp007', 'jzs002'],\n", - " 'Group028-SP23': ['mabdilah', 'yuh045', 'ktnakai', 'jonza'],\n", - " 'Group029-SP23': ['lmitbo', 'jsalce', 'lskerrett', 'jubamadu'],\n", - " 'Group030-SP23': ['ruc003', 'shh035', 'btn003', 'knino'],\n", - " 'Group031-SP23': ['shc007', 'n6garcia', 'ken010', 'mmpak'],\n", - " 'Group032-SP23': ['zachao', 'smurase', 'j1xu', 'z5zhang'],\n", - " 'Group033-SP23': ['sbodhisartha', 'ndeepak', 'arlu', 'trucker', 'jaxu'],\n", - " 'Group034-SP23': ['kabalaji', 'rchaklas', 'vspillai', 'jmvillal'],\n", - " 'Group035-SP23': ['cantoniohernandez', 'rpuranam', 'rsedano', 'zshao'],\n", - " 'Group036-SP23': ['malkhalifah', 'djjani', 'rkohli', 'smtrived'],\n", - " 'Group037-SP23': ['nazpeitia', 'g2hong', 'gkweon'],\n", - " 'Group038-SP23': ['qil016', 'msherrick', 'yiw085', 'r4zhou'],\n", - " 'Group039-SP23': ['cgutierrezgodoy', 'z8jiang', 'dar005', 'jtaolan']}" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "groups = cg.get_groups(\"Final Project\")\n", - "groups" - ] - }, - { - "cell_type": "markdown", - "id": "11be10d8", - "metadata": {}, - "source": [ - "## GitHub Repository Creation\n", - "\n", - "Given the gathered information about both group membership and students' GitHub Username, we are ready to create group repositories for them." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "50e54f49", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Successfully Authenticated. GitHub account: \u001b[92m scott-yj-yang \u001b[0m\n", - "Target Organization Set: \u001b[92m COGS118A \u001b[0m\n" - ] - } - ], - "source": [ - "ggroup = GitHubGroup(\"Group_Eng/credentials.json\", verbosity=1)\n", - "ggroup.set_org(\"COGS118A\")" - ] - }, - { - "cell_type": "markdown", - "id": "be22eaba", - "metadata": {}, - "source": [ - "In the following for loop, we create the group repositories via a series of `GitHubGroup.create_group_repo` command. This is the place where we can get personalized (or I shall say _groupalized_) repositories. Be sure to change the appropriate parameters." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "915d6dfa", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Repo \u001b[92m Group001-SP23 \u001b[0m Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from \u001b[96m Checkpoint_groupXXX.ipynb \u001b[0m to \u001b[92m Checkpoint_Group001-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m FinalProject_groupXXX.ipynb \u001b[0m to \u001b[92m FinalProject_Group001-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m Proposal_groupXXX.ipynb \u001b[0m to \u001b[92m Proposal_Group001-SP23.ipynb \u001b[0m\n", - "Added Collaborator: \u001b[92m TaraaHe \u001b[0m to: \u001b[92m Group001-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m demimao \u001b[0m to: \u001b[92m Group001-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m xiw013 \u001b[0m to: \u001b[92m Group001-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m willwen96 \u001b[0m to: \u001b[92m Group001-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m Ju-dyz \u001b[0m to: \u001b[92m Group001-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Team \u001b[92m Instructors_Sp23 \u001b[0m added to \u001b[92m Group001-SP23 \u001b[0m with permission \u001b[92m admin \u001b[0m\n", - "Group Repo: \u001b[92m Group001-SP23 \u001b[0m successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group001-SP23\n", - "\n", - "Repo \u001b[92m Group002-SP23 \u001b[0m Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from \u001b[96m Checkpoint_groupXXX.ipynb \u001b[0m to \u001b[92m Checkpoint_Group002-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m FinalProject_groupXXX.ipynb \u001b[0m to \u001b[92m FinalProject_Group002-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m Proposal_groupXXX.ipynb \u001b[0m to \u001b[92m Proposal_Group002-SP23.ipynb \u001b[0m\n", - "Added Collaborator: \u001b[92m connormcmanigal \u001b[0m to: \u001b[92m Group002-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m jup006 \u001b[0m to: \u001b[92m Group002-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m ssutharucsd \u001b[0m to: \u001b[92m Group002-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m d3yu \u001b[0m to: \u001b[92m Group002-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Team \u001b[92m Instructors_Sp23 \u001b[0m added to \u001b[92m Group002-SP23 \u001b[0m with permission \u001b[92m admin \u001b[0m\n", - "Group Repo: \u001b[92m Group002-SP23 \u001b[0m successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group002-SP23\n", - "\n", - "Repo \u001b[92m Group003-SP23 \u001b[0m Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from \u001b[96m Checkpoint_groupXXX.ipynb \u001b[0m to \u001b[92m Checkpoint_Group003-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m FinalProject_groupXXX.ipynb \u001b[0m to \u001b[92m FinalProject_Group003-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m Proposal_groupXXX.ipynb \u001b[0m to \u001b[92m Proposal_Group003-SP23.ipynb \u001b[0m\n", - "Added Collaborator: \u001b[92m Cyl200215 \u001b[0m to: \u001b[92m Group003-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m scottieboyzhang \u001b[0m to: \u001b[92m Group003-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m Orang1s \u001b[0m to: \u001b[92m Group003-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Team \u001b[92m Instructors_Sp23 \u001b[0m added to \u001b[92m Group003-SP23 \u001b[0m with permission \u001b[92m admin \u001b[0m\n", - "Group Repo: \u001b[92m Group003-SP23 \u001b[0m successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group003-SP23\n", - "\n", - "Repo \u001b[92m Group004-SP23 \u001b[0m Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from \u001b[96m Checkpoint_groupXXX.ipynb \u001b[0m to \u001b[92m Checkpoint_Group004-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m FinalProject_groupXXX.ipynb \u001b[0m to \u001b[92m FinalProject_Group004-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m Proposal_groupXXX.ipynb \u001b[0m to \u001b[92m Proposal_Group004-SP23.ipynb \u001b[0m\n", - "Added Collaborator: \u001b[92m CDavid99 \u001b[0m to: \u001b[92m Group004-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m nilu0311 \u001b[0m to: \u001b[92m Group004-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m cookingoil88 \u001b[0m to: \u001b[92m Group004-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m g6zhu \u001b[0m to: \u001b[92m Group004-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Team \u001b[92m Instructors_Sp23 \u001b[0m added to \u001b[92m Group004-SP23 \u001b[0m with permission \u001b[92m admin \u001b[0m\n", - "Group Repo: \u001b[92m Group004-SP23 \u001b[0m successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group004-SP23\n", - "\n", - "a8chu's GitHub Username not found\n", - "Repo \u001b[92m Group005-SP23 \u001b[0m Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from \u001b[96m Checkpoint_groupXXX.ipynb \u001b[0m to \u001b[92m Checkpoint_Group005-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m FinalProject_groupXXX.ipynb \u001b[0m to \u001b[92m FinalProject_Group005-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m Proposal_groupXXX.ipynb \u001b[0m to \u001b[92m Proposal_Group005-SP23.ipynb \u001b[0m\n", - "Added Collaborator: \u001b[92m kchen283 \u001b[0m to: \u001b[92m Group005-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m cdelira9 \u001b[0m to: \u001b[92m Group005-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m wj6801 \u001b[0m to: \u001b[92m Group005-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m arth-shukla \u001b[0m to: \u001b[92m Group005-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Team \u001b[92m Instructors_Sp23 \u001b[0m added to \u001b[92m Group005-SP23 \u001b[0m with permission \u001b[92m admin \u001b[0m\n", - "Group Repo: \u001b[92m Group005-SP23 \u001b[0m successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group005-SP23\n", - "\n", - "Repo \u001b[92m Group006-SP23 \u001b[0m Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from \u001b[96m Checkpoint_groupXXX.ipynb \u001b[0m to \u001b[92m Checkpoint_Group006-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m FinalProject_groupXXX.ipynb \u001b[0m to \u001b[92m FinalProject_Group006-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m Proposal_groupXXX.ipynb \u001b[0m to \u001b[92m Proposal_Group006-SP23.ipynb \u001b[0m\n", - "Added Collaborator: \u001b[92m Leoooo333 \u001b[0m to: \u001b[92m Group006-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m qdh-2002 \u001b[0m to: \u001b[92m Group006-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m Chihhsinli \u001b[0m to: \u001b[92m Group006-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Team \u001b[92m Instructors_Sp23 \u001b[0m added to \u001b[92m Group006-SP23 \u001b[0m with permission \u001b[92m admin \u001b[0m\n", - "Group Repo: \u001b[92m Group006-SP23 \u001b[0m successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group006-SP23\n", - "\n", - "j3dong's GitHub Username not found\n", - "Repo \u001b[92m Group007-SP23 \u001b[0m Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from \u001b[96m Checkpoint_groupXXX.ipynb \u001b[0m to \u001b[92m Checkpoint_Group007-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m FinalProject_groupXXX.ipynb \u001b[0m to \u001b[92m FinalProject_Group007-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m Proposal_groupXXX.ipynb \u001b[0m to \u001b[92m Proposal_Group007-SP23.ipynb \u001b[0m\n", - "Added Collaborator: \u001b[92m YunxiangChi \u001b[0m to: \u001b[92m Group007-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m alien-invader \u001b[0m to: \u001b[92m Group007-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m XiaoyanHe0713 \u001b[0m to: \u001b[92m Group007-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m Andrina-iris \u001b[0m to: \u001b[92m Group007-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Team \u001b[92m Instructors_Sp23 \u001b[0m added to \u001b[92m Group007-SP23 \u001b[0m with permission \u001b[92m admin \u001b[0m\n", - "Group Repo: \u001b[92m Group007-SP23 \u001b[0m successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group007-SP23\n", - "\n", - "Repo \u001b[92m Group008-SP23 \u001b[0m Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from \u001b[96m Checkpoint_groupXXX.ipynb \u001b[0m to \u001b[92m Checkpoint_Group008-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m FinalProject_groupXXX.ipynb \u001b[0m to \u001b[92m FinalProject_Group008-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m Proposal_groupXXX.ipynb \u001b[0m to \u001b[92m Proposal_Group008-SP23.ipynb \u001b[0m\n", - "Added Collaborator: \u001b[92m wwjasperww \u001b[0m to: \u001b[92m Group008-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m ChengqinLi1206 \u001b[0m to: \u001b[92m Group008-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m junyuelin \u001b[0m to: \u001b[92m Group008-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m fergusyyang \u001b[0m to: \u001b[92m Group008-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Team \u001b[92m Instructors_Sp23 \u001b[0m added to \u001b[92m Group008-SP23 \u001b[0m with permission \u001b[92m admin \u001b[0m\n", - "Group Repo: \u001b[92m Group008-SP23 \u001b[0m successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group008-SP23\n", - "\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Repo \u001b[92m Group009-SP23 \u001b[0m Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from \u001b[96m Checkpoint_groupXXX.ipynb \u001b[0m to \u001b[92m Checkpoint_Group009-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m FinalProject_groupXXX.ipynb \u001b[0m to \u001b[92m FinalProject_Group009-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m Proposal_groupXXX.ipynb \u001b[0m to \u001b[92m Proposal_Group009-SP23.ipynb \u001b[0m\n", - "Added Collaborator: \u001b[92m thaiscodafond \u001b[0m to: \u001b[92m Group009-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m ckavanagh21 \u001b[0m to: \u001b[92m Group009-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m 404EZRA \u001b[0m to: \u001b[92m Group009-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m vvishnus \u001b[0m to: \u001b[92m Group009-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Team \u001b[92m Instructors_Sp23 \u001b[0m added to \u001b[92m Group009-SP23 \u001b[0m with permission \u001b[92m admin \u001b[0m\n", - "Group Repo: \u001b[92m Group009-SP23 \u001b[0m successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group009-SP23\n", - "\n", - "Repo \u001b[92m Group010-SP23 \u001b[0m Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from \u001b[96m Checkpoint_groupXXX.ipynb \u001b[0m to \u001b[92m Checkpoint_Group010-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m FinalProject_groupXXX.ipynb \u001b[0m to \u001b[92m FinalProject_Group010-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m Proposal_groupXXX.ipynb \u001b[0m to \u001b[92m Proposal_Group010-SP23.ipynb \u001b[0m\n", - "Added Collaborator: \u001b[92m strawhatwilson23 \u001b[0m to: \u001b[92m Group010-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m tjamalcodes \u001b[0m to: \u001b[92m Group010-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m jaysunl \u001b[0m to: \u001b[92m Group010-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m idereknguyen \u001b[0m to: \u001b[92m Group010-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Team \u001b[92m Instructors_Sp23 \u001b[0m added to \u001b[92m Group010-SP23 \u001b[0m with permission \u001b[92m admin \u001b[0m\n", - "Group Repo: \u001b[92m Group010-SP23 \u001b[0m successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group010-SP23\n", - "\n", - "Repo \u001b[92m Group011-SP23 \u001b[0m Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from \u001b[96m Checkpoint_groupXXX.ipynb \u001b[0m to \u001b[92m Checkpoint_Group011-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m FinalProject_groupXXX.ipynb \u001b[0m to \u001b[92m FinalProject_Group011-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m Proposal_groupXXX.ipynb \u001b[0m to \u001b[92m Proposal_Group011-SP23.ipynb \u001b[0m\n", - "Added Collaborator: \u001b[92m khchuang12 \u001b[0m to: \u001b[92m Group011-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m Emdavis02 \u001b[0m to: \u001b[92m Group011-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m jennifer-jiang \u001b[0m to: \u001b[92m Group011-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m nrejai \u001b[0m to: \u001b[92m Group011-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Team \u001b[92m Instructors_Sp23 \u001b[0m added to \u001b[92m Group011-SP23 \u001b[0m with permission \u001b[92m admin \u001b[0m\n", - "Group Repo: \u001b[92m Group011-SP23 \u001b[0m successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group011-SP23\n", - "\n", - "Repo \u001b[92m Group012-SP23 \u001b[0m Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from \u001b[96m Checkpoint_groupXXX.ipynb \u001b[0m to \u001b[92m Checkpoint_Group012-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m FinalProject_groupXXX.ipynb \u001b[0m to \u001b[92m FinalProject_Group012-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m Proposal_groupXXX.ipynb \u001b[0m to \u001b[92m Proposal_Group012-SP23.ipynb \u001b[0m\n", - "Added Collaborator: \u001b[92m afleschner \u001b[0m to: \u001b[92m Group012-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m githubharsono \u001b[0m to: \u001b[92m Group012-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m JJSanchez23 \u001b[0m to: \u001b[92m Group012-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m antarasengupta26 \u001b[0m to: \u001b[92m Group012-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Team \u001b[92m Instructors_Sp23 \u001b[0m added to \u001b[92m Group012-SP23 \u001b[0m with permission \u001b[92m admin \u001b[0m\n", - "Group Repo: \u001b[92m Group012-SP23 \u001b[0m successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group012-SP23\n", - "\n", - "kehu's GitHub Username not found\n", - "Repo \u001b[92m Group013-SP23 \u001b[0m Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from \u001b[96m Checkpoint_groupXXX.ipynb \u001b[0m to \u001b[92m Checkpoint_Group013-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m FinalProject_groupXXX.ipynb \u001b[0m to \u001b[92m FinalProject_Group013-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m Proposal_groupXXX.ipynb \u001b[0m to \u001b[92m Proposal_Group013-SP23.ipynb \u001b[0m\n", - "Added Collaborator: \u001b[92m jnhuang02 \u001b[0m to: \u001b[92m Group013-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m Sean1572 \u001b[0m to: \u001b[92m Group013-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m valenciaaalberto \u001b[0m to: \u001b[92m Group013-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Team \u001b[92m Instructors_Sp23 \u001b[0m added to \u001b[92m Group013-SP23 \u001b[0m with permission \u001b[92m admin \u001b[0m\n", - "Group Repo: \u001b[92m Group013-SP23 \u001b[0m successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group013-SP23\n", - "\n", - "Repo \u001b[92m Group014-SP23 \u001b[0m Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from \u001b[96m Checkpoint_groupXXX.ipynb \u001b[0m to \u001b[92m Checkpoint_Group014-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m FinalProject_groupXXX.ipynb \u001b[0m to \u001b[92m FinalProject_Group014-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m Proposal_groupXXX.ipynb \u001b[0m to \u001b[92m Proposal_Group014-SP23.ipynb \u001b[0m\n", - "Added Collaborator: \u001b[92m empire-penguin \u001b[0m to: \u001b[92m Group014-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m villafun \u001b[0m to: \u001b[92m Group014-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m 50ShadesOfShawn \u001b[0m to: \u001b[92m Group014-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m ericzyl \u001b[0m to: \u001b[92m Group014-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Team \u001b[92m Instructors_Sp23 \u001b[0m added to \u001b[92m Group014-SP23 \u001b[0m with permission \u001b[92m admin \u001b[0m\n", - "Group Repo: \u001b[92m Group014-SP23 \u001b[0m successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group014-SP23\n", - "\n", - "Repo \u001b[92m Group015-SP23 \u001b[0m Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from \u001b[96m Checkpoint_groupXXX.ipynb \u001b[0m to \u001b[92m Checkpoint_Group015-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m FinalProject_groupXXX.ipynb \u001b[0m to \u001b[92m FinalProject_Group015-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m Proposal_groupXXX.ipynb \u001b[0m to \u001b[92m Proposal_Group015-SP23.ipynb \u001b[0m\n", - "Added Collaborator: \u001b[92m arjunanna \u001b[0m to: \u001b[92m Group015-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m sdsilva1 \u001b[0m to: \u001b[92m Group015-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m abi2020 \u001b[0m to: \u001b[92m Group015-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m nikothomas \u001b[0m to: \u001b[92m Group015-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Team \u001b[92m Instructors_Sp23 \u001b[0m added to \u001b[92m Group015-SP23 \u001b[0m with permission \u001b[92m admin \u001b[0m\n", - "Group Repo: \u001b[92m Group015-SP23 \u001b[0m successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group015-SP23\n", - "\n", - "Repo \u001b[92m Group016-SP23 \u001b[0m Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from \u001b[96m Checkpoint_groupXXX.ipynb \u001b[0m to \u001b[92m Checkpoint_Group016-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m FinalProject_groupXXX.ipynb \u001b[0m to \u001b[92m FinalProject_Group016-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m Proposal_groupXXX.ipynb \u001b[0m to \u001b[92m Proposal_Group016-SP23.ipynb \u001b[0m\n", - "Added Collaborator: \u001b[92m shendu11 \u001b[0m to: \u001b[92m Group016-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m Y3GUO \u001b[0m to: \u001b[92m Group016-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m EthanHu0 \u001b[0m to: \u001b[92m Group016-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m claireZHL \u001b[0m to: \u001b[92m Group016-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m QilunLiu5216 \u001b[0m to: \u001b[92m Group016-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Team \u001b[92m Instructors_Sp23 \u001b[0m added to \u001b[92m Group016-SP23 \u001b[0m with permission \u001b[92m admin \u001b[0m\n", - "Group Repo: \u001b[92m Group016-SP23 \u001b[0m successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group016-SP23\n", - "\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Repo \u001b[92m Group017-SP23 \u001b[0m Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from \u001b[96m Checkpoint_groupXXX.ipynb \u001b[0m to \u001b[92m Checkpoint_Group017-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m FinalProject_groupXXX.ipynb \u001b[0m to \u001b[92m FinalProject_Group017-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m Proposal_groupXXX.ipynb \u001b[0m to \u001b[92m Proposal_Group017-SP23.ipynb \u001b[0m\n", - "Added Collaborator: \u001b[92m sreetama02 \u001b[0m to: \u001b[92m Group017-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m llennemann \u001b[0m to: \u001b[92m Group017-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m pabbi5 \u001b[0m to: \u001b[92m Group017-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m AstuteFern \u001b[0m to: \u001b[92m Group017-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Team \u001b[92m Instructors_Sp23 \u001b[0m added to \u001b[92m Group017-SP23 \u001b[0m with permission \u001b[92m admin \u001b[0m\n", - "Group Repo: \u001b[92m Group017-SP23 \u001b[0m successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group017-SP23\n", - "\n", - "Repo \u001b[92m Group018-SP23 \u001b[0m Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from \u001b[96m Checkpoint_groupXXX.ipynb \u001b[0m to \u001b[92m Checkpoint_Group018-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m FinalProject_groupXXX.ipynb \u001b[0m to \u001b[92m FinalProject_Group018-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m Proposal_groupXXX.ipynb \u001b[0m to \u001b[92m Proposal_Group018-SP23.ipynb \u001b[0m\n", - "Added Collaborator: \u001b[92m ajcagle8 \u001b[0m to: \u001b[92m Group018-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m aHewig \u001b[0m to: \u001b[92m Group018-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m notSaranshMalik \u001b[0m to: \u001b[92m Group018-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m mnodini \u001b[0m to: \u001b[92m Group018-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m maryamkusman \u001b[0m to: \u001b[92m Group018-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Team \u001b[92m Instructors_Sp23 \u001b[0m added to \u001b[92m Group018-SP23 \u001b[0m with permission \u001b[92m admin \u001b[0m\n", - "Group Repo: \u001b[92m Group018-SP23 \u001b[0m successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group018-SP23\n", - "\n", - "Repo \u001b[92m Group019-SP23 \u001b[0m Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from \u001b[96m Checkpoint_groupXXX.ipynb \u001b[0m to \u001b[92m Checkpoint_Group019-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m FinalProject_groupXXX.ipynb \u001b[0m to \u001b[92m FinalProject_Group019-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m Proposal_groupXXX.ipynb \u001b[0m to \u001b[92m Proposal_Group019-SP23.ipynb \u001b[0m\n", - "Added Collaborator: \u001b[92m SSingh44343 \u001b[0m to: \u001b[92m Group019-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m nathansit \u001b[0m to: \u001b[92m Group019-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Added Collaborator: \u001b[92m DaimengSun \u001b[0m to: \u001b[92m Group019-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "Team \u001b[92m Instructors_Sp23 \u001b[0m added to \u001b[92m Group019-SP23 \u001b[0m with permission \u001b[92m admin \u001b[0m\n", - "Group Repo: \u001b[92m Group019-SP23 \u001b[0m successfuly created!\n", - "Repo URL: https://github.com/COGS118A/Group019-SP23\n", - "\n", - "Repo \u001b[92m Group020-SP23 \u001b[0m Created... Wait for 3 sec to updates\n", - "File Successfully Renamed from \u001b[96m Checkpoint_groupXXX.ipynb \u001b[0m to \u001b[92m Checkpoint_Group020-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m FinalProject_groupXXX.ipynb \u001b[0m to \u001b[92m FinalProject_Group020-SP23.ipynb \u001b[0m\n", - "File Successfully Renamed from \u001b[96m Proposal_groupXXX.ipynb \u001b[0m to \u001b[92m Proposal_Group020-SP23.ipynb \u001b[0m\n", - "Added Collaborator: \u001b[92m ashesh8500 \u001b[0m to: \u001b[92m Group020-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n" - ] - } - ], - "source": [ - "repos = []\n", - "for group_name, members in groups.items():\n", - " group_git_usernames = []\n", - " for email in members:\n", - " try:\n", - " # try to get the git username for each student.\n", - " # not all students completed their quiz.\n", - " group_git_usernames.append(github_usernames[email])\n", - " except KeyError:\n", - " print(f\"{email}'s GitHub Username not found\")\n", - " repo = ggroup.create_group_repo(\n", - " repo_name=group_name,\n", - " collaborators=group_git_usernames,\n", - " permission=\"write\",\n", - " repo_template=\"COGS118A/group_template\",\n", - " rename_files={\n", - " \"Checkpoint_groupXXX.ipynb\": f\"Checkpoint_{group_name}.ipynb\",\n", - " \"FinalProject_groupXXX.ipynb\": f\"FinalProject_{group_name}.ipynb\",\n", - " \"Proposal_groupXXX.ipynb\": f\"Proposal_{group_name}.ipynb\"\n", - " },\n", - " private=False,\n", - " description=f\"COGS118A Final Project {group_name} Repository\",\n", - " team_slug=\"Instructors_Sp23\",\n", - " team_permission=\"admin\"\n", - " )\n", - " print(\"\")\n", - " repos.append(repo)" - ] - }, - { - "cell_type": "markdown", - "id": "573ddd71", - "metadata": {}, - "source": [ - "## Resent Invitations\n", - "\n", - "GitHub collaboration invites will be [expired automatically](https://docs.github.com/en/organizations/managing-membership-in-your-organization/inviting-users-to-join-your-organization#retrying-or-canceling-expired-invitations) when the user did not accept the invite after a certain period of time. After all the group repositories are created, the command `GitHubGroup.resent_invitations_team_repos` will rescind all pending invitations and resent invitation to that collaborators. \n", - "\n", - "This command is particularly useful when managing a large volume of repositories as it painlessly re-validated and re-sent all pending invitations of all repositories under a team. We ran this command daily to constantly remind student to accept their GitHub invitations, until all students have a valid permission to the target repository." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6acb7b7d", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Repository \u001b[96m AssignmentNotebooksSource_SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[]\n", - "Repository \u001b[96m AssignmentNotebooks_SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[]\n", - "Repository \u001b[96m DiscussionSectionNotebooks \u001b[0m:\n", - "The list of pending invitation:\n", - "[]\n", - "Repository \u001b[96m Dockerfiles \u001b[0m:\n", - "The list of pending invitation:\n", - "[]\n", - "Repository \u001b[96m Group001-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"demimao\"),\n", - " NamedUser(login=\"xiw013\"),\n", - " NamedUser(login=\"Ju-dyz\"),\n", - " NamedUser(login=\"TaraaHe\")]\n", - "\u001b[93m\u001b[4mdemimao\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m demimao \u001b[0m to: \u001b[92m Group001-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to demimao \u001b[0m\n", - "\u001b[93m\u001b[4mxiw013\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m xiw013 \u001b[0m to: \u001b[92m Group001-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to xiw013 \u001b[0m\n", - "\u001b[93m\u001b[4mJu-dyz\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m Ju-dyz \u001b[0m to: \u001b[92m Group001-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to Ju-dyz \u001b[0m\n", - "\u001b[93m\u001b[4mTaraaHe\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m TaraaHe \u001b[0m to: \u001b[92m Group001-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to TaraaHe \u001b[0m\n", - "Repository \u001b[96m Group002-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"jup006\"),\n", - " NamedUser(login=\"ssutharucsd\"),\n", - " NamedUser(login=\"connormcmanigal\"),\n", - " NamedUser(login=\"d3yu\")]\n", - "\u001b[93m\u001b[4mjup006\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m jup006 \u001b[0m to: \u001b[92m Group002-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to jup006 \u001b[0m\n", - "\u001b[93m\u001b[4mssutharucsd\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m ssutharucsd \u001b[0m to: \u001b[92m Group002-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to ssutharucsd \u001b[0m\n", - "\u001b[93m\u001b[4mconnormcmanigal\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m connormcmanigal \u001b[0m to: \u001b[92m Group002-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to connormcmanigal \u001b[0m\n", - "\u001b[93m\u001b[4md3yu\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m d3yu \u001b[0m to: \u001b[92m Group002-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to d3yu \u001b[0m\n", - "Repository \u001b[96m Group003-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"scottieboyzhang\"), NamedUser(login=\"Orang1s\")]\n", - "\u001b[93m\u001b[4mscottieboyzhang\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m scottieboyzhang \u001b[0m to: \u001b[92m Group003-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to scottieboyzhang \u001b[0m\n", - "\u001b[93m\u001b[4mOrang1s\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m Orang1s \u001b[0m to: \u001b[92m Group003-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to Orang1s \u001b[0m\n", - "Repository \u001b[96m Group004-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"CDavid99\"),\n", - " NamedUser(login=\"nilu0311\"),\n", - " NamedUser(login=\"g6zhu\")]\n", - "\u001b[93m\u001b[4mCDavid99\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m CDavid99 \u001b[0m to: \u001b[92m Group004-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to CDavid99 \u001b[0m\n", - "\u001b[93m\u001b[4mnilu0311\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m nilu0311 \u001b[0m to: \u001b[92m Group004-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to nilu0311 \u001b[0m\n", - "\u001b[93m\u001b[4mg6zhu\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m g6zhu \u001b[0m to: \u001b[92m Group004-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to g6zhu \u001b[0m\n", - "Repository \u001b[96m Group005-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"arth-shukla\"),\n", - " NamedUser(login=\"kchen283\"),\n", - " NamedUser(login=\"cdelira9\")]\n", - "\u001b[93m\u001b[4marth-shukla\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m arth-shukla \u001b[0m to: \u001b[92m Group005-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to arth-shukla \u001b[0m\n", - "\u001b[93m\u001b[4mkchen283\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m kchen283 \u001b[0m to: \u001b[92m Group005-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to kchen283 \u001b[0m\n", - "\u001b[93m\u001b[4mcdelira9\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m cdelira9 \u001b[0m to: \u001b[92m Group005-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to cdelira9 \u001b[0m\n", - "Repository \u001b[96m Group006-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"qdh-2002\"), NamedUser(login=\"Chihhsinli\")]\n", - "\u001b[93m\u001b[4mqdh-2002\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m qdh-2002 \u001b[0m to: \u001b[92m Group006-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to qdh-2002 \u001b[0m\n", - "\u001b[93m\u001b[4mChihhsinli\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m Chihhsinli \u001b[0m to: \u001b[92m Group006-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to Chihhsinli \u001b[0m\n", - "Repository \u001b[96m Group007-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"YunxiangChi\"),\n", - " NamedUser(login=\"Andrina-iris\"),\n", - " NamedUser(login=\"alien-invader\")]\n", - "\u001b[93m\u001b[4mYunxiangChi\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m YunxiangChi \u001b[0m to: \u001b[92m Group007-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to YunxiangChi \u001b[0m\n", - "\u001b[93m\u001b[4mAndrina-iris\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m Andrina-iris \u001b[0m to: \u001b[92m Group007-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to Andrina-iris \u001b[0m\n", - "\u001b[93m\u001b[4malien-invader\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m alien-invader \u001b[0m to: \u001b[92m Group007-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to alien-invader \u001b[0m\n", - "Repository \u001b[96m Group008-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"wwjasperww\")]\n", - "\u001b[93m\u001b[4mwwjasperww\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m wwjasperww \u001b[0m to: \u001b[92m Group008-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to wwjasperww \u001b[0m\n", - "Repository \u001b[96m Group009-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"404EZRA\"), NamedUser(login=\"vvishnus\")]\n", - "\u001b[93m\u001b[4m404EZRA\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m 404EZRA \u001b[0m to: \u001b[92m Group009-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to 404EZRA \u001b[0m\n", - "\u001b[93m\u001b[4mvvishnus\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m vvishnus \u001b[0m to: \u001b[92m Group009-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to vvishnus \u001b[0m\n", - "Repository \u001b[96m Group010-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"idereknguyen\"),\n", - " NamedUser(login=\"jaysunl\"),\n", - " NamedUser(login=\"tjamalcodes\"),\n", - " NamedUser(login=\"strawhatwilson23\")]\n", - "\u001b[93m\u001b[4midereknguyen\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m idereknguyen \u001b[0m to: \u001b[92m Group010-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to idereknguyen \u001b[0m\n", - "\u001b[93m\u001b[4mjaysunl\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m jaysunl \u001b[0m to: \u001b[92m Group010-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to jaysunl \u001b[0m\n", - "\u001b[93m\u001b[4mtjamalcodes\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m tjamalcodes \u001b[0m to: \u001b[92m Group010-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to tjamalcodes \u001b[0m\n", - "\u001b[93m\u001b[4mstrawhatwilson23\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m strawhatwilson23 \u001b[0m to: \u001b[92m Group010-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to strawhatwilson23 \u001b[0m\n", - "Repository \u001b[96m Group011-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"khchuang12\"),\n", - " NamedUser(login=\"nrejai\"),\n", - " NamedUser(login=\"emdavis02\")]\n", - "\u001b[93m\u001b[4mkhchuang12\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m khchuang12 \u001b[0m to: \u001b[92m Group011-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to khchuang12 \u001b[0m\n", - "\u001b[93m\u001b[4mnrejai\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m nrejai \u001b[0m to: \u001b[92m Group011-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to nrejai \u001b[0m\n", - "\u001b[93m\u001b[4memdavis02\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m emdavis02 \u001b[0m to: \u001b[92m Group011-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to emdavis02 \u001b[0m\n", - "Repository \u001b[96m Group012-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"githubharsono\"),\n", - " NamedUser(login=\"JJSanchez23\"),\n", - " NamedUser(login=\"antarasengupta26\")]\n", - "\u001b[93m\u001b[4mgithubharsono\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m githubharsono \u001b[0m to: \u001b[92m Group012-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to githubharsono \u001b[0m\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[93m\u001b[4mJJSanchez23\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m JJSanchez23 \u001b[0m to: \u001b[92m Group012-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to JJSanchez23 \u001b[0m\n", - "\u001b[93m\u001b[4mantarasengupta26\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m antarasengupta26 \u001b[0m to: \u001b[92m Group012-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to antarasengupta26 \u001b[0m\n", - "Repository \u001b[96m Group013-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"Sean1572\"), NamedUser(login=\"jnhuang02\")]\n", - "\u001b[93m\u001b[4mSean1572\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m Sean1572 \u001b[0m to: \u001b[92m Group013-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to Sean1572 \u001b[0m\n", - "\u001b[93m\u001b[4mjnhuang02\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m jnhuang02 \u001b[0m to: \u001b[92m Group013-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to jnhuang02 \u001b[0m\n", - "Repository \u001b[96m Group014-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"ericzyl\"),\n", - " NamedUser(login=\"50ShadesOfShawn\"),\n", - " NamedUser(login=\"villafun\")]\n", - "\u001b[93m\u001b[4mericzyl\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m ericzyl \u001b[0m to: \u001b[92m Group014-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to ericzyl \u001b[0m\n", - "\u001b[93m\u001b[4m50ShadesOfShawn\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m 50ShadesOfShawn \u001b[0m to: \u001b[92m Group014-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to 50ShadesOfShawn \u001b[0m\n", - "\u001b[93m\u001b[4mvillafun\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m villafun \u001b[0m to: \u001b[92m Group014-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to villafun \u001b[0m\n", - "Repository \u001b[96m Group015-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"nikothomas\"),\n", - " NamedUser(login=\"abi2020\"),\n", - " NamedUser(login=\"arjunanna\"),\n", - " NamedUser(login=\"sdsilva1\")]\n", - "\u001b[93m\u001b[4mnikothomas\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m nikothomas \u001b[0m to: \u001b[92m Group015-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to nikothomas \u001b[0m\n", - "\u001b[93m\u001b[4mabi2020\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m abi2020 \u001b[0m to: \u001b[92m Group015-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to abi2020 \u001b[0m\n", - "\u001b[93m\u001b[4marjunanna\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m arjunanna \u001b[0m to: \u001b[92m Group015-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to arjunanna \u001b[0m\n", - "\u001b[93m\u001b[4msdsilva1\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m sdsilva1 \u001b[0m to: \u001b[92m Group015-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to sdsilva1 \u001b[0m\n", - "Repository \u001b[96m Group016-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"shendu11\"),\n", - " NamedUser(login=\"EthanHu0\"),\n", - " NamedUser(login=\"QilunLiu5216\"),\n", - " NamedUser(login=\"Y3GUO\"),\n", - " NamedUser(login=\"claireZHL\")]\n", - "\u001b[93m\u001b[4mshendu11\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m shendu11 \u001b[0m to: \u001b[92m Group016-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to shendu11 \u001b[0m\n", - "\u001b[93m\u001b[4mEthanHu0\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m EthanHu0 \u001b[0m to: \u001b[92m Group016-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to EthanHu0 \u001b[0m\n", - "\u001b[93m\u001b[4mQilunLiu5216\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m QilunLiu5216 \u001b[0m to: \u001b[92m Group016-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to QilunLiu5216 \u001b[0m\n", - "\u001b[93m\u001b[4mY3GUO\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m Y3GUO \u001b[0m to: \u001b[92m Group016-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to Y3GUO \u001b[0m\n", - "\u001b[93m\u001b[4mclaireZHL\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m claireZHL \u001b[0m to: \u001b[92m Group016-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to claireZHL \u001b[0m\n", - "Repository \u001b[96m Group017-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"AstuteFern\"),\n", - " NamedUser(login=\"sreetama02\"),\n", - " NamedUser(login=\"pabbi5\")]\n", - "\u001b[93m\u001b[4mAstuteFern\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m AstuteFern \u001b[0m to: \u001b[92m Group017-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to AstuteFern \u001b[0m\n", - "\u001b[93m\u001b[4msreetama02\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m sreetama02 \u001b[0m to: \u001b[92m Group017-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to sreetama02 \u001b[0m\n", - "\u001b[93m\u001b[4mpabbi5\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m pabbi5 \u001b[0m to: \u001b[92m Group017-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to pabbi5 \u001b[0m\n", - "Repository \u001b[96m Group018-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"notSaranshMalik\"),\n", - " NamedUser(login=\"mnodini\"),\n", - " NamedUser(login=\"Maryamkusman\"),\n", - " NamedUser(login=\"ajcagle8\")]\n", - "\u001b[93m\u001b[4mnotSaranshMalik\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m notSaranshMalik \u001b[0m to: \u001b[92m Group018-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to notSaranshMalik \u001b[0m\n", - "\u001b[93m\u001b[4mmnodini\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m mnodini \u001b[0m to: \u001b[92m Group018-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to mnodini \u001b[0m\n", - "\u001b[93m\u001b[4mMaryamkusman\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m Maryamkusman \u001b[0m to: \u001b[92m Group018-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to Maryamkusman \u001b[0m\n", - "\u001b[93m\u001b[4majcagle8\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m ajcagle8 \u001b[0m to: \u001b[92m Group018-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to ajcagle8 \u001b[0m\n", - "Repository \u001b[96m Group019-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"nathansit\"),\n", - " NamedUser(login=\"SSingh44343\"),\n", - " NamedUser(login=\"DaimengSun\")]\n", - "\u001b[93m\u001b[4mnathansit\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m nathansit \u001b[0m to: \u001b[92m Group019-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to nathansit \u001b[0m\n", - "\u001b[93m\u001b[4mSSingh44343\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m SSingh44343 \u001b[0m to: \u001b[92m Group019-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to SSingh44343 \u001b[0m\n", - "\u001b[93m\u001b[4mDaimengSun\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m DaimengSun \u001b[0m to: \u001b[92m Group019-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to DaimengSun \u001b[0m\n", - "Repository \u001b[96m Group020-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"ashesh8500\"),\n", - " NamedUser(login=\"ATrapenard\"),\n", - " NamedUser(login=\"meghapareek2003\")]\n", - "\u001b[93m\u001b[4mashesh8500\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m ashesh8500 \u001b[0m to: \u001b[92m Group020-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to ashesh8500 \u001b[0m\n", - "\u001b[93m\u001b[4mATrapenard\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m ATrapenard \u001b[0m to: \u001b[92m Group020-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to ATrapenard \u001b[0m\n", - "\u001b[93m\u001b[4mmeghapareek2003\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m meghapareek2003 \u001b[0m to: \u001b[92m Group020-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to meghapareek2003 \u001b[0m\n", - "Repository \u001b[96m Group021-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"JasonKrentsel\"),\n", - " NamedUser(login=\"shantellemeganserafin\"),\n", - " NamedUser(login=\"orangejustin\"),\n", - " NamedUser(login=\"jmlai08\")]\n", - "\u001b[93m\u001b[4mJasonKrentsel\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m JasonKrentsel \u001b[0m to: \u001b[92m Group021-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to JasonKrentsel \u001b[0m\n", - "\u001b[93m\u001b[4mshantellemeganserafin\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m shantellemeganserafin \u001b[0m to: \u001b[92m Group021-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to shantellemeganserafin \u001b[0m\n", - "\u001b[93m\u001b[4morangejustin\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m orangejustin \u001b[0m to: \u001b[92m Group021-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to orangejustin \u001b[0m\n", - "\u001b[93m\u001b[4mjmlai08\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m jmlai08 \u001b[0m to: \u001b[92m Group021-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to jmlai08 \u001b[0m\n", - "Repository \u001b[96m Group022-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"anchen31\"), NamedUser(login=\"nggalen\")]\n", - "\u001b[93m\u001b[4manchen31\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m anchen31 \u001b[0m to: \u001b[92m Group022-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to anchen31 \u001b[0m\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[93m\u001b[4mnggalen\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m nggalen \u001b[0m to: \u001b[92m Group022-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to nggalen \u001b[0m\n", - "Repository \u001b[96m Group023-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"VigneshJ14\"),\n", - " NamedUser(login=\"helclp\"),\n", - " NamedUser(login=\"rioak\"),\n", - " NamedUser(login=\"chrishrochez\"),\n", - " NamedUser(login=\"kpstern\")]\n", - "\u001b[93m\u001b[4mVigneshJ14\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m VigneshJ14 \u001b[0m to: \u001b[92m Group023-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to VigneshJ14 \u001b[0m\n", - "\u001b[93m\u001b[4mhelclp\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m helclp \u001b[0m to: \u001b[92m Group023-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to helclp \u001b[0m\n", - "\u001b[93m\u001b[4mrioak\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m rioak \u001b[0m to: \u001b[92m Group023-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to rioak \u001b[0m\n", - "\u001b[93m\u001b[4mchrishrochez\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m chrishrochez \u001b[0m to: \u001b[92m Group023-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to chrishrochez \u001b[0m\n", - "\u001b[93m\u001b[4mkpstern\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m kpstern \u001b[0m to: \u001b[92m Group023-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to kpstern \u001b[0m\n", - "Repository \u001b[96m Group024-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"MoMo339610\"),\n", - " NamedUser(login=\"ccaban6\"),\n", - " NamedUser(login=\"Nakshatra120\"),\n", - " NamedUser(login=\"sim-anna\")]\n", - "\u001b[93m\u001b[4mMoMo339610\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m MoMo339610 \u001b[0m to: \u001b[92m Group024-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to MoMo339610 \u001b[0m\n", - "\u001b[93m\u001b[4mccaban6\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m ccaban6 \u001b[0m to: \u001b[92m Group024-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to ccaban6 \u001b[0m\n", - "\u001b[93m\u001b[4mNakshatra120\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m Nakshatra120 \u001b[0m to: \u001b[92m Group024-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to Nakshatra120 \u001b[0m\n", - "\u001b[93m\u001b[4msim-anna\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m sim-anna \u001b[0m to: \u001b[92m Group024-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to sim-anna \u001b[0m\n", - "Repository \u001b[96m Group025-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"Shayfe\"),\n", - " NamedUser(login=\"jenniferwong1808\"),\n", - " NamedUser(login=\"belindayan1000\"),\n", - " NamedUser(login=\"hibask\")]\n", - "\u001b[93m\u001b[4mShayfe\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m Shayfe \u001b[0m to: \u001b[92m Group025-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to Shayfe \u001b[0m\n", - "\u001b[93m\u001b[4mjenniferwong1808\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m jenniferwong1808 \u001b[0m to: \u001b[92m Group025-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to jenniferwong1808 \u001b[0m\n", - "\u001b[93m\u001b[4mbelindayan1000\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m belindayan1000 \u001b[0m to: \u001b[92m Group025-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to belindayan1000 \u001b[0m\n", - "\u001b[93m\u001b[4mhibask\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m hibask \u001b[0m to: \u001b[92m Group025-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to hibask \u001b[0m\n", - "Repository \u001b[96m Group026-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"e81786\"),\n", - " NamedUser(login=\"hyun04p\"),\n", - " NamedUser(login=\"Yuanzhen-Zhu\"),\n", - " NamedUser(login=\"aaolivas\")]\n", - "\u001b[93m\u001b[4me81786\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m e81786 \u001b[0m to: \u001b[92m Group026-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to e81786 \u001b[0m\n", - "\u001b[93m\u001b[4mhyun04p\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m hyun04p \u001b[0m to: \u001b[92m Group026-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to hyun04p \u001b[0m\n", - "\u001b[93m\u001b[4mYuanzhen-Zhu\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m Yuanzhen-Zhu \u001b[0m to: \u001b[92m Group026-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to Yuanzhen-Zhu \u001b[0m\n", - "\u001b[93m\u001b[4maaolivas\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m aaolivas \u001b[0m to: \u001b[92m Group026-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to aaolivas \u001b[0m\n", - "Repository \u001b[96m Group027-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"Jddeleon1981\"),\n", - " NamedUser(login=\"natenyul\"),\n", - " NamedUser(login=\"jifsus\")]\n", - "\u001b[93m\u001b[4mJddeleon1981\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m Jddeleon1981 \u001b[0m to: \u001b[92m Group027-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to Jddeleon1981 \u001b[0m\n", - "\u001b[93m\u001b[4mnatenyul\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m natenyul \u001b[0m to: \u001b[92m Group027-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to natenyul \u001b[0m\n", - "\u001b[93m\u001b[4mjifsus\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m jifsus \u001b[0m to: \u001b[92m Group027-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to jifsus \u001b[0m\n", - "Repository \u001b[96m Group028-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"mabdilahCSE\"),\n", - " NamedUser(login=\"kylenakai\"),\n", - " NamedUser(login=\"valar23\"),\n", - " NamedUser(login=\"johnpaulonza\")]\n", - "\u001b[93m\u001b[4mmabdilahCSE\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m mabdilahCSE \u001b[0m to: \u001b[92m Group028-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to mabdilahCSE \u001b[0m\n", - "\u001b[93m\u001b[4mkylenakai\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m kylenakai \u001b[0m to: \u001b[92m Group028-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to kylenakai \u001b[0m\n", - "\u001b[93m\u001b[4mvalar23\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m valar23 \u001b[0m to: \u001b[92m Group028-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to valar23 \u001b[0m\n", - "\u001b[93m\u001b[4mjohnpaulonza\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m johnpaulonza \u001b[0m to: \u001b[92m Group028-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to johnpaulonza \u001b[0m\n", - "Repository \u001b[96m Group029-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"joshsalce\"),\n", - " NamedUser(login=\"lmitbo\"),\n", - " NamedUser(login=\"LukeSkerrett\"),\n", - " NamedUser(login=\"jubamadu\")]\n", - "\u001b[93m\u001b[4mjoshsalce\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m joshsalce \u001b[0m to: \u001b[92m Group029-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to joshsalce \u001b[0m\n", - "\u001b[93m\u001b[4mlmitbo\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m lmitbo \u001b[0m to: \u001b[92m Group029-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to lmitbo \u001b[0m\n", - "\u001b[93m\u001b[4mLukeSkerrett\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m LukeSkerrett \u001b[0m to: \u001b[92m Group029-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to LukeSkerrett \u001b[0m\n", - "\u001b[93m\u001b[4mjubamadu\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m jubamadu \u001b[0m to: \u001b[92m Group029-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to jubamadu \u001b[0m\n", - "Repository \u001b[96m Group030-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"kmbnino\"),\n", - " NamedUser(login=\"hiiminbush\"),\n", - " NamedUser(login=\"RafferyChen\"),\n", - " NamedUser(login=\"bonzonwin\")]\n", - "\u001b[93m\u001b[4mkmbnino\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m kmbnino \u001b[0m to: \u001b[92m Group030-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to kmbnino \u001b[0m\n", - "\u001b[93m\u001b[4mhiiminbush\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m hiiminbush \u001b[0m to: \u001b[92m Group030-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to hiiminbush \u001b[0m\n", - "\u001b[93m\u001b[4mRafferyChen\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m RafferyChen \u001b[0m to: \u001b[92m Group030-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to RafferyChen \u001b[0m\n", - "\u001b[93m\u001b[4mbonzonwin\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m bonzonwin \u001b[0m to: \u001b[92m Group030-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to bonzonwin \u001b[0m\n", - "Repository \u001b[96m Group031-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"kendrick010\"), NamedUser(login=\"getpakt\")]\n", - "\u001b[93m\u001b[4mkendrick010\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m kendrick010 \u001b[0m to: \u001b[92m Group031-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to kendrick010 \u001b[0m\n", - "\u001b[93m\u001b[4mgetpakt\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m getpakt \u001b[0m to: \u001b[92m Group031-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to getpakt \u001b[0m\n", - "Repository \u001b[96m Group032-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"CharlesXu-Jingyue\"),\n", - " NamedUser(login=\"hinyzee\"),\n", - " NamedUser(login=\"Zachary-chao\"),\n", - " NamedUser(login=\"smurase\")]\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[93m\u001b[4mCharlesXu-Jingyue\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m CharlesXu-Jingyue \u001b[0m to: \u001b[92m Group032-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to CharlesXu-Jingyue \u001b[0m\n", - "\u001b[93m\u001b[4mhinyzee\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m hinyzee \u001b[0m to: \u001b[92m Group032-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to hinyzee \u001b[0m\n", - "\u001b[93m\u001b[4mZachary-chao\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m Zachary-chao \u001b[0m to: \u001b[92m Group032-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to Zachary-chao \u001b[0m\n", - "\u001b[93m\u001b[4msmurase\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m smurase \u001b[0m to: \u001b[92m Group032-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to smurase \u001b[0m\n", - "Repository \u001b[96m Group033-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"jason886595\"),\n", - " NamedUser(login=\"cqrnik\"),\n", - " NamedUser(login=\"AnyaBoo\"),\n", - " NamedUser(login=\"areenlu\"),\n", - " NamedUser(login=\"TydenRucker\")]\n", - "\u001b[93m\u001b[4mjason886595\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m jason886595 \u001b[0m to: \u001b[92m Group033-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to jason886595 \u001b[0m\n", - "\u001b[93m\u001b[4mcqrnik\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m cqrnik \u001b[0m to: \u001b[92m Group033-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to cqrnik \u001b[0m\n", - "\u001b[93m\u001b[4mAnyaBoo\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m AnyaBoo \u001b[0m to: \u001b[92m Group033-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to AnyaBoo \u001b[0m\n", - "\u001b[93m\u001b[4mareenlu\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m areenlu \u001b[0m to: \u001b[92m Group033-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to areenlu \u001b[0m\n", - "\u001b[93m\u001b[4mTydenRucker\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m TydenRucker \u001b[0m to: \u001b[92m Group033-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to TydenRucker \u001b[0m\n", - "Repository \u001b[96m Group034-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"vinaypillai\"),\n", - " NamedUser(login=\"rchaklas\"),\n", - " NamedUser(login=\"kavyaabalaji\"),\n", - " NamedUser(login=\"Juan2002V\")]\n", - "\u001b[93m\u001b[4mvinaypillai\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m vinaypillai \u001b[0m to: \u001b[92m Group034-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to vinaypillai \u001b[0m\n", - "\u001b[93m\u001b[4mrchaklas\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m rchaklas \u001b[0m to: \u001b[92m Group034-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to rchaklas \u001b[0m\n", - "\u001b[93m\u001b[4mkavyaabalaji\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m kavyaabalaji \u001b[0m to: \u001b[92m Group034-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to kavyaabalaji \u001b[0m\n", - "\u001b[93m\u001b[4mJuan2002V\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m Juan2002V \u001b[0m to: \u001b[92m Group034-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to Juan2002V \u001b[0m\n", - "Repository \u001b[96m Group035-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"hyperburn777\"),\n", - " NamedUser(login=\"CristianAH\"),\n", - " NamedUser(login=\"rihusedesign\")]\n", - "\u001b[93m\u001b[4mhyperburn777\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m hyperburn777 \u001b[0m to: \u001b[92m Group035-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to hyperburn777 \u001b[0m\n", - "\u001b[93m\u001b[4mCristianAH\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m CristianAH \u001b[0m to: \u001b[92m Group035-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to CristianAH \u001b[0m\n", - "\u001b[93m\u001b[4mrihusedesign\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m rihusedesign \u001b[0m to: \u001b[92m Group035-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to rihusedesign \u001b[0m\n", - "Repository \u001b[96m Group036-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"kohlir2020\"),\n", - " NamedUser(login=\"dhavaljjani\"),\n", - " NamedUser(login=\"Mkhlf\"),\n", - " NamedUser(login=\"esti28\")]\n", - "\u001b[93m\u001b[4mkohlir2020\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m kohlir2020 \u001b[0m to: \u001b[92m Group036-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to kohlir2020 \u001b[0m\n", - "\u001b[93m\u001b[4mdhavaljjani\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m dhavaljjani \u001b[0m to: \u001b[92m Group036-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to dhavaljjani \u001b[0m\n", - "\u001b[93m\u001b[4mMkhlf\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m Mkhlf \u001b[0m to: \u001b[92m Group036-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to Mkhlf \u001b[0m\n", - "\u001b[93m\u001b[4mesti28\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m esti28 \u001b[0m to: \u001b[92m Group036-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to esti28 \u001b[0m\n", - "Repository \u001b[96m Group037-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"gyuj\"),\n", - " NamedUser(login=\"NickAzp\"),\n", - " NamedUser(login=\"kleumas\")]\n", - "\u001b[93m\u001b[4mgyuj\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m gyuj \u001b[0m to: \u001b[92m Group037-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to gyuj \u001b[0m\n", - "\u001b[93m\u001b[4mNickAzp\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m NickAzp \u001b[0m to: \u001b[92m Group037-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to NickAzp \u001b[0m\n", - "\u001b[93m\u001b[4mkleumas\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m kleumas \u001b[0m to: \u001b[92m Group037-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to kleumas \u001b[0m\n", - "Repository \u001b[96m Group038-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"crickwang\"),\n", - " NamedUser(login=\"the-bruz\"),\n", - " NamedUser(login=\"m-sherrick\")]\n", - "\u001b[93m\u001b[4mcrickwang\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m crickwang \u001b[0m to: \u001b[92m Group038-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to crickwang \u001b[0m\n", - "\u001b[93m\u001b[4mthe-bruz\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m the-bruz \u001b[0m to: \u001b[92m Group038-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to the-bruz \u001b[0m\n", - "\u001b[93m\u001b[4mm-sherrick\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m m-sherrick \u001b[0m to: \u001b[92m Group038-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to m-sherrick \u001b[0m\n", - "Repository \u001b[96m Group039-SP23 \u001b[0m:\n", - "The list of pending invitation:\n", - "[NamedUser(login=\"dannyr742\"),\n", - " NamedUser(login=\"clarissagtz\"),\n", - " NamedUser(login=\"z8jiang\"),\n", - " NamedUser(login=\"jtaolan\")]\n", - "\u001b[93m\u001b[4mdannyr742\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m dannyr742 \u001b[0m to: \u001b[92m Group039-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to dannyr742 \u001b[0m\n", - "\u001b[93m\u001b[4mclarissagtz\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m clarissagtz \u001b[0m to: \u001b[92m Group039-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to clarissagtz \u001b[0m\n", - "\u001b[93m\u001b[4mz8jiang\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m z8jiang \u001b[0m to: \u001b[92m Group039-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to z8jiang \u001b[0m\n", - "\u001b[93m\u001b[4mjtaolan\u001b[0m \u001b[91mInvite Revoked \u001b[0m\n", - "Added Collaborator: \u001b[92m jtaolan \u001b[0m to: \u001b[92m Group039-SP23 \u001b[0m with permission: \u001b[92m write \u001b[0m\n", - "\u001b[92m Invite Resent to jtaolan \u001b[0m\n", - "Repository \u001b[96m Lectures \u001b[0m:\n", - "The list of pending invitation:\n", - "[]\n", - "Repository \u001b[96m Notebooks \u001b[0m:\n", - "The list of pending invitation:\n", - "[]\n" - ] - } - ], - "source": [ - "ggroup.resent_invitations_team_repos(\n", - " team_slug=\"Instructors_Sp23\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "6cd203d4", - "metadata": {}, - "source": [ - "## The End of the Workflow\n", - "\n", - "If you still have concerns, please reach out via GitHub Issue (on the RHS bar) or reach out me directly via email: " - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "python3", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/nbs/tutorials/create template issues.ipynb b/nbs/tutorials/create template issues.ipynb deleted file mode 100644 index b45753c..0000000 --- a/nbs/tutorials/create template issues.ipynb +++ /dev/null @@ -1,108 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Populate Grading Rubric Template on GitHub Repositories\n", - "\n", - "> Create modifiable and gradable GitHub issues for project grading" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Successfully Authenticated. GitHub account: \u001b[92m scott-yj-yang \u001b[0m\n", - "Target Organization Set: \u001b[92m COGS118A \u001b[0m\n" - ] - } - ], - "source": [ - "import time\n", - "from CanvasGroupy import *\n", - "credentials_fp = \"../credentials.json\"\n", - "ghg = GitHubGroup(credentials_fp, org=\"COGS118A\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "In the repo: \u001b[92mGroup021-SP23\u001b[0m,\n", - "Issue \u001b[92mProject Proposal Feedback\u001b[0m Created!\n", - "In the repo: \u001b[92mGroup022-SP23\u001b[0m,\n", - "Issue \u001b[92mProject Proposal Feedback\u001b[0m Created!\n", - "In the repo: \u001b[92mGroup023-SP23\u001b[0m,\n", - "Issue \u001b[92mProject Proposal Feedback\u001b[0m Created!\n", - "In the repo: \u001b[92mGroup024-SP23\u001b[0m,\n", - "Issue \u001b[92mProject Proposal Feedback\u001b[0m Created!\n", - "In the repo: \u001b[92mGroup025-SP23\u001b[0m,\n", - "Issue \u001b[92mProject Proposal Feedback\u001b[0m Created!\n", - "In the repo: \u001b[92mGroup026-SP23\u001b[0m,\n", - "Issue \u001b[92mProject Proposal Feedback\u001b[0m Created!\n", - "In the repo: \u001b[92mGroup027-SP23\u001b[0m,\n", - "Issue \u001b[92mProject Proposal Feedback\u001b[0m Created!\n", - "In the repo: \u001b[92mGroup028-SP23\u001b[0m,\n", - "Issue \u001b[92mProject Proposal Feedback\u001b[0m Created!\n", - "In the repo: \u001b[92mGroup029-SP23\u001b[0m,\n", - "Issue \u001b[92mProject Proposal Feedback\u001b[0m Created!\n", - "In the repo: \u001b[92mGroup030-SP23\u001b[0m,\n", - "Issue \u001b[92mProject Proposal Feedback\u001b[0m Created!\n", - "In the repo: \u001b[92mGroup031-SP23\u001b[0m,\n", - "Issue \u001b[92mProject Proposal Feedback\u001b[0m Created!\n", - "In the repo: \u001b[92mGroup032-SP23\u001b[0m,\n", - "Issue \u001b[92mProject Proposal Feedback\u001b[0m Created!\n", - "In the repo: \u001b[92mGroup033-SP23\u001b[0m,\n", - "Issue \u001b[92mProject Proposal Feedback\u001b[0m Created!\n", - "In the repo: \u001b[92mGroup034-SP23\u001b[0m,\n", - "Issue \u001b[92mProject Proposal Feedback\u001b[0m Created!\n", - "In the repo: \u001b[92mGroup035-SP23\u001b[0m,\n", - "Issue \u001b[92mProject Proposal Feedback\u001b[0m Created!\n", - "In the repo: \u001b[92mGroup036-SP23\u001b[0m,\n", - "Issue \u001b[92mProject Proposal Feedback\u001b[0m Created!\n", - "In the repo: \u001b[92mGroup037-SP23\u001b[0m,\n", - "Issue \u001b[92mProject Proposal Feedback\u001b[0m Created!\n", - "In the repo: \u001b[92mGroup038-SP23\u001b[0m,\n", - "Issue \u001b[92mProject Proposal Feedback\u001b[0m Created!\n", - "In the repo: \u001b[92mGroup039-SP23\u001b[0m,\n", - "Issue \u001b[92mProject Proposal Feedback\u001b[0m Created!\n" - ] - } - ], - "source": [ - "for i in range(1, 40):\n", - " repo_name = f\"Group{i:03}-SP23\"\n", - " repo = ghg.get_repo(repo_name)\n", - " ghg.create_issue_from_md(repo, \"../CanvasGroupy/nbs/feedback_template/proposal_feedback.md\")\n", - " time.sleep(1)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "python3", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/test_nbs/grab_groups.ipynb b/test_nbs/grab_groups.ipynb deleted file mode 100644 index 960a2b0..0000000 --- a/test_nbs/grab_groups.ipynb +++ /dev/null @@ -1,136 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": true, - "ExecuteTime": { - "start_time": "2023-05-19T11:58:47.765728Z", - "end_time": "2023-05-19T11:58:48.845230Z" - } - }, - "outputs": [], - "source": [ - "from CanvasGroupy import *" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001B[92mAuthorization Successful!\u001B[0m\n", - "Course Set: \u001B[92m COGS 108 - Data Science in Practice - Fleischer [SP23] \u001B[0m\n", - "Getting List of Users... This might take a while...\n", - "Users Fetch Complete! The course has \u001B[94m433\u001B[0m students.\n", - "Setting Group Category... \n", - "Group Category: \u001B[92mProject Groups\u001B[0m Set!\n" - ] - } - ], - "source": [ - "cg = CanvasGroup(\"../../credentials.json\", course_id=45023, group_category=\"Project Groups\")" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "start_time": "2023-05-19T11:59:32.468910Z", - "end_time": "2023-05-19T12:00:19.270447Z" - } - } - }, - { - "cell_type": "code", - "execution_count": 5, - "outputs": [ - { - "data": { - "text/plain": "{'108108108': ['tchaudry', 'nateng', 'cvivo', 'rzou'],\n 'AAA - H': ['tbach', 'chn019', 'pap002', 'ahvuong'],\n 'Academic Weapons of Destruction': ['pfalak',\n 'miivanov',\n 'yojang',\n 'bele',\n 'ckl006'],\n \"Adri's Group\": ['fichen', 'jdayrit', 'atolenti', 'rwohnhaa'],\n 'AISHA': ['hadinh', 'sblevin', 'apeng', 'dyao', 's3zhong'],\n 'AKJA': ['aavanzado', 'kmurata', 'adschermerhorn', 'j4villan', 'jaxu'],\n 'alyson, marlyn, kenneth, jorge, kent': ['marquerupa',\n 'kdu',\n 'kkn011',\n 'aotanez',\n 'jlramos'],\n \"Benson's Group\": ['zil037', 'kusong', 'shw088', 'x8zhao'],\n 'BLESSQL': ['eflemus', 'lat005', 'svempati', 'brwalton', 'sozhu'],\n 'Bots': ['tadiep', 'm1gill', 'rug005', 'dpachchigar', 'grweber'],\n 'Burgerflippers': ['znkhan', 'ans019', 'csinghas', 'ksunkara'],\n 'Calvin & Hobbes Fan Club': ['adk003', 'a3phan', 'd1solis', 'zyuen'],\n 'Camp Snoopy Gang': ['vem', 'oleong', 'zwei', 'dyermek', 'm2zheng'],\n 'Campuswire Warriors': ['tkurpane', 'homu', 'vsinghal', 'b9yu'],\n \"Casey's Group\": ['afituri', 'child', 'jpquinto', 'lwanderley'],\n 'CATGPT': ['lujin', 'hal065', 'xil093', 'tewang', 'yuy047'],\n 'Chat GPT & co.': ['m9chen', 'zdunmire', 'lmathers', 'knero'],\n 'Cindy Linzy Saleha Giovanni': ['sahmedi', 'nbeserle', 'ljmueller', 'ctn003'],\n 'Code Crusaders': ['yal067', 'zhl118', 'p3sun', 'zhw049'],\n 'Coding Experts': ['dboateng', 'skarumud', 'jyl006', 'ypatki', 'psodhi'],\n 'Cogito Ergo Sum': ['lanand', 'kearora', 'asc002', 'cachiu', 'eiroman'],\n 'cogs 108 proj': ['mak009', 'hel011', 'attan', 'ataneja'],\n 'COGS-108-Data-ers': ['aschen', 'redejer', 's1joshi', 'juk018', 'syassein'],\n 'COGS108 Group': ['nichiu', 'rideng', 'srnaik', 'rpaner', 'sksarmie'],\n 'COGS108 Project Group': ['sgmerek',\n 'emikulec',\n 'kpage',\n 'cjvelasq',\n 'tlwoo'],\n 'Cogs108 Team': ['ken003', 'ttn077', 'nprimero', 'a5vo', 'tiz009'],\n 'cogs108gang': ['esk003', 'm2malik', 'vsasaoka', 'atabares'],\n 'cogs108project': ['xic054', 'yug006', 'zliu', 'q1peng', 'zhw040'],\n 'Data Demons': ['k3alvare', 'aliem', 'ttanaka', 'fltang', 'jez006'],\n 'Data Detectives': ['tgunawan', 'mmerten', 'mypan', 'nppatil', 'bsekhon'],\n 'Data Ninjas': ['dol008', 'yul156', 'huw005', 'zew003', 'ziz056'],\n 'Data Science Majors, We Are Not': ['ijjones',\n 'wakim',\n 'jkononov',\n 'basimon',\n 'csulaiman'],\n 'Data with Destiny': ['oaaung', 'vbatres', 'nsaboonchi', 'qutran', 'dbwade'],\n 'DigitSapiens': ['ybo', 'kec020', 'fagao', 'h4he', 'xkan'],\n 'Doujiang Youtiao': ['dufu', 'jih028', 'z6pan', 'dez002'],\n 'DREWS Project': ['rrdu', 'wijin', 'dkong', 'ekudriavtsev', 'samao'],\n 'emma, beverly, sam, shad, jinuk': ['econteh',\n 'jul076',\n 's9reyes',\n 'ssayson',\n 'bto'],\n 'Gamer Gang (GG)': ['gdheilly', 'amn001', 'd3thai', 'r1truong', 'kwesterm'],\n 'GEARS108': ['hdaustin', 'c1dong', 'abnishaa', 'ndtorres', 'bvinhnee'],\n 'GETZS': ['sburbach', 'trdu', 'zhori', 'g6lin', 'e3yu'],\n 'Group dino nuggets': ['amdenny', 'hojin', 'atorok', 'nueda'],\n 'GroupSpectators': ['wiliao', 'yiw092', 'wwu', 'gozhou'],\n 'grup': [],\n 'i dont even know': ['k1freema', 'dmanoiu', 's8subram'],\n 'I Like Big Data and I Cannot Lie': ['nbuhr',\n 'apcalpe',\n 'acanonig',\n 'jkovar'],\n 'InsertGroupName': ['vibai', 'mvf001', 'mloeraar', 'anm020', 'zesun'],\n 'Jack, Matt, Iisha, Alex, Michael': ['wgascarosas',\n 'mahuynh',\n 'ikshatriya',\n 'mmmansour',\n 'q4tran'],\n 'Jake, Sophie, Jordan, Ian, Emma': ['ebackman',\n 'jgliniak',\n 'ijanson',\n 'jplee',\n 'smazor'],\n 'Kendata': ['azchoi', 'a2ko', 'r6ma'],\n 'Khoa, Rafe, Ethan, Andy, Josephine': ['rgerson',\n 'anmai',\n 'j1nguyen',\n 'ktnguyen',\n 'tkt004'],\n 'MaYun Ta Die': ['xic039', 'y6du', 'y2geng', 'haw062', 'yay010'],\n \"McDonald's\": ['chh029', 'yupang', 'z3qi', 'sswu'],\n 'MilkTea': ['jeg024', 'gkao', 'kkogami', 'kqwu', 'kayamamo'],\n 'Neural Network Ninjas': ['gikwon',\n 'jkmolina',\n 'wmonroe',\n 'visalcedo',\n 'rsohan'],\n 'Number One Best Group': ['sibhagat', 'all010', 'bpollack'],\n 'Panda Express': ['zeh003', 'xil173', 'c7qian', 'yis004', 'ruw018'],\n 'POGS108': ['tambat', 'jhn006', 'stpark', 'zsyed'],\n 'Project Group 0': ['acortes', 'jam003', 'tnn009', 'vhnguyen', 'saw003'],\n 'Project Group 1': ['jvbui', 'tqdo', 'njahan', 'sprestrelski', 'jew005'],\n 'Project Group 2': ['omakbari', 'mlucernas', 'jaouyang'],\n 'Project Group 3': ['mbachar', 'arh003', 'bhkwon', 'auhm', 'etwang'],\n 'Project Group 5': ['z9sun', 'shw007', 'h7xie', 'yux029'],\n 'Project Group 9': ['yug027', 'ziguan', 'yum013', 'hozhao'],\n 'Project Group 10': ['kkn003', 'dtp004', 'kvphan', 't8vo'],\n 'Project Group 11': [],\n 'Project Group 12': ['boho', 'jis033', 'yuw095', 'xuy001'],\n 'Project Group 14': ['mastaund', 'cmmcdonald', 't7ngo', 'gzatarai'],\n 'Project Group 16': ['ifulgenc', 'rygoh', 'ahiyer', 'pdn002'],\n 'Project Group 18': ['ckentish', 'rkodali', 'bmlincol'],\n 'Project Group 19': ['lloo', 'ax008752', 'h5ly'],\n 'Project Group 20': ['edmadera', 'smarwah', 'jwmccull', 'rlothanh'],\n 'Project Group 21': ['eparise', 'spierce', 'nporemba', 'jeqi'],\n 'Project Group 22': ['lgtan', 'mtedjo', 'jtornero', 'atrapena'],\n 'Project Group 23': ['zhw058', 'y2wei', 'h5yoon'],\n 'Project Group 24': ['chsanche', 'dysander', 'arsobhan', 'sfsuresh'],\n 'Queen Mac & loyal subjects': ['gbengard', 'jel031', 'epei', 'mreta', 'mnyu'],\n 'Raffery': ['zcarolino', 'ruc003', 'bcurrier', 'rifische'],\n 'Rando': ['tgatewood', 'k1htet', 'jil029', 'semcdonnell'],\n 'RandomName': ['w3dong', 'jil185', 'mil011', 'yus029'],\n 'Rise and Grind': ['ncardozo', 'stfuhrma', 'tal005', 'sminowada'],\n 'shilpa, chris, daniel, and aman.': ['schowbey',\n 'akar',\n 'drmathew',\n 'cvillafa'],\n 'Slayerz': ['ybaher', 'nhelsherif', 'skjandu', 'mbsharma'],\n 'Steven, Sandy, Ruiqi, Yiming, Joyce': ['yijia',\n 'zil039',\n 'ruw004',\n 'j4xie',\n 'q1yan'],\n 'Subway Before 108': ['jpc005', 'd6le', 'anl049', 'jal086', 'maw022'],\n 'TBD': ['vavila', 'srh002', 'j8ly', 'nhn005'],\n 'Team 108': ['d2cook', 'ndamle', 'dfinn', 'alotti', 'psaluja'],\n 'Team NERV': ['m1cohen', 'ahamid', 'thsiao', 'hmendoza', 'jpalicki'],\n 'Team Number 1': ['hoguan', 'kdl002', 'jiy040', 'wiyu'],\n 'Team Real': ['hajin', 'wpalmaor'],\n 'The A Team': ['a3barbosa', 'adchung', 'a1ho', 'amanlangit', 'anivarthi'],\n 'The data avengers': ['erchen', 'zhl023', 'zsui', 'zixiong'],\n 'The Group': ['csk025', 'lkebir', 'brlu', 'snsheth', 'awixson'],\n 'The group chat': ['nadelrosario',\n 'lshwang',\n 'mjignacio',\n 'spiltch',\n 'gdwhite'],\n 'The Quack GACK Group 🦆': ['kal049', 'ajpan', 'gryang', 'caz002'],\n 'WeebLosers': ['kjl001', 'alow', 'mapeng', 'wsperry', 'm3sun'],\n 'Welcome new teammates': ['y1chu', 'dblee', 'gleong', 'cll007', 'rtpham'],\n 'Weston, Nicole, Adi, Sophia, Katie': ['wchester',\n 'sconti',\n 'kdgao',\n 'n5liu',\n 'adsriram'],\n 'Yes': ['mjee', 'rrohinku', 'gmzhou'],\n 'yippee!': ['jjl025', 'apandey', 'rricafor', 'avallian', 'mavillen'],\n 'Zachary, Jisoo, Ryan, Goldar': ['zachao', 'jik006', 'goluu', 'rvanny']}" - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cg.group_to_emails" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "start_time": "2023-05-19T12:00:19.279053Z", - "end_time": "2023-05-19T12:00:19.286235Z" - } - } - }, - { - "cell_type": "code", - "execution_count": 8, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "grup\n", - "i dont even know\n", - "Kendata\n", - "Number One Best Group\n", - "Project Group 2\n", - "Project Group 11\n", - "Project Group 18\n", - "Project Group 19\n", - "Project Group 23\n", - "Team Real\n", - "Yes\n" - ] - } - ], - "source": [ - "for name, member in cg.group_to_emails.items():\n", - " if len(member) <= 3:\n", - " print(name)" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "start_time": "2023-05-19T12:00:32.002580Z", - "end_time": "2023-05-19T12:00:32.004911Z" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false - } - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 2 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.6" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/test_nbs/grading.ipynb b/test_nbs/grading.ipynb deleted file mode 100644 index 037c9fb..0000000 --- a/test_nbs/grading.ipynb +++ /dev/null @@ -1,199 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": true, - "ExecuteTime": { - "start_time": "2023-05-25T10:35:49.579807Z", - "end_time": "2023-05-25T10:35:49.947093Z" - } - }, - "outputs": [], - "source": [ - "from CanvasGroupy import *" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Successfully Authenticated. GitHub account: \u001B[92m scott-yj-yang \u001B[0m\n", - "Target Organization Set: \u001B[92m COGS118A \u001B[0m\n", - "\u001B[92mAuthorization Successful!\u001B[0m\n", - "Course Set: \u001B[92m COGS 118A - Supvr/Mach Learning Algorithms - Fleischer [SP23] \u001B[0m\n", - "Getting List of Users... This might take a while...\n", - "Users Fetch Complete! The course has \u001B[94m160\u001B[0m students.\n", - "Setting Group Category... \n", - "Group Category: \u001B[92mFinal Project\u001B[0m Set!\n" - ] - } - ], - "source": [ - "ghg = GitHubGroup(\"../../credentials.json\", org=\"COGS118A\")\n", - "cg = CanvasGroup(\"../../credentials.json\", course_id=45059, group_category=\"Final Project\")" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "start_time": "2023-05-25T10:35:50.387886Z", - "end_time": "2023-05-25T10:36:05.508490Z" - } - } - }, - { - "cell_type": "code", - "execution_count": 3, - "outputs": [], - "source": [ - "grading = Grading(ghg, cg)" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "start_time": "2023-05-25T10:36:05.509929Z", - "end_time": "2023-05-25T10:36:05.511553Z" - } - } - }, - { - "cell_type": "code", - "execution_count": 4, - "outputs": [], - "source": [ - "repo = ghg.get_repo(\"Group1-SP23-Testing\")" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "start_time": "2023-05-25T10:10:37.976641Z", - "end_time": "2023-05-25T10:10:38.424073Z" - } - } - }, - { - "cell_type": "code", - "execution_count": 6, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Assignment \u001B[92mFinal Project Proposal\u001B[0m Link!\n", - "Grade for \u001B[92mgrader-cogs118a-01\u001B[0m Posted!\n" - ] - } - ], - "source": [ - "grading.grade_project(repo, \"Proposal\", assignment_id=651728, post=True)" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "start_time": "2023-05-18T23:54:03.741163Z", - "end_time": "2023-05-18T23:54:05.137950Z" - } - } - }, - { - "cell_type": "code", - "execution_count": 4, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001B[92mGroup001-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup002-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup003-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup004-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup005-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup006-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup007-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup008-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup009-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup010-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup011-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup012-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup013-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup014-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup015-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup016-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup017-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup018-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup019-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup020-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup021-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup022-SP23's proposal Graded. \u001B[0m\n", - "\u001B[93mGroup023-SP23's proposal Not Graded. \u001B[0m\n", - "\u001B[92mGroup024-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup025-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup026-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup027-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup028-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup029-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup030-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup031-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup032-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup033-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup034-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup035-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup036-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup037-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup038-SP23's proposal Graded. \u001B[0m\n", - "\u001B[92mGroup039-SP23's proposal Graded. \u001B[0m\n" - ] - } - ], - "source": [ - "for i in range(1, 40):\n", - " repo_name = f\"Group{i:03}-SP23\"\n", - " repo = ghg.get_repo(repo_name)\n", - " grading.check_graded(repo, \"proposal\")\n", - " # grading.grade_project(repo, \"Proposal\", assignment_id=651728, post=True)" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "start_time": "2023-05-25T10:36:05.513985Z", - "end_time": "2023-05-25T10:36:30.429208Z" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false - } - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 2 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.6" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} From 427264088effbf5f26a00270633958e2a8517d92 Mon Sep 17 00:00:00 2001 From: scott-yj-yang Date: Fri, 27 Feb 2026 16:17:58 -0800 Subject: [PATCH 17/24] feat: add mkdocs-jupyter plugin with demo notebook tutorial Notebooks with pre-computed outputs can now be used as documentation pages alongside markdown, giving the same output-style rendering that nbdev/Quarto provided. Co-Authored-By: Claude Opus 4.6 --- docs/tutorials/assign-groups-demo.ipynb | 174 ++++++ mkdocs.yml | 4 + pyproject.toml | 1 + uv.lock | 717 ++++++++++++++++++++++++ 4 files changed, 896 insertions(+) create mode 100644 docs/tutorials/assign-groups-demo.ipynb diff --git a/docs/tutorials/assign-groups-demo.ipynb b/docs/tutorials/assign-groups-demo.ipynb new file mode 100644 index 0000000..36fe131 --- /dev/null +++ b/docs/tutorials/assign-groups-demo.ipynb @@ -0,0 +1,174 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Assign Groups Demo\n", + "\n", + "This notebook demonstrates how to load group assignments from a CSV or DataFrame using `AssignGroup`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load groups from a DataFrame" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "

    \n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    group_namestudent_id
    0Team_Alphaalice
    1Team_Alphabob
    2Team_Alphacarol
    3Team_Betadave
    4Team_Betaeve
    5Team_Betafrank
    \n
    " + ], + "text/plain": [ + " group_name student_id\n0 Team_Alpha alice\n1 Team_Alpha bob\n2 Team_Alpha carol\n3 Team_Beta dave\n4 Team_Beta eve\n5 Team_Beta frank" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import pandas as pd\n", + "\n", + "df = pd.DataFrame({\n", + " \"group_name\": [\"Team_Alpha\", \"Team_Alpha\", \"Team_Alpha\", \"Team_Beta\", \"Team_Beta\", \"Team_Beta\"],\n", + " \"student_id\": [\"alice\", \"bob\", \"carol\", \"dave\", \"eve\", \"frank\"],\n", + "})\n", + "df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Use AssignGroup to load and inspect groups\n", + "\n", + "We use `unittest.mock.MagicMock` here to simulate the Canvas and GitHub services without real API credentials." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loaded groups:\n", + " Team_Alpha: ['alice', 'bob', 'carol']\n", + " Team_Beta: ['dave', 'eve', 'frank']\n" + ] + } + ], + "source": [ + "from unittest.mock import MagicMock\n", + "from CanvasGroupy import AssignGroup\n", + "\n", + "# Mock the Canvas and GitHub services for demo purposes\n", + "mock_cg = MagicMock()\n", + "mock_ghg = MagicMock()\n", + "\n", + "ag = AssignGroup(ghg=mock_ghg, cg=mock_cg)\n", + "ag.load_groups(df)\n", + "\n", + "print(\"Loaded groups:\")\n", + "for name, members in ag.groups.items():\n", + " print(f\" {name}: {members}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load groups from a CSV file\n", + "\n", + "You can also pass a file path to a CSV with the same `group_name` and `student_id` columns:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CSV content:\n", + "group_name,student_id\n", + "Team_Alpha,alice\n", + "Team_Alpha,bob\n", + "Team_Beta,carol\n", + "Team_Beta,dave\n", + "\n", + "Loaded from CSV:\n", + " Team_Alpha: ['alice', 'bob']\n", + " Team_Beta: ['carol', 'dave']\n" + ] + } + ], + "source": [ + "import tempfile, os\n", + "\n", + "csv_content = \"\"\"group_name,student_id\n", + "Team_Alpha,alice\n", + "Team_Alpha,bob\n", + "Team_Beta,carol\n", + "Team_Beta,dave\"\"\"\n", + "\n", + "print(\"CSV content:\")\n", + "print(csv_content)\n", + "\n", + "# Write to a temp file and load\n", + "with tempfile.NamedTemporaryFile(mode=\"w\", suffix=\".csv\", delete=False) as f:\n", + " f.write(csv_content)\n", + " csv_path = f.name\n", + "\n", + "ag2 = AssignGroup(ghg=mock_ghg, cg=mock_cg)\n", + "ag2.load_groups(csv_path)\n", + "\n", + "print(\"\\nLoaded from CSV:\")\n", + "for name, members in ag2.groups.items():\n", + " print(f\" {name}: {members}\")\n", + "\n", + "os.unlink(csv_path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Next steps\n", + "\n", + "Once groups are loaded, you can:\n", + "\n", + "- Call `ag.create_canvas_group(in_group_category=\"...\")` to create groups on Canvas\n", + "- Call `ag.create_github_group(username_quiz_id=...)` to create GitHub repos for each group\n", + "\n", + "See the [Canvas Groups](canvas-groups.md) and [GitHub Repos](github-repos.md) tutorials for details." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.12.0" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/mkdocs.yml b/mkdocs.yml index 42dfb1a..b00a9d1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -26,6 +26,9 @@ theme: plugins: - search + - mkdocs-jupyter: + include_source: true + execute: false - mkdocstrings: handlers: python: @@ -55,6 +58,7 @@ nav: - Authentication: getting-started/authentication.md - Quick Start: getting-started/quickstart.md - Tutorials: + - Assign Groups Demo: tutorials/assign-groups-demo.ipynb - Canvas Groups: tutorials/canvas-groups.md - GitHub Repos: tutorials/github-repos.md - Grading Workflow: tutorials/grading.md diff --git a/pyproject.toml b/pyproject.toml index d5d8620..c141404 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,7 @@ dev = [ docs = [ "mkdocs-material", "mkdocstrings[python]", + "mkdocs-jupyter", ] [project.urls] diff --git a/uv.lock b/uv.lock index b033c1d..d9f5af1 100644 --- a/uv.lock +++ b/uv.lock @@ -10,6 +10,15 @@ resolution-markers = [ "python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] +[[package]] +name = "appnope" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170, upload-time = "2024-02-06T09:43:11.258Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321, upload-time = "2024-02-06T09:43:09.663Z" }, +] + [[package]] name = "arrow" version = "1.4.0" @@ -23,6 +32,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl", hash = "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205", size = 68797, upload-time = "2025-10-18T17:46:45.663Z" }, ] +[[package]] +name = "asttokens" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" }, +] + +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, +] + [[package]] name = "babel" version = "2.18.0" @@ -46,6 +73,36 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/21/f8/d02f650c47d05034dcd6f9c8cf94f39598b7a89c00ecda0ecb2911bc27e9/backrefs-6.2-py39-none-any.whl", hash = "sha256:664e33cd88c6840b7625b826ecf2555f32d491800900f5a541f772c485f7cda7", size = 381077, upload-time = "2026-02-16T19:10:13.74Z" }, ] +[[package]] +name = "beautifulsoup4" +version = "4.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, +] + +[[package]] +name = "bleach" +version = "6.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/18/3c8523962314be6bf4c8989c79ad9531c825210dd13a8669f6b84336e8bd/bleach-6.3.0.tar.gz", hash = "sha256:6f3b91b1c0a02bb9a78b5a454c92506aa0fdf197e1d5e114d2e00c6f64306d22", size = 203533, upload-time = "2025-10-27T17:57:39.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl", hash = "sha256:fe10ec77c93ddf3d13a73b035abaac7a9f5e436513864ccdad516693213c65d6", size = 164437, upload-time = "2025-10-27T17:57:37.538Z" }, +] + +[package.optional-dependencies] +css = [ + { name = "tinycss2" }, +] + [[package]] name = "canvasapi" version = "3.4.0" @@ -76,6 +133,7 @@ dev = [ { name = "pytest" }, ] docs = [ + { name = "mkdocs-jupyter" }, { name = "mkdocs-material" }, { name = "mkdocstrings", extra = ["python"] }, ] @@ -83,6 +141,7 @@ docs = [ [package.metadata] requires-dist = [ { name = "canvasapi" }, + { name = "mkdocs-jupyter", marker = "extra == 'docs'" }, { name = "mkdocs-material", marker = "extra == 'docs'" }, { name = "mkdocstrings", extras = ["python"], marker = "extra == 'docs'" }, { name = "numpy" }, @@ -236,6 +295,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "comm" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/13/7d740c5849255756bc17888787313b61fd38a0a8304fc4f073dfc46122aa/comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971", size = 6319, upload-time = "2025-07-25T14:02:04.452Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294, upload-time = "2025-07-25T14:02:02.896Z" }, +] + [[package]] name = "cryptography" version = "46.0.5" @@ -289,6 +357,63 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/48/ef/0c2f4a8e31018a986949d34a01115dd057bf536905dca38897bacd21fac3/cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595", size = 3467050, upload-time = "2026-02-10T19:18:18.899Z" }, ] +[[package]] +name = "debugpy" +version = "1.8.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/b7/cd8080344452e4874aae67c40d8940e2b4d47b01601a8fd9f44786c757c7/debugpy-1.8.20.tar.gz", hash = "sha256:55bc8701714969f1ab89a6d5f2f3d40c36f91b2cbe2f65d98bf8196f6a6a2c33", size = 1645207, upload-time = "2026-01-29T23:03:28.199Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/57/7f34f4736bfb6e00f2e4c96351b07805d83c9a7b33d28580ae01374430f7/debugpy-1.8.20-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:4ae3135e2089905a916909ef31922b2d733d756f66d87345b3e5e52b7a55f13d", size = 2550686, upload-time = "2026-01-29T23:03:42.023Z" }, + { url = "https://files.pythonhosted.org/packages/ab/78/b193a3975ca34458f6f0e24aaf5c3e3da72f5401f6054c0dfd004b41726f/debugpy-1.8.20-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:88f47850a4284b88bd2bfee1f26132147d5d504e4e86c22485dfa44b97e19b4b", size = 4310588, upload-time = "2026-01-29T23:03:43.314Z" }, + { url = "https://files.pythonhosted.org/packages/c1/55/f14deb95eaf4f30f07ef4b90a8590fc05d9e04df85ee379712f6fb6736d7/debugpy-1.8.20-cp312-cp312-win32.whl", hash = "sha256:4057ac68f892064e5f98209ab582abfee3b543fb55d2e87610ddc133a954d390", size = 5331372, upload-time = "2026-01-29T23:03:45.526Z" }, + { url = "https://files.pythonhosted.org/packages/a1/39/2bef246368bd42f9bd7cba99844542b74b84dacbdbea0833e610f384fee8/debugpy-1.8.20-cp312-cp312-win_amd64.whl", hash = "sha256:a1a8f851e7cf171330679ef6997e9c579ef6dd33c9098458bd9986a0f4ca52e3", size = 5372835, upload-time = "2026-01-29T23:03:47.245Z" }, + { url = "https://files.pythonhosted.org/packages/15/e2/fc500524cc6f104a9d049abc85a0a8b3f0d14c0a39b9c140511c61e5b40b/debugpy-1.8.20-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:5dff4bb27027821fdfcc9e8f87309a28988231165147c31730128b1c983e282a", size = 2539560, upload-time = "2026-01-29T23:03:48.738Z" }, + { url = "https://files.pythonhosted.org/packages/90/83/fb33dcea789ed6018f8da20c5a9bc9d82adc65c0c990faed43f7c955da46/debugpy-1.8.20-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:84562982dd7cf5ebebfdea667ca20a064e096099997b175fe204e86817f64eaf", size = 4293272, upload-time = "2026-01-29T23:03:50.169Z" }, + { url = "https://files.pythonhosted.org/packages/a6/25/b1e4a01bfb824d79a6af24b99ef291e24189080c93576dfd9b1a2815cd0f/debugpy-1.8.20-cp313-cp313-win32.whl", hash = "sha256:da11dea6447b2cadbf8ce2bec59ecea87cc18d2c574980f643f2d2dfe4862393", size = 5331208, upload-time = "2026-01-29T23:03:51.547Z" }, + { url = "https://files.pythonhosted.org/packages/13/f7/a0b368ce54ffff9e9028c098bd2d28cfc5b54f9f6c186929083d4c60ba58/debugpy-1.8.20-cp313-cp313-win_amd64.whl", hash = "sha256:eb506e45943cab2efb7c6eafdd65b842f3ae779f020c82221f55aca9de135ed7", size = 5372930, upload-time = "2026-01-29T23:03:53.585Z" }, + { url = "https://files.pythonhosted.org/packages/33/2e/f6cb9a8a13f5058f0a20fe09711a7b726232cd5a78c6a7c05b2ec726cff9/debugpy-1.8.20-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:9c74df62fc064cd5e5eaca1353a3ef5a5d50da5eb8058fcef63106f7bebe6173", size = 2538066, upload-time = "2026-01-29T23:03:54.999Z" }, + { url = "https://files.pythonhosted.org/packages/c5/56/6ddca50b53624e1ca3ce1d1e49ff22db46c47ea5fb4c0cc5c9b90a616364/debugpy-1.8.20-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:077a7447589ee9bc1ff0cdf443566d0ecf540ac8aa7333b775ebcb8ce9f4ecad", size = 4269425, upload-time = "2026-01-29T23:03:56.518Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d9/d64199c14a0d4c476df46c82470a3ce45c8d183a6796cfb5e66533b3663c/debugpy-1.8.20-cp314-cp314-win32.whl", hash = "sha256:352036a99dd35053b37b7803f748efc456076f929c6a895556932eaf2d23b07f", size = 5331407, upload-time = "2026-01-29T23:03:58.481Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d9/1f07395b54413432624d61524dfd98c1a7c7827d2abfdb8829ac92638205/debugpy-1.8.20-cp314-cp314-win_amd64.whl", hash = "sha256:a98eec61135465b062846112e5ecf2eebb855305acc1dfbae43b72903b8ab5be", size = 5372521, upload-time = "2026-01-29T23:03:59.864Z" }, + { url = "https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl", hash = "sha256:5be9bed9ae3be00665a06acaa48f8329d2b9632f15fd09f6a9a8c8d9907e54d7", size = 5337658, upload-time = "2026-01-29T23:04:17.404Z" }, +] + +[[package]] +name = "decorator" +version = "5.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, +] + +[[package]] +name = "executing" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, +] + +[[package]] +name = "fastjsonschema" +version = "2.21.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/b5/23b216d9d985a956623b6bd12d4086b60f0059b27799f23016af04a74ea1/fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de", size = 374130, upload-time = "2025-08-14T18:49:36.666Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463", size = 24024, upload-time = "2025-08-14T18:49:34.776Z" }, +] + [[package]] name = "ghp-import" version = "2.1.0" @@ -327,6 +452,75 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] +[[package]] +name = "ipykernel" +version = "6.31.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "appnope", marker = "sys_platform == 'darwin'" }, + { name = "comm" }, + { name = "debugpy" }, + { name = "ipython" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "matplotlib-inline" }, + { name = "nest-asyncio" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a5/1d/d5ba6edbfe6fae4c3105bca3a9c889563cc752c7f2de45e333164c7f4846/ipykernel-6.31.0.tar.gz", hash = "sha256:2372ce8bc1ff4f34e58cafed3a0feb2194b91fc7cad0fc72e79e47b45ee9e8f6", size = 167493, upload-time = "2025-10-20T11:42:39.948Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl", hash = "sha256:abe5386f6ced727a70e0eb0cf1da801fa7c5fa6ff82147747d5a0406cd8c94af", size = 117003, upload-time = "2025-10-20T11:42:37.502Z" }, +] + +[[package]] +name = "ipython" +version = "9.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "ipython-pygments-lexers" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/60/2111715ea11f39b1535bed6024b7dec7918b71e5e5d30855a5b503056b50/ipython-9.10.0.tar.gz", hash = "sha256:cd9e656be97618a0676d058134cd44e6dc7012c0e5cb36a9ce96a8c904adaf77", size = 4426526, upload-time = "2026-02-02T10:00:33.594Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/aa/898dec789a05731cd5a9f50605b7b44a72bd198fd0d4528e11fc610177cc/ipython-9.10.0-py3-none-any.whl", hash = "sha256:c6ab68cc23bba8c7e18e9b932797014cc61ea7fd6f19de180ab9ba73e65ee58d", size = 622774, upload-time = "2026-02-02T10:00:31.503Z" }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, +] + +[[package]] +name = "jedi" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, +] + [[package]] name = "jinja2" version = "3.1.6" @@ -339,6 +533,87 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] +[[package]] +name = "jsonschema" +version = "4.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + +[[package]] +name = "jupyter-client" +version = "8.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-core" }, + { name = "python-dateutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/e4/ba649102a3bc3fbca54e7239fb924fd434c766f855693d86de0b1f2bec81/jupyter_client-8.8.0.tar.gz", hash = "sha256:d556811419a4f2d96c869af34e854e3f059b7cc2d6d01a9cd9c85c267691be3e", size = 348020, upload-time = "2026-01-08T13:55:47.938Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl", hash = "sha256:f93a5b99c5e23a507b773d3a1136bd6e16c67883ccdbd9a829b0bbdb98cd7d7a", size = 107371, upload-time = "2026-01-08T13:55:45.562Z" }, +] + +[[package]] +name = "jupyter-core" +version = "5.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "platformdirs" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/49/9d1284d0dc65e2c757b74c6687b6d319b02f822ad039e5c512df9194d9dd/jupyter_core-5.9.1.tar.gz", hash = "sha256:4d09aaff303b9566c3ce657f580bd089ff5c91f5f89cf7d8846c3cdf465b5508", size = 89814, upload-time = "2025-10-16T19:19:18.444Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl", hash = "sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407", size = 29032, upload-time = "2025-10-16T19:19:16.783Z" }, +] + +[[package]] +name = "jupyterlab-pygments" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/51/9187be60d989df97f5f0aba133fa54e7300f17616e065d1ada7d7646b6d6/jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d", size = 512900, upload-time = "2023-11-23T09:26:37.44Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884, upload-time = "2023-11-23T09:26:34.325Z" }, +] + +[[package]] +name = "jupytext" +version = "1.19.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "mdit-py-plugins" }, + { name = "nbformat" }, + { name = "packaging" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/a5/80c02f307c8ce863cb33e27daf049315e9d96979e14eead700923b5ec9cc/jupytext-1.19.1.tar.gz", hash = "sha256:82587c07e299173c70ed5e8ec7e75183edf1be289ed518bab49ad0d4e3d5f433", size = 4307829, upload-time = "2026-01-25T21:35:13.276Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl", hash = "sha256:d8975035155d034bdfde5c0c37891425314b7ea8d3a6c4b5d18c294348714cd9", size = 170478, upload-time = "2026-01-25T21:35:11.17Z" }, +] + [[package]] name = "markdown" version = "3.10.2" @@ -348,6 +623,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, ] +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + [[package]] name = "markupsafe" version = "3.0.3" @@ -411,6 +698,39 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, ] +[[package]] +name = "matplotlib-inline" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/74/97e72a36efd4ae2bccb3463284300f8953f199b5ffbc04cbbb0ec78f74b1/matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe", size = 8110, upload-time = "2025-10-23T09:00:22.126Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" }, +] + +[[package]] +name = "mdit-py-plugins" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b2/fd/a756d36c0bfba5f6e39a1cdbdbfdd448dc02692467d83816dff4592a1ebc/mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6", size = 44655, upload-time = "2025-08-11T07:25:49.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl", hash = "sha256:07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f", size = 57205, upload-time = "2025-08-11T07:25:47.597Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + [[package]] name = "mergedeep" version = "1.3.4" @@ -420,6 +740,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" }, ] +[[package]] +name = "mistune" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/55/d01f0c4b45ade6536c51170b9043db8b2ec6ddf4a35c7ea3f5f559ac935b/mistune-3.2.0.tar.gz", hash = "sha256:708487c8a8cdd99c9d90eb3ed4c3ed961246ff78ac82f03418f5183ab70e398a", size = 95467, upload-time = "2025-12-23T11:36:34.994Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl", hash = "sha256:febdc629a3c78616b94393c6580551e0e34cc289987ec6c35ed3f4be42d0eee1", size = 53598, upload-time = "2025-12-23T11:36:33.211Z" }, +] + [[package]] name = "mkdocs" version = "1.6.1" @@ -472,6 +801,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521, upload-time = "2023-11-20T17:51:08.587Z" }, ] +[[package]] +name = "mkdocs-jupyter" +version = "0.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ipykernel" }, + { name = "jupytext" }, + { name = "mkdocs" }, + { name = "mkdocs-material" }, + { name = "nbconvert" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6c/23/6ffb8d2fd2117aa860a04c6fe2510b21bc3c3c085907ffdd851caba53152/mkdocs_jupyter-0.25.1.tar.gz", hash = "sha256:0e9272ff4947e0ec683c92423a4bfb42a26477c103ab1a6ab8277e2dcc8f7afe", size = 1626747, upload-time = "2024-10-15T14:56:32.373Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl", hash = "sha256:3f679a857609885d322880e72533ef5255561bbfdb13cfee2a1e92ef4d4ad8d8", size = 1456197, upload-time = "2024-10-15T14:56:29.854Z" }, +] + [[package]] name = "mkdocs-material" version = "9.7.3" @@ -539,6 +885,70 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl", hash = "sha256:0b83513478bdfd803ff05aa43e9b1fca9dd22bcd9471f09ca6257f009bc5ee12", size = 104779, upload-time = "2026-02-20T10:38:34.517Z" }, ] +[[package]] +name = "nbclient" +version = "0.10.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "nbformat" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/91/1c1d5a4b9a9ebba2b4e32b8c852c2975c872aec1fe42ab5e516b2cecd193/nbclient-0.10.4.tar.gz", hash = "sha256:1e54091b16e6da39e297b0ece3e10f6f29f4ac4e8ee515d29f8a7099bd6553c9", size = 62554, upload-time = "2025-12-23T07:45:46.369Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl", hash = "sha256:9162df5a7373d70d606527300a95a975a47c137776cd942e52d9c7e29ff83440", size = 25465, upload-time = "2025-12-23T07:45:44.51Z" }, +] + +[[package]] +name = "nbconvert" +version = "7.17.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "bleach", extra = ["css"] }, + { name = "defusedxml" }, + { name = "jinja2" }, + { name = "jupyter-core" }, + { name = "jupyterlab-pygments" }, + { name = "markupsafe" }, + { name = "mistune" }, + { name = "nbclient" }, + { name = "nbformat" }, + { name = "packaging" }, + { name = "pandocfilters" }, + { name = "pygments" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/47/81f886b699450d0569f7bc551df2b1673d18df7ff25cc0c21ca36ed8a5ff/nbconvert-7.17.0.tar.gz", hash = "sha256:1b2696f1b5be12309f6c7d707c24af604b87dfaf6d950794c7b07acab96dda78", size = 862855, upload-time = "2026-01-29T16:37:48.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl", hash = "sha256:4f99a63b337b9a23504347afdab24a11faa7d86b405e5c8f9881cd313336d518", size = 261510, upload-time = "2026-01-29T16:37:46.322Z" }, +] + +[[package]] +name = "nbformat" +version = "5.10.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastjsonschema" }, + { name = "jsonschema" }, + { name = "jupyter-core" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749, upload-time = "2024-04-04T11:20:37.371Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454, upload-time = "2024-04-04T11:20:34.895Z" }, +] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, +] + [[package]] name = "numpy" version = "2.4.2" @@ -670,6 +1080,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/68/b0/34937815889fa982613775e4b97fddd13250f11012d769949c5465af2150/pandas-3.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:108dd1790337a494aa80e38def654ca3f0968cf4f362c85f44c15e471667102d", size = 9452085, upload-time = "2026-02-17T22:20:14.331Z" }, ] +[[package]] +name = "pandocfilters" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/70/6f/3dd4940bbe001c06a65f88e36bad298bc7a0de5036115639926b0c5c0458/pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e", size = 8454, upload-time = "2024-01-18T20:08:13.726Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc", size = 8663, upload-time = "2024-01-18T20:08:11.28Z" }, +] + +[[package]] +name = "parso" +version = "0.8.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/76/a1e769043c0c0c9fe391b702539d594731a4362334cdf4dc25d0c09761e7/parso-0.8.6.tar.gz", hash = "sha256:2b9a0332696df97d454fa67b81618fd69c35a7b90327cbe6ba5c92d2c68a7bfd", size = 401621, upload-time = "2026-02-09T15:45:24.425Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl", hash = "sha256:2c549f800b70a5c4952197248825584cb00f033b29c692671d3bf08bf380baff", size = 106894, upload-time = "2026-02-09T15:45:21.391Z" }, +] + [[package]] name = "pathspec" version = "1.0.4" @@ -679,6 +1107,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, ] +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, +] + [[package]] name = "platformdirs" version = "4.9.2" @@ -697,6 +1137,64 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, +] + +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, +] + [[package]] name = "pycparser" version = "3.0" @@ -888,6 +1386,63 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, ] +[[package]] +name = "pyzmq" +version = "27.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "implementation_name == 'pypy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" }, + { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" }, + { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" }, + { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" }, + { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, + { url = "https://files.pythonhosted.org/packages/60/cb/84a13459c51da6cec1b7b1dc1a47e6db6da50b77ad7fd9c145842750a011/pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5", size = 1122436, upload-time = "2025-09-08T23:08:20.801Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b6/94414759a69a26c3dd674570a81813c46a078767d931a6c70ad29fc585cb/pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6", size = 1156301, upload-time = "2025-09-08T23:08:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ad/15906493fd40c316377fd8a8f6b1f93104f97a752667763c9b9c1b71d42d/pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7", size = 1341197, upload-time = "2025-09-08T23:08:24.286Z" }, + { url = "https://files.pythonhosted.org/packages/14/1d/d343f3ce13db53a54cb8946594e567410b2125394dafcc0268d8dda027e0/pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05", size = 897275, upload-time = "2025-09-08T23:08:26.063Z" }, + { url = "https://files.pythonhosted.org/packages/69/2d/d83dd6d7ca929a2fc67d2c3005415cdf322af7751d773524809f9e585129/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9", size = 660469, upload-time = "2025-09-08T23:08:27.623Z" }, + { url = "https://files.pythonhosted.org/packages/3e/cd/9822a7af117f4bc0f1952dbe9ef8358eb50a24928efd5edf54210b850259/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128", size = 847961, upload-time = "2025-09-08T23:08:29.672Z" }, + { url = "https://files.pythonhosted.org/packages/9a/12/f003e824a19ed73be15542f172fd0ec4ad0b60cf37436652c93b9df7c585/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39", size = 1650282, upload-time = "2025-09-08T23:08:31.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4a/e82d788ed58e9a23995cee70dbc20c9aded3d13a92d30d57ec2291f1e8a3/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97", size = 2024468, upload-time = "2025-09-08T23:08:33.543Z" }, + { url = "https://files.pythonhosted.org/packages/d9/94/2da0a60841f757481e402b34bf4c8bf57fa54a5466b965de791b1e6f747d/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db", size = 1885394, upload-time = "2025-09-08T23:08:35.51Z" }, + { url = "https://files.pythonhosted.org/packages/4f/6f/55c10e2e49ad52d080dc24e37adb215e5b0d64990b57598abc2e3f01725b/pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c", size = 574964, upload-time = "2025-09-08T23:08:37.178Z" }, + { url = "https://files.pythonhosted.org/packages/87/4d/2534970ba63dd7c522d8ca80fb92777f362c0f321900667c615e2067cb29/pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2", size = 641029, upload-time = "2025-09-08T23:08:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/f6/fa/f8aea7a28b0641f31d40dea42d7ef003fded31e184ef47db696bc74cd610/pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e", size = 561541, upload-time = "2025-09-08T23:08:42.668Z" }, + { url = "https://files.pythonhosted.org/packages/87/45/19efbb3000956e82d0331bafca5d9ac19ea2857722fa2caacefb6042f39d/pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a", size = 1341197, upload-time = "2025-09-08T23:08:44.973Z" }, + { url = "https://files.pythonhosted.org/packages/48/43/d72ccdbf0d73d1343936296665826350cb1e825f92f2db9db3e61c2162a2/pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea", size = 897175, upload-time = "2025-09-08T23:08:46.601Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2e/a483f73a10b65a9ef0161e817321d39a770b2acf8bcf3004a28d90d14a94/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96", size = 660427, upload-time = "2025-09-08T23:08:48.187Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d2/5f36552c2d3e5685abe60dfa56f91169f7a2d99bbaf67c5271022ab40863/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d", size = 847929, upload-time = "2025-09-08T23:08:49.76Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2a/404b331f2b7bf3198e9945f75c4c521f0c6a3a23b51f7a4a401b94a13833/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146", size = 1650193, upload-time = "2025-09-08T23:08:51.7Z" }, + { url = "https://files.pythonhosted.org/packages/1c/0b/f4107e33f62a5acf60e3ded67ed33d79b4ce18de432625ce2fc5093d6388/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd", size = 2024388, upload-time = "2025-09-08T23:08:53.393Z" }, + { url = "https://files.pythonhosted.org/packages/0d/01/add31fe76512642fd6e40e3a3bd21f4b47e242c8ba33efb6809e37076d9b/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a", size = 1885316, upload-time = "2025-09-08T23:08:55.702Z" }, + { url = "https://files.pythonhosted.org/packages/c4/59/a5f38970f9bf07cee96128de79590bb354917914a9be11272cfc7ff26af0/pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92", size = 587472, upload-time = "2025-09-08T23:08:58.18Z" }, + { url = "https://files.pythonhosted.org/packages/70/d8/78b1bad170f93fcf5e3536e70e8fadac55030002275c9a29e8f5719185de/pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0", size = 661401, upload-time = "2025-09-08T23:08:59.802Z" }, + { url = "https://files.pythonhosted.org/packages/81/d6/4bfbb40c9a0b42fc53c7cf442f6385db70b40f74a783130c5d0a5aa62228/pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7", size = 575170, upload-time = "2025-09-08T23:09:01.418Z" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + [[package]] name = "requests" version = "2.32.5" @@ -903,6 +1458,87 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] +[[package]] +name = "rpds-py" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, + { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, + { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, + { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, + { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, + { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, + { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, + { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, + { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, + { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, + { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, + { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, + { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, + { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, + { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, + { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, + { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, + { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, + { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, + { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, + { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, + { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, + { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, + { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, + { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, + { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, + { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, + { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, + { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, + { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, + { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, + { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, + { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, + { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, + { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, + { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, + { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, + { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, + { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, + { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, + { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, + { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, + { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, +] + [[package]] name = "six" version = "1.17.0" @@ -912,6 +1548,69 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] +[[package]] +name = "soupsieve" +version = "2.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, +] + +[[package]] +name = "tinycss2" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085, upload-time = "2024-10-24T14:58:29.895Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610, upload-time = "2024-10-24T14:58:28.029Z" }, +] + +[[package]] +name = "tornado" +version = "6.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/1d/0a336abf618272d53f62ebe274f712e213f5a03c0b2339575430b8362ef2/tornado-6.5.4.tar.gz", hash = "sha256:a22fa9047405d03260b483980635f0b041989d8bcc9a313f8fe18b411d84b1d7", size = 513632, upload-time = "2025-12-15T19:21:03.836Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d6241c1a16b1c9e4cc28148b1cda97dd1c6cb4fb7068ac1bedc610768dff0ba9", size = 443909, upload-time = "2025-12-15T19:20:48.382Z" }, + { url = "https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2d50f63dda1d2cac3ae1fa23d254e16b5e38153758470e9956cbc3d813d40843", size = 442163, upload-time = "2025-12-15T19:20:49.791Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b5/206f82d51e1bfa940ba366a8d2f83904b15942c45a78dd978b599870ab44/tornado-6.5.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1cf66105dc6acb5af613c054955b8137e34a03698aa53272dbda4afe252be17", size = 445746, upload-time = "2025-12-15T19:20:51.491Z" }, + { url = "https://files.pythonhosted.org/packages/8e/9d/1a3338e0bd30ada6ad4356c13a0a6c35fbc859063fa7eddb309183364ac1/tornado-6.5.4-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50ff0a58b0dc97939d29da29cd624da010e7f804746621c78d14b80238669335", size = 445083, upload-time = "2025-12-15T19:20:52.778Z" }, + { url = "https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fb5e04efa54cf0baabdd10061eb4148e0be137166146fff835745f59ab9f7f", size = 445315, upload-time = "2025-12-15T19:20:53.996Z" }, + { url = "https://files.pythonhosted.org/packages/27/07/2273972f69ca63dbc139694a3fc4684edec3ea3f9efabf77ed32483b875c/tornado-6.5.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9c86b1643b33a4cd415f8d0fe53045f913bf07b4a3ef646b735a6a86047dda84", size = 446003, upload-time = "2025-12-15T19:20:56.101Z" }, + { url = "https://files.pythonhosted.org/packages/d1/83/41c52e47502bf7260044413b6770d1a48dda2f0246f95ee1384a3cd9c44a/tornado-6.5.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:6eb82872335a53dd063a4f10917b3efd28270b56a33db69009606a0312660a6f", size = 445412, upload-time = "2025-12-15T19:20:57.398Z" }, + { url = "https://files.pythonhosted.org/packages/10/c7/bc96917f06cbee182d44735d4ecde9c432e25b84f4c2086143013e7b9e52/tornado-6.5.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6076d5dda368c9328ff41ab5d9dd3608e695e8225d1cd0fd1e006f05da3635a8", size = 445392, upload-time = "2025-12-15T19:20:58.692Z" }, + { url = "https://files.pythonhosted.org/packages/0c/1a/d7592328d037d36f2d2462f4bc1fbb383eec9278bc786c1b111cbbd44cfa/tornado-6.5.4-cp39-abi3-win32.whl", hash = "sha256:1768110f2411d5cd281bac0a090f707223ce77fd110424361092859e089b38d1", size = 446481, upload-time = "2025-12-15T19:21:00.008Z" }, + { url = "https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl", hash = "sha256:fa07d31e0cd85c60713f2b995da613588aa03e1303d75705dca6af8babc18ddc", size = 446886, upload-time = "2025-12-15T19:21:01.287Z" }, + { url = "https://files.pythonhosted.org/packages/50/49/8dc3fd90902f70084bd2cd059d576ddb4f8bb44c2c7c0e33a11422acb17e/tornado-6.5.4-cp39-abi3-win_arm64.whl", hash = "sha256:053e6e16701eb6cbe641f308f4c1a9541f91b6261991160391bfc342e8a551a1", size = 445910, upload-time = "2025-12-15T19:21:02.571Z" }, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0" @@ -962,3 +1661,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, ] + +[[package]] +name = "wcwidth" +version = "0.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/a2/8e3becb46433538a38726c948d3399905a4c7cabd0df578ede5dc51f0ec2/wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159", size = 159684, upload-time = "2026-02-06T19:19:40.919Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189, upload-time = "2026-02-06T19:19:39.646Z" }, +] + +[[package]] +name = "webencodings" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721, upload-time = "2017-04-05T20:21:34.189Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" }, +] From ffc12bf606a54df640b9a377c2d99b3e435b513b Mon Sep 17 00:00:00 2001 From: scott-yj-yang Date: Fri, 27 Feb 2026 16:29:01 -0800 Subject: [PATCH 18/24] test: add shared conftest.py with mock fixtures for Canvas, GitHub, and credentials Co-Authored-By: Claude Opus 4.6 --- tests/conftest.py | 147 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 tests/conftest.py diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..30e5932 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,147 @@ +"""Shared test fixtures for CanvasGroupy test suite.""" + +import json +import pytest +from unittest.mock import MagicMock, patch + + +# --------------------------------------------------------------------------- +# Credential fixtures +# --------------------------------------------------------------------------- + +@pytest.fixture +def credentials(tmp_path): + """Create a temporary credentials.json and return its path.""" + creds = {"Canvas Token": "fake-canvas-token", "GitHub Token": "fake-github-token"} + fp = tmp_path / "credentials.json" + fp.write_text(json.dumps(creds)) + return str(fp) + + +@pytest.fixture +def bad_credentials(tmp_path): + """Create a credentials.json missing required keys.""" + fp = tmp_path / "credentials.json" + fp.write_text(json.dumps({"wrong_key": "value"})) + return str(fp) + + +# --------------------------------------------------------------------------- +# Canvas mock fixtures +# --------------------------------------------------------------------------- + +def _make_mock_user(email_prefix, canvas_id, short_name=None): + """Helper to create a mock Canvas user object.""" + user = MagicMock() + user.email = f"{email_prefix}@ucsd.edu" + user.id = canvas_id + user.short_name = short_name or email_prefix.title() + return user + + +@pytest.fixture +def mock_canvas_users(): + """Three mock students.""" + return [ + _make_mock_user("alice", 101, "Alice Smith"), + _make_mock_user("bob", 102, "Bob Jones"), + _make_mock_user("carol", 103, "Carol Lee"), + ] + + +@pytest.fixture +def mock_course(mock_canvas_users): + """Mock Canvas course with students pre-loaded.""" + course = MagicMock() + course.name = "Test Course" + course.id = 99999 + course.get_users.return_value = mock_canvas_users + return course + + +@pytest.fixture +def mock_group_category(): + """Mock Canvas group category with one group.""" + cat = MagicMock() + cat.name = "Project Groups" + + group = MagicMock() + group.name = "Team_Alpha" + member = MagicMock() + member.login_id = "alice" + group.get_users.return_value = [member] + + cat.get_groups.return_value = [group] + return cat + + +@pytest.fixture +def mock_canvas_api(mock_course, mock_group_category): + """Patch canvasapi.Canvas and return (MockCanvas, canvas_instance, mock_course).""" + with patch("CanvasGroupy.canvas.Canvas") as MockCanvas: + canvas_instance = MagicMock() + MockCanvas.return_value = canvas_instance + canvas_instance.get_activity_stream_summary.return_value = {} + canvas_instance.get_course.return_value = mock_course + mock_course.get_group_categories.return_value = [mock_group_category] + yield MockCanvas, canvas_instance, mock_course + + +# --------------------------------------------------------------------------- +# GitHub mock fixtures +# --------------------------------------------------------------------------- + +@pytest.fixture +def mock_github_api(): + """Patch PyGithub's Github class and return (MockGithub, github_instance, mock_org).""" + with patch("CanvasGroupy.github.Github") as MockGithub: + github_instance = MagicMock() + MockGithub.return_value = github_instance + + mock_user = MagicMock() + mock_user.login = "test-user" + mock_repo = MagicMock() + mock_repo.name = "test-repo" + mock_user.get_repos.return_value = [mock_repo] + github_instance.get_user.return_value = mock_user + + mock_org = MagicMock() + mock_org.login = "TestOrg" + github_instance.get_organization.return_value = mock_org + + yield MockGithub, github_instance, mock_org + + +# --------------------------------------------------------------------------- +# Grading fixtures +# --------------------------------------------------------------------------- + +@pytest.fixture +def mock_issue(): + """Mock GitHub issue with a parseable score.""" + issue = MagicMock() + issue.title = "Project Checkpoint Feedback" + issue.body = ( + "# Project Checkpoint Feedback\n\n" + "## Feedback\n\n" + "| Category | Score |\n" + "|----------|-------|\n\n" + "[comment]: # (Score = ...)\n" + "Score = 8.5\n" + ) + issue.url = "https://api.github.com/repos/TestOrg/team-alpha/issues/1" + return issue + + +@pytest.fixture +def mock_ungraded_issue(): + """Mock GitHub issue with an ungraded (Ellipsis) score.""" + issue = MagicMock() + issue.title = "Project Checkpoint Feedback" + issue.body = ( + "# Project Checkpoint Feedback\n\n" + "[comment]: # (Score = ...)\n" + "Score = ...\n" + ) + issue.url = "https://api.github.com/repos/TestOrg/team-alpha/issues/1" + return issue From 1e1bd2b0addc464f4844df5f47e6036fb951daf8 Mon Sep 17 00:00:00 2001 From: scott-yj-yang Date: Fri, 27 Feb 2026 16:33:53 -0800 Subject: [PATCH 19/24] test: add comprehensive CanvasGroup tests for auth, groups, grading, conversations Co-Authored-By: Claude Opus 4.6 --- tests/canvas_test.py | 15 --- tests/test_canvas.py | 273 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 273 insertions(+), 15 deletions(-) delete mode 100644 tests/canvas_test.py create mode 100644 tests/test_canvas.py diff --git a/tests/canvas_test.py b/tests/canvas_test.py deleted file mode 100644 index d8871ce..0000000 --- a/tests/canvas_test.py +++ /dev/null @@ -1,15 +0,0 @@ -from CanvasGroupy import * -import pytest -import requests -import requests_mock - -credentials_fp = "../../credentials.json" - - - -def test_auth(): - ... - - - - diff --git a/tests/test_canvas.py b/tests/test_canvas.py new file mode 100644 index 0000000..c886f11 --- /dev/null +++ b/tests/test_canvas.py @@ -0,0 +1,273 @@ +"""Tests for CanvasGroupy.canvas — CanvasGroup class.""" + +import json +import pytest +from unittest.mock import MagicMock, patch, call +from CanvasGroupy.canvas import CanvasGroup + + +class TestAuthCanvas: + def test_auth_loads_token_and_creates_canvas(self, credentials, mock_canvas_api): + MockCanvas, canvas_instance, _ = mock_canvas_api + cg = CanvasGroup(verbosity=0) + cg.auth_canvas(credentials) + + MockCanvas.assert_called_once_with("https://canvas.ucsd.edu", "fake-canvas-token") + canvas_instance.get_activity_stream_summary.assert_called_once() + assert cg.API_KEY == "fake-canvas-token" + + def test_auth_with_missing_file_raises(self): + cg = CanvasGroup(verbosity=0) + with pytest.raises(FileNotFoundError): + cg.auth_canvas("/nonexistent/path.json") + + def test_auth_with_bad_keys_raises(self, bad_credentials): + cg = CanvasGroup(verbosity=0) + with patch("CanvasGroupy.canvas.Canvas"): + with pytest.raises(KeyError): + cg.auth_canvas(bad_credentials) + + def test_custom_api_url(self, credentials): + with patch("CanvasGroupy.canvas.Canvas") as MockCanvas: + MockCanvas.return_value = MagicMock() + cg = CanvasGroup(API_URL="https://custom.canvas.edu", verbosity=0) + cg.auth_canvas(credentials) + MockCanvas.assert_called_once_with("https://custom.canvas.edu", "fake-canvas-token") + + +class TestSetCourse: + def test_set_course_fetches_students(self, credentials, mock_canvas_api): + _, canvas_instance, mock_course = mock_canvas_api + cg = CanvasGroup(verbosity=0) + cg.auth_canvas(credentials) + cg.set_course(99999) + + canvas_instance.get_course.assert_called_once_with(99999) + mock_course.get_users.assert_called_once_with(enrollment_type=["student"]) + assert cg.email_to_canvas_id["alice"] == 101 + assert cg.canvas_id_to_email[101] == "alice" + assert cg.email_to_name["alice"] == "Alice Smith" + assert len(cg.email_to_canvas_id) == 3 + + def test_set_course_skips_users_without_email(self, credentials): + with patch("CanvasGroupy.canvas.Canvas") as MockCanvas: + canvas_instance = MagicMock() + MockCanvas.return_value = canvas_instance + canvas_instance.get_activity_stream_summary.return_value = {} + + # One user has no email attribute + good_user = MagicMock() + good_user.email = "alice@ucsd.edu" + good_user.id = 101 + good_user.short_name = "Alice" + + bad_user = MagicMock(spec=[]) # no email attribute + bad_user.short_name = "Ghost" + + mock_course = MagicMock() + mock_course.get_users.return_value = [good_user, bad_user] + canvas_instance.get_course.return_value = mock_course + + cg = CanvasGroup(verbosity=0) + cg.auth_canvas(credentials) + cg.set_course(99999) + assert len(cg.email_to_canvas_id) == 1 + + +class TestGetEmailByName: + def test_finds_by_first_name(self, credentials, mock_canvas_api): + _, _, _ = mock_canvas_api + cg = CanvasGroup(verbosity=0) + cg.auth_canvas(credentials) + cg.set_course(99999) + assert cg.get_email_by_name("alice") == "alice" + + def test_case_insensitive(self, credentials, mock_canvas_api): + _, _, _ = mock_canvas_api + cg = CanvasGroup(verbosity=0) + cg.auth_canvas(credentials) + cg.set_course(99999) + assert cg.get_email_by_name("ALICE") == "alice" + + def test_raises_on_not_found(self, credentials, mock_canvas_api): + _, _, _ = mock_canvas_api + cg = CanvasGroup(verbosity=0) + cg.auth_canvas(credentials) + cg.set_course(99999) + with pytest.raises(ValueError, match="Not Found"): + cg.get_email_by_name("nobody") + + +class TestGroupOperations: + def _setup_cg(self, credentials, mock_canvas_api): + """Helper: return a fully-initialized CanvasGroup.""" + _, _, _ = mock_canvas_api + cg = CanvasGroup(verbosity=0) + cg.auth_canvas(credentials) + cg.set_course(99999) + return cg + + def test_get_group_categories(self, credentials, mock_canvas_api): + cg = self._setup_cg(credentials, mock_canvas_api) + cats = cg.get_group_categories() + assert "Project Groups" in cats + + def test_set_group_category(self, credentials, mock_canvas_api): + cg = self._setup_cg(credentials, mock_canvas_api) + result = cg.set_group_category("Project Groups") + assert result.name == "Project Groups" + assert "Team_Alpha" in cg.group_to_emails + + def test_set_group_category_raises_on_missing(self, credentials, mock_canvas_api): + cg = self._setup_cg(credentials, mock_canvas_api) + with pytest.raises(KeyError): + cg.set_group_category("Nonexistent Category") + + def test_get_groups_after_set(self, credentials, mock_canvas_api): + cg = self._setup_cg(credentials, mock_canvas_api) + cg.set_group_category("Project Groups") + groups = cg.get_groups() + assert isinstance(groups, dict) + assert "Team_Alpha" in groups + + def test_get_groups_raises_without_category(self, credentials, mock_canvas_api): + cg = self._setup_cg(credentials, mock_canvas_api) + with pytest.raises(ValueError): + cg.get_groups() + + def test_create_group_category(self, credentials, mock_canvas_api): + _, _, mock_course = mock_canvas_api + new_cat = MagicMock() + new_cat.name = "New Category" + mock_course.create_group_category.return_value = new_cat + + cg = self._setup_cg(credentials, mock_canvas_api) + result = cg.create_group_category({"name": "New Category"}) + mock_course.create_group_category.assert_called_once_with(name="New Category") + assert result.name == "New Category" + + def test_create_group(self, credentials, mock_canvas_api, mock_group_category): + cg = self._setup_cg(credentials, mock_canvas_api) + cg.set_group_category("Project Groups") + + new_group = MagicMock() + new_group.name = "Team_Beta" + mock_group_category.create_group.return_value = new_group + + result = cg.create_group({"name": "Team_Beta"}) + assert result.name == "Team_Beta" + + def test_create_group_raises_without_category(self, credentials, mock_canvas_api): + cg = self._setup_cg(credentials, mock_canvas_api) + with pytest.raises(ValueError): + cg.create_group({"name": "Team_Beta"}) + + def test_join_canvas_group(self, credentials, mock_canvas_api): + cg = self._setup_cg(credentials, mock_canvas_api) + group = MagicMock() + unsuccessful = cg.join_canvas_group(group, ["alice", "bob"]) + assert group.create_membership.call_count == 2 + assert unsuccessful == [] + + def test_join_canvas_group_returns_failures(self, credentials, mock_canvas_api): + cg = self._setup_cg(credentials, mock_canvas_api) + group = MagicMock() + unsuccessful = cg.join_canvas_group(group, ["alice", "nonexistent_student"]) + assert "nonexistent_student" in unsuccessful + + def test_assign_canvas_group_creates_and_joins(self, credentials, mock_canvas_api, mock_group_category): + cg = self._setup_cg(credentials, mock_canvas_api) + + new_group = MagicMock() + mock_group_category.create_group.return_value = new_group + + result_group, unsuccessful = cg.assign_canvas_group( + group_name="Team_Beta", + group_members=["alice", "bob"], + in_group_category="Project Groups", + ) + assert result_group == new_group + assert new_group.create_membership.call_count == 2 + + +class TestGradePosting: + def _setup_cg_with_assignment(self, credentials, mock_canvas_api): + _, _, mock_course = mock_canvas_api + cg = CanvasGroup(verbosity=0) + cg.auth_canvas(credentials) + cg.set_course(99999) + + mock_assignment = MagicMock() + mock_assignment.name = "Homework 1" + mock_course.get_assignment.return_value = mock_assignment + + cg.link_assignment(42) + return cg, mock_assignment + + def test_link_assignment(self, credentials, mock_canvas_api): + _, _, mock_course = mock_canvas_api + mock_assignment = MagicMock() + mock_assignment.name = "Homework 1" + mock_course.get_assignment.return_value = mock_assignment + + cg = CanvasGroup(verbosity=0) + cg.auth_canvas(credentials) + cg.set_course(99999) + result = cg.link_assignment(42) + + mock_course.get_assignment.assert_called_once_with(42) + assert result.name == "Homework 1" + + def test_post_grade(self, credentials, mock_canvas_api): + cg, mock_assignment = self._setup_cg_with_assignment(credentials, mock_canvas_api) + + mock_submission = MagicMock() + mock_submission.score = 0 # different from new grade + mock_assignment.get_submission.return_value = mock_submission + + cg.post_grade(student_id=101, grade=95.0, text_comment="Great work") + mock_submission.edit.assert_called_once_with( + submission={"posted_grade": 95.0}, + comment={"text_comment": "Great work"}, + ) + + def test_post_grade_skips_same_score(self, credentials, mock_canvas_api): + cg, mock_assignment = self._setup_cg_with_assignment(credentials, mock_canvas_api) + + mock_submission = MagicMock() + mock_submission.score = 95.0 # same as new grade + mock_assignment.get_submission.return_value = mock_submission + + result = cg.post_grade(student_id=101, grade=95.0) + mock_submission.edit.assert_not_called() + assert result is None + + def test_post_grade_force_overrides_skip(self, credentials, mock_canvas_api): + cg, mock_assignment = self._setup_cg_with_assignment(credentials, mock_canvas_api) + + mock_submission = MagicMock() + mock_submission.score = 95.0 # same score, but force=True + mock_assignment.get_submission.return_value = mock_submission + + cg.post_grade(student_id=101, grade=95.0, force=True) + mock_submission.edit.assert_called_once() + + +class TestConversation: + def test_create_conversation(self, credentials, mock_canvas_api): + _, canvas_instance, _ = mock_canvas_api + cg = CanvasGroup(verbosity=0) + cg.auth_canvas(credentials) + cg.set_course(99999) + + cg.create_conversation( + recipients=101, + subject="Test Subject", + body="Test Body", + ) + canvas_instance.create_conversation.assert_called_once_with( + [101], + body="Test Body", + subject="Test Subject", + context_code="course_99999", + ) From ca75db2765bd270690d1f75c78ef89f4fd24e305 Mon Sep 17 00:00:00 2001 From: scott-yj-yang Date: Fri, 27 Feb 2026 16:34:52 -0800 Subject: [PATCH 20/24] test: add comprehensive GitHubGroup tests for auth, repos, teams, issues Co-Authored-By: Claude Opus 4.6 --- tests/test_github.py | 333 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 333 insertions(+) create mode 100644 tests/test_github.py diff --git a/tests/test_github.py b/tests/test_github.py new file mode 100644 index 0000000..7231feb --- /dev/null +++ b/tests/test_github.py @@ -0,0 +1,333 @@ +"""Tests for CanvasGroupy.github — GitHubGroup class.""" + +import json +import os +import pytest +from unittest.mock import MagicMock, patch, call +from CanvasGroupy.github import GitHubGroup + + +class TestAuthGitHub: + def test_auth_loads_token(self, credentials, mock_github_api): + MockGithub, github_instance, _ = mock_github_api + ghg = GitHubGroup(verbosity=0) + ghg.auth_github(credentials) + + MockGithub.assert_called_once_with("fake-github-token") + assert ghg.github is not None + + def test_auth_missing_file_raises(self): + ghg = GitHubGroup(verbosity=0) + with pytest.raises(FileNotFoundError): + ghg.auth_github("/nonexistent/path.json") + + def test_set_org(self, credentials, mock_github_api): + _, github_instance, mock_org = mock_github_api + ghg = GitHubGroup(verbosity=0) + ghg.auth_github(credentials) + ghg.set_org("TestOrg") + + github_instance.get_organization.assert_called_once_with("TestOrg") + assert ghg.org.login == "TestOrg" + + +class TestRepoOperations: + def _setup_ghg(self, credentials, mock_github_api): + _, _, mock_org = mock_github_api + ghg = GitHubGroup(verbosity=0) + ghg.auth_github(credentials) + ghg.set_org("TestOrg") + return ghg, mock_org + + def test_create_blank_repo(self, credentials, mock_github_api): + ghg, mock_org = self._setup_ghg(credentials, mock_github_api) + mock_repo = MagicMock() + mock_repo.name = "new-repo" + mock_org.create_repo.return_value = mock_repo + + result = ghg.create_repo("new-repo", private=True, description="test") + mock_org.create_repo.assert_called_once_with( + name="new-repo", private=True, description="test" + ) + assert result.name == "new-repo" + + def test_create_repo_from_template(self, credentials, mock_github_api): + ghg, mock_org = self._setup_ghg(credentials, mock_github_api) + _, github_instance, _ = mock_github_api + + template_repo = MagicMock() + github_instance.get_repo.return_value = template_repo + + new_repo = MagicMock() + mock_org.create_repo_from_template.return_value = new_repo + + result = ghg.create_repo("new-repo", repo_template="owner/template") + mock_org.create_repo_from_template.assert_called_once() + assert result == new_repo + + def test_get_repo(self, credentials, mock_github_api): + ghg, _ = self._setup_ghg(credentials, mock_github_api) + _, github_instance, _ = mock_github_api + + mock_repo = MagicMock() + github_instance.get_repo.return_value = mock_repo + + result = ghg.get_repo("owner/repo") + github_instance.get_repo.assert_called_with("owner/repo") + assert result == mock_repo + + def test_get_repo_falls_back_to_org(self, credentials, mock_github_api): + ghg, mock_org = self._setup_ghg(credentials, mock_github_api) + _, github_instance, _ = mock_github_api + + github_instance.get_repo.side_effect = Exception("Not found") + org_repo = MagicMock() + mock_org.get_repo.return_value = org_repo + + result = ghg.get_repo("some-repo") + assert result == org_repo + + +class TestCollaborators: + def test_add_collaborator(self, credentials, mock_github_api): + ghg = GitHubGroup(verbosity=0) + ghg.auth_github(credentials) + + repo = MagicMock() + ghg.add_collaborator(repo, "new-user", "write") + repo.add_to_collaborators.assert_called_once_with("new-user", "write") + + def test_remove_collaborator(self, credentials, mock_github_api): + ghg = GitHubGroup(verbosity=0) + ghg.auth_github(credentials) + + repo = MagicMock() + ghg.remove_collaborator(repo, "old-user") + repo.remove_from_collaborators.assert_called_once_with("old-user") + + def test_add_collaborator_handles_failure(self, credentials, mock_github_api): + ghg = GitHubGroup(verbosity=0) + ghg.auth_github(credentials) + + repo = MagicMock() + repo.add_to_collaborators.side_effect = Exception("Permission denied") + # Should not raise — prints warning and continues + ghg.add_collaborator(repo, "bad-user", "write") + + +class TestTeamOperations: + def test_get_team(self, credentials, mock_github_api): + ghg = GitHubGroup(verbosity=0) + ghg.auth_github(credentials) + ghg.set_org("TestOrg") + + _, _, mock_org = mock_github_api + mock_team = MagicMock() + mock_team.name = "instructors" + mock_org.get_team_by_slug.return_value = mock_team + + result = ghg.get_team("instructors") + assert result.name == "instructors" + + def test_get_team_raises_without_org(self, credentials, mock_github_api): + ghg = GitHubGroup(verbosity=0) + ghg.auth_github(credentials) + # org not set + with pytest.raises(ValueError): + ghg.get_team("instructors") + + def test_add_team_to_repo(self, credentials, mock_github_api): + ghg = GitHubGroup(verbosity=0) + ghg.auth_github(credentials) + ghg.set_org("TestOrg") + + _, _, mock_org = mock_github_api + mock_team = MagicMock() + mock_org.get_team_by_slug.return_value = mock_team + + repo = MagicMock() + ghg.add_team(repo, "instructors", "admin") + mock_team.add_to_repos.assert_called_once_with(repo) + mock_team.update_team_repository.assert_called_once_with(repo, "admin") + + +class TestFileOperations: + def test_rename_files(self, credentials, mock_github_api): + ghg = GitHubGroup(verbosity=0) + ghg.auth_github(credentials) + + repo = MagicMock() + mock_file = MagicMock() + mock_file.decoded_content = b"file content" + mock_file.sha = "abc123" + repo.get_contents.return_value = mock_file + + ghg.rename_files(repo, "old_name.txt", "new_name.txt") + repo.get_contents.assert_called_once_with("old_name.txt") + repo.create_file.assert_called_once_with( + "new_name.txt", "rename files", b"file content" + ) + repo.delete_file.assert_called_once_with( + "old_name.txt", "delete old files", "abc123" + ) + + def test_create_feedback_dir(self, credentials, mock_github_api, tmp_path): + ghg = GitHubGroup(verbosity=0) + ghg.auth_github(credentials) + + # Create template files + template_dir = tmp_path / "templates" + template_dir.mkdir() + (template_dir / "feedback.md").write_text("# Feedback\nScore = ...") + (template_dir / "rubric.md").write_text("# Rubric\n...") + + repo = MagicMock() + repo.name = "team-alpha" + + dest = str(tmp_path / "feedback") + ghg.create_feedback_dir(repo, str(template_dir), destination=dest) + + assert os.path.exists(f"{dest}/team-alpha/feedback.md") + assert os.path.exists(f"{dest}/team-alpha/rubric.md") + + +class TestIssues: + def test_create_issue(self, credentials, mock_github_api): + ghg = GitHubGroup(verbosity=0) + ghg.auth_github(credentials) + + repo = MagicMock() + mock_issue = MagicMock() + repo.create_issue.return_value = mock_issue + + result = ghg.create_issue(repo, "Bug Report", "There is a bug") + repo.create_issue.assert_called_once_with(title="Bug Report", body="There is a bug") + assert result == mock_issue + + def test_create_issue_from_md(self, credentials, mock_github_api, tmp_path): + ghg = GitHubGroup(verbosity=0) + ghg.auth_github(credentials) + + md_file = tmp_path / "feedback.md" + md_file.write_text("# Checkpoint Feedback\n\nGreat work on the project.") + + repo = MagicMock() + mock_issue = MagicMock() + repo.create_issue.return_value = mock_issue + + result = ghg.create_issue_from_md(repo, str(md_file)) + # Title should be first line without "# " + repo.create_issue.assert_called_once() + args = repo.create_issue.call_args + assert args[1]["title"] == "Checkpoint Feedback" + + def test_release_feedback(self, credentials, mock_github_api, tmp_path): + ghg = GitHubGroup(verbosity=0) + ghg.auth_github(credentials) + ghg.set_org("TestOrg") + + _, _, mock_org = mock_github_api + + # Create feedback structure + feedback_dir = tmp_path / "feedback" + (feedback_dir / "team-alpha").mkdir(parents=True) + (feedback_dir / "team-alpha" / "checkpoint.md").write_text( + "# Checkpoint Feedback\nScore = 8.5" + ) + + mock_repo = MagicMock() + mock_org.get_repo.return_value = mock_repo + + ghg.release_feedback("checkpoint.md", feedback_dir=str(feedback_dir)) + mock_org.get_repo.assert_called_with("team-alpha") + mock_repo.create_issue.assert_called_once() + + +class TestResendInvitations: + def test_resend_invitations(self, credentials, mock_github_api): + ghg = GitHubGroup(verbosity=0) + ghg.auth_github(credentials) + + repo = MagicMock() + invite = MagicMock() + invite.id = 1 + invite.invitee.login = "pending-user" + invite.permissions = "write" + repo.get_pending_invitations.return_value = [invite] + + result = ghg.resend_invitations(repo) + repo.remove_invitation.assert_called_once_with(1) + repo.add_to_collaborators.assert_called_once() + assert len(result) == 1 + + +class TestCreateGroupRepo: + def _setup_ghg(self, credentials, mock_github_api): + _, _, mock_org = mock_github_api + ghg = GitHubGroup(verbosity=0) + ghg.auth_github(credentials) + ghg.set_org("TestOrg") + return ghg, mock_org + + @patch("time.sleep", return_value=None) + def test_creates_repo_and_adds_collaborators(self, mock_sleep, credentials, mock_github_api): + ghg, mock_org = self._setup_ghg(credentials, mock_github_api) + + mock_repo = MagicMock() + mock_repo.name = "team-alpha" + mock_org.create_repo.return_value = mock_repo + + result = ghg.create_group_repo( + repo_name="team-alpha", + collaborators=["user1", "user2"], + permission="write", + private=True, + ) + assert result == mock_repo + assert mock_repo.add_to_collaborators.call_count == 2 + + @patch("time.sleep", return_value=None) + def test_creates_from_template_with_renames(self, mock_sleep, credentials, mock_github_api): + ghg, mock_org = self._setup_ghg(credentials, mock_github_api) + _, github_instance, _ = mock_github_api + + template = MagicMock() + github_instance.get_repo.return_value = template + + mock_repo = MagicMock() + mock_repo.name = "team-alpha" + mock_file = MagicMock() + mock_file.decoded_content = b"content" + mock_file.sha = "sha123" + mock_repo.get_contents.return_value = mock_file + mock_org.create_repo_from_template.return_value = mock_repo + + result = ghg.create_group_repo( + repo_name="team-alpha", + collaborators=["user1"], + permission="write", + repo_template="owner/template", + rename_files={"old.txt": "new.txt"}, + ) + mock_repo.get_contents.assert_called_with("old.txt") + mock_repo.create_file.assert_called_once() + + @patch("time.sleep", return_value=None) + def test_adds_team_when_specified(self, mock_sleep, credentials, mock_github_api): + ghg, mock_org = self._setup_ghg(credentials, mock_github_api) + + mock_repo = MagicMock() + mock_org.create_repo.return_value = mock_repo + + mock_team = MagicMock() + mock_org.get_team_by_slug.return_value = mock_team + + ghg.create_group_repo( + repo_name="team-alpha", + collaborators=[], + permission="write", + team_slug="instructors", + team_permission="admin", + ) + mock_team.add_to_repos.assert_called_once_with(mock_repo) + mock_team.update_team_repository.assert_called_once_with(mock_repo, "admin") From f7b132bdd77530360240c48dab0f1c3404744721 Mon Sep 17 00:00:00 2001 From: scott-yj-yang Date: Fri, 27 Feb 2026 16:35:30 -0800 Subject: [PATCH 21/24] test: add comprehensive Grading tests for score parsing, grading workflow Co-Authored-By: Claude Opus 4.6 --- tests/test_grading.py | 225 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 tests/test_grading.py diff --git a/tests/test_grading.py b/tests/test_grading.py new file mode 100644 index 0000000..152ca52 --- /dev/null +++ b/tests/test_grading.py @@ -0,0 +1,225 @@ +"""Tests for CanvasGroupy.grading — Grading class.""" + +import pytest +from unittest.mock import MagicMock, patch +from CanvasGroupy.grading import Grading + + +class TestFetchIssue: + def test_finds_issue_by_component(self, mock_issue): + repo = MagicMock() + repo.get_issues.return_value = [mock_issue] + + grading = Grading() + result = grading.fetch_issue(repo, "checkpoint") + assert result == mock_issue + + def test_case_insensitive_match(self, mock_issue): + repo = MagicMock() + repo.get_issues.return_value = [mock_issue] + + grading = Grading() + result = grading.fetch_issue(repo, "CHECKPOINT") + assert result == mock_issue + + def test_raises_when_not_found(self): + repo = MagicMock() + repo.get_issues.return_value = [] + + grading = Grading() + with pytest.raises(ValueError, match="did not found"): + grading.fetch_issue(repo, "nonexistent") + + +class TestParseScore: + def test_parses_numeric_score(self, mock_issue): + repo = MagicMock() + repo.get_issues.return_value = [mock_issue] + + grading = Grading() + score = grading.parse_score_from_issue(repo, "checkpoint") + assert score == 8.5 + + def test_ignores_comment_lines(self): + """Score lines with [comment] should be skipped.""" + issue = MagicMock() + issue.title = "Checkpoint Feedback" + issue.body = ( + "[comment]: # (Score = ...)\n" + "Score = 9.0\n" + ) + repo = MagicMock() + repo.get_issues.return_value = [issue] + + grading = Grading() + score = grading.parse_score_from_issue(repo, "checkpoint") + assert score == 9.0 + + def test_raises_on_missing_score(self): + issue = MagicMock() + issue.title = "Checkpoint Feedback" + issue.body = "No score here\nJust feedback" + issue.url = "https://example.com" + repo = MagicMock() + repo.get_issues.return_value = [issue] + + grading = Grading() + with pytest.raises(ValueError, match="Score Parse Error"): + grading.parse_score_from_issue(repo, "checkpoint") + + +class TestCheckGraded: + def test_graded_returns_true(self, mock_issue): + repo = MagicMock() + repo.get_issues.return_value = [mock_issue] + + grading = Grading() + assert grading.check_graded(repo, "checkpoint") is True + + def test_ungraded_returns_false(self, mock_ungraded_issue): + repo = MagicMock() + repo.get_issues.return_value = [mock_ungraded_issue] + + grading = Grading() + assert grading.check_graded(repo, "checkpoint") is False + + +class TestUpdateCanvasScore: + def test_posts_grade_to_all_members(self, mock_issue): + cg = MagicMock() + cg.group_category = MagicMock() + cg.group_to_emails = {"Team_Alpha": ["alice", "bob"]} + cg.email_to_canvas_id = {"alice": 101, "bob": 102} + + grading = Grading(cg=cg) + grading.update_canvas_score( + group_name="Team_Alpha", + assignment_id=42, + score=8.5, + issue=mock_issue, + post=True, + ) + cg.link_assignment.assert_called_once_with(42) + assert cg.post_grade.call_count == 2 + + def test_dry_run_does_not_post(self, capsys): + cg = MagicMock() + cg.group_category = MagicMock() + cg.group_to_emails = {"Team_Alpha": ["alice"]} + cg.email_to_canvas_id = {"alice": 101} + + grading = Grading(cg=cg) + grading.update_canvas_score( + group_name="Team_Alpha", + assignment_id=42, + score=8.5, + post=False, + ) + cg.post_grade.assert_not_called() + captured = capsys.readouterr() + assert "Post Disable" in captured.out + + def test_includes_issue_url_in_comment(self, mock_issue): + cg = MagicMock() + cg.group_category = MagicMock() + cg.group_to_emails = {"Team_Alpha": ["alice"]} + cg.email_to_canvas_id = {"alice": 101} + + grading = Grading(cg=cg) + grading.update_canvas_score( + group_name="Team_Alpha", + assignment_id=42, + score=8.5, + issue=mock_issue, + post=True, + ) + # Verify the comment includes the GitHub URL (not API URL) + call_args = cg.post_grade.call_args + assert "github.com" in call_args[1]["text_comment"] + + +class TestGradeProject: + def test_full_grading_workflow(self, mock_ungraded_issue): + cg = MagicMock() + cg.group_category = MagicMock() + cg.group_to_emails = {"team-alpha": ["alice"]} + cg.email_to_canvas_id = {"alice": 101} + + repo = MagicMock() + repo.name = "team-alpha" + repo.get_issues.return_value = [mock_ungraded_issue] + + grading = Grading(cg=cg) + grading.grade_project( + repo=repo, + component="checkpoint", + assignment_id=42, + post=True, + ) + cg.link_assignment.assert_called_once_with(42) + + def test_skips_already_graded(self, mock_issue): + """check_graded returns True -> grade_project returns early.""" + cg = MagicMock() + cg.group_category = MagicMock() + cg.group_to_emails = {"team-alpha": ["alice"]} + + repo = MagicMock() + repo.name = "team-alpha" + repo.get_issues.return_value = [mock_issue] + + grading = Grading(cg=cg) + # check_graded will return True since score is 8.5 (not Ellipsis) + grading.grade_project( + repo=repo, + component="checkpoint", + assignment_id=42, + post=True, + ) + # Should return early without posting + cg.link_assignment.assert_not_called() + + def test_uses_canvas_group_name_mapping(self, mock_ungraded_issue): + cg = MagicMock() + cg.group_category = MagicMock() + cg.group_to_emails = {"Canvas Team Alpha": ["alice"]} + cg.email_to_canvas_id = {"alice": 101} + + repo = MagicMock() + repo.name = "github-team-alpha" + repo.get_issues.return_value = [mock_ungraded_issue] + + grading = Grading(cg=cg) + grading.grade_project( + repo=repo, + component="checkpoint", + assignment_id=42, + canvas_group_name={"github-team-alpha": "Canvas Team Alpha"}, + post=True, + ) + cg.link_assignment.assert_called_once_with(42) + + def test_sets_group_category_if_provided(self, mock_ungraded_issue): + cg = MagicMock() + cg.group_category = None + cg.group_to_emails = {"team-alpha": ["alice"]} + cg.email_to_canvas_id = {"alice": 101} + + # Simulate set_group_category actually setting the attribute + def _set_gc(name): + cg.group_category = MagicMock(name=name) + cg.set_group_category.side_effect = _set_gc + + repo = MagicMock() + repo.name = "team-alpha" + repo.get_issues.return_value = [mock_ungraded_issue] + + grading = Grading(cg=cg) + grading.grade_project( + repo=repo, + component="checkpoint", + assignment_id=42, + canvas_group_category="Project Groups", + post=True, + ) + cg.set_group_category.assert_called_once_with("Project Groups") From ef7057b8fa6ecbd91874298d97e26415ac65d446 Mon Sep 17 00:00:00 2001 From: scott-yj-yang Date: Fri, 27 Feb 2026 16:48:28 -0800 Subject: [PATCH 22/24] fix: address 3 issues from code review 1. assign.py: add UserWarning when load_groups() detects numeric student_id values (likely Canvas IDs instead of email prefixes), and clarify docstring that student_id must be the SIS Login ID. 2. github.py: add missing `continue` in release_feedback() exception handler so a failed repo lookup skips to the next repo instead of using an undefined/stale variable. 3. pyproject.toml: add missing `requests` to dependencies (used by canvas.py fetch_username_from_quiz). Co-Authored-By: Claude Opus 4.6 --- CanvasGroupy/assign.py | 21 +++++++++++++++++++-- CanvasGroupy/github.py | 1 + pyproject.toml | 1 + tests/test_assign.py | 15 +++++++++++++++ tests/test_github.py | 28 ++++++++++++++++++++++++++++ 5 files changed, 64 insertions(+), 2 deletions(-) diff --git a/CanvasGroupy/assign.py b/CanvasGroupy/assign.py index 998a9a9..7f5ce95 100644 --- a/CanvasGroupy/assign.py +++ b/CanvasGroupy/assign.py @@ -33,13 +33,18 @@ def __init__( def load_groups(self, source) -> dict[str, list[str]]: """Load group assignments from a CSV file path or DataFrame. - The input must have columns ``group_name`` and ``student_id``. + The input must have columns ``group_name`` and ``student_id``, + where ``student_id`` is the student's email prefix (SIS Login ID, + the part before the ``@``), matching the keys used by Canvas + (``CanvasGroup.email_to_canvas_id``) and by + ``fetch_username_from_quiz``. Args: source: A file path (str) to a CSV or a pandas DataFrame. Returns: - Dictionary mapping group names to lists of student IDs. + Dictionary mapping group names to lists of student email + prefixes. Raises: TypeError: If source is not a str or DataFrame. @@ -57,6 +62,18 @@ def load_groups(self, source) -> dict[str, list[str]]: f"Missing required column: '{col}'. " f"Got columns: {list(source.columns)}" ) + # Warn early if student_id values look like numeric Canvas IDs + # rather than email prefixes (SIS Login IDs). + sample = source["student_id"].dropna().head(5) + if sample.apply(lambda v: str(v).isdigit()).all() and len(sample) > 0: + import warnings + warnings.warn( + "All sampled student_id values are numeric. " + "student_id should be the email prefix (SIS Login ID), " + "not the Canvas numeric user ID.", + UserWarning, + stacklevel=2, + ) self.groups = ( source.groupby("group_name")["student_id"] .apply(list) diff --git a/CanvasGroupy/github.py b/CanvasGroupy/github.py index 97251cb..62aa250 100644 --- a/CanvasGroupy/github.py +++ b/CanvasGroupy/github.py @@ -442,6 +442,7 @@ def release_feedback(self, repo = self.org.get_repo(repo_name) except Exception: print(f"Repo: {bcolors.WARNING}{repo_name} NOT FOUND!{bcolors.ENDC}") + continue self.create_issue_from_md(repo, os.path.join(feedback_dir, repo_name, md_filename)) def create_group_repo(self, diff --git a/pyproject.toml b/pyproject.toml index c141404..2a13da0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ dependencies = [ "numpy", "PyGithub", "canvasapi", + "requests", ] [project.optional-dependencies] diff --git a/tests/test_assign.py b/tests/test_assign.py index 17f1e4e..bafcf56 100644 --- a/tests/test_assign.py +++ b/tests/test_assign.py @@ -1,3 +1,4 @@ +import warnings import pytest import pandas as pd from unittest.mock import MagicMock @@ -74,6 +75,20 @@ def test_constructor_groups_parameter_auto_loads(self, mock_services): "Team2": ["carol"], } + def test_load_groups_warns_on_numeric_student_ids(self, mock_services): + ghg, cg = mock_services + ag = AssignGroup(ghg=ghg, cg=cg) + df = pd.DataFrame({ + "group_name": ["Group1", "Group1"], + "student_id": ["12345", "67890"], + }) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + ag.load_groups(df) + assert len(w) == 1 + assert "numeric" in str(w[0].message).lower() + assert "email prefix" in str(w[0].message).lower() + class TestCreateCanvasGroup: def test_calls_assign_canvas_group_for_each_group(self, loaded_ag): diff --git a/tests/test_github.py b/tests/test_github.py index 7231feb..029a2ab 100644 --- a/tests/test_github.py +++ b/tests/test_github.py @@ -242,6 +242,34 @@ def test_release_feedback(self, credentials, mock_github_api, tmp_path): mock_org.get_repo.assert_called_with("team-alpha") mock_repo.create_issue.assert_called_once() + def test_release_feedback_skips_missing_repos(self, credentials, mock_github_api, tmp_path): + ghg = GitHubGroup(verbosity=0) + ghg.auth_github(credentials) + ghg.set_org("TestOrg") + + _, _, mock_org = mock_github_api + + # Create feedback structure with two repos + feedback_dir = tmp_path / "feedback" + (feedback_dir / "missing-repo").mkdir(parents=True) + (feedback_dir / "missing-repo" / "checkpoint.md").write_text("# Feedback\n") + (feedback_dir / "good-repo").mkdir(parents=True) + (feedback_dir / "good-repo" / "checkpoint.md").write_text("# Feedback\n") + + good_repo = MagicMock() + good_repo.name = "good-repo" + + def get_repo_side_effect(name): + if name == "missing-repo": + raise Exception("Not found") + return good_repo + + mock_org.get_repo.side_effect = get_repo_side_effect + + # Should not raise; should skip missing-repo and still process good-repo + ghg.release_feedback("checkpoint.md", feedback_dir=str(feedback_dir)) + good_repo.create_issue.assert_called_once() + class TestResendInvitations: def test_resend_invitations(self, credentials, mock_github_api): From 678aea994761543f850793423bc70c1ade09a740 Mon Sep 17 00:00:00 2001 From: scott-yj-yang Date: Fri, 27 Feb 2026 16:54:01 -0800 Subject: [PATCH 23/24] black formatted --- CanvasGroupy/assign.py | 15 +-- CanvasGroupy/canvas.py | 288 ++++++++++++++++++++++------------------ CanvasGroupy/github.py | 283 ++++++++++++++++++++++----------------- CanvasGroupy/grading.py | 115 ++++++++-------- tests/conftest.py | 5 +- tests/test_assign.py | 44 +++--- tests/test_canvas.py | 24 +++- tests/test_github.py | 16 ++- tests/test_grading.py | 6 +- uv.lock | 2 + 10 files changed, 449 insertions(+), 349 deletions(-) diff --git a/CanvasGroupy/assign.py b/CanvasGroupy/assign.py index 7f5ce95..a8f848a 100644 --- a/CanvasGroupy/assign.py +++ b/CanvasGroupy/assign.py @@ -67,6 +67,7 @@ def load_groups(self, source) -> dict[str, list[str]]: sample = source["student_id"].dropna().head(5) if sample.apply(lambda v: str(v).isdigit()).all() and len(sample) > 0: import warnings + warnings.warn( "All sampled student_id values are numeric. " "student_id should be the email prefix (SIS Login ID), " @@ -74,11 +75,7 @@ def load_groups(self, source) -> dict[str, list[str]]: UserWarning, stacklevel=2, ) - self.groups = ( - source.groupby("group_name")["student_id"] - .apply(list) - .to_dict() - ) + self.groups = source.groupby("group_name")["student_id"].apply(list).to_dict() return self.groups def create_canvas_group( @@ -97,9 +94,7 @@ def create_canvas_group( ValueError: If no groups are loaded or no group category is set. """ if not self.groups: - raise ValueError( - "No groups loaded. Call load_groups() first." - ) + raise ValueError("No groups loaded. Call load_groups() first.") if self.cg.group_category is None and in_group_category == "": raise ValueError( "Specify in_group_category or set it on the CanvasGroup instance." @@ -131,9 +126,7 @@ def create_github_group( ``description``, ``team_slug``, ``team_permission``). """ if not self.groups: - raise ValueError( - "No groups loaded. Call load_groups() first." - ) + raise ValueError("No groups loaded. Call load_groups() first.") github_usernames = self.cg.fetch_username_from_quiz(username_quiz_id) repos = [] for group_name, members in self.groups.items(): diff --git a/CanvasGroupy/canvas.py b/CanvasGroupy/canvas.py index 6b3effc..a48514c 100644 --- a/CanvasGroupy/canvas.py +++ b/CanvasGroupy/canvas.py @@ -1,4 +1,4 @@ -__all__ = ['bcolors', 'CanvasGroup'] +__all__ = ["bcolors", "CanvasGroup"] from canvasapi import Canvas from github import Github @@ -11,18 +11,20 @@ import pandas as pd from io import StringIO + class bcolors: - HEADER = '\033[95m' - OKBLUE = '\033[94m' - OKCYAN = '\033[96m' - OKGREEN = '\033[92m' - WARNING = '\033[93m' - FAIL = '\033[91m' - ENDC = '\033[0m' - BOLD = '\033[1m' - UNDERLINE = '\033[4m' - -class CanvasGroup(): + HEADER = "\033[95m" + OKBLUE = "\033[94m" + OKCYAN = "\033[96m" + OKGREEN = "\033[92m" + WARNING = "\033[93m" + FAIL = "\033[91m" + ENDC = "\033[0m" + BOLD = "\033[1m" + UNDERLINE = "\033[4m" + + +class CanvasGroup: """Manage Canvas LMS group operations including roster, grading, and messaging. Provides methods to authenticate with the Canvas API, manage courses and @@ -39,13 +41,14 @@ class CanvasGroup(): verbosity: Controls output verbosity (0 = silent, 1 = print all). """ - def __init__(self, - credentials_fp = "", # credential file path. See docs/getting-started/authentication.md for the template. - API_URL="https://canvas.ucsd.edu", # the domain name of canvas - course_id="", # Course ID, can be found in the course url - group_category="", # target group category (set) of interests - verbosity=1 # Controls the verbosity: 0 = Silent, 1 = print all messages - ): + def __init__( + self, + credentials_fp="", # credential file path. See docs/getting-started/authentication.md for the template. + API_URL="https://canvas.ucsd.edu", # the domain name of canvas + course_id="", # Course ID, can be found in the course url + group_category="", # target group category (set) of interests + verbosity=1, # Controls the verbosity: 0 = Silent, 1 = print all messages + ): """Initialize a CanvasGroup instance and optionally authenticate and configure. Args: @@ -83,9 +86,9 @@ def __init__(self, if group_category != "": self.set_group_category(group_category) - def auth_canvas(self, - credentials_fp: str # the Authenticator key generated from canvas - ): + def auth_canvas( + self, credentials_fp: str # the Authenticator key generated from canvas + ): """Authenticate with the Canvas API using a credentials file. Reads the Canvas API token from the JSON credentials file and @@ -110,9 +113,7 @@ def auth_canvas(self, if self.verbosity != 0: print(f"{bcolors.OKGREEN}Authorization Successful!{bcolors.ENDC}") - def set_course(self, - course_id: int # the course id of the target course - ): + def set_course(self, course_id: int): # the course id of the target course """Set the target course and fetch the student roster. Retrieves the course by ID, fetches all enrolled students, and @@ -129,9 +130,11 @@ def set_course(self, if self.verbosity != 0: print(f"Course Set: {bcolors.OKGREEN} {self.course.name} {bcolors.ENDC}") print(f"Getting List of Users... This might take a while...") - self.users = list(self.course.get_users(enrollment_type=['student'])) + self.users = list(self.course.get_users(enrollment_type=["student"])) if self.verbosity != 0: - print(f"Users Fetch Complete! The course has {bcolors.OKBLUE}{len(self.users)}{bcolors.ENDC} students.") + print( + f"Users Fetch Complete! The course has {bcolors.OKBLUE}{len(self.users)}{bcolors.ENDC} students." + ) self.email_to_canvas_id = {} self.canvas_id_to_email = {} self.email_to_name = {} @@ -142,12 +145,14 @@ def set_course(self, self.email_to_name[u.email.split("@")[0]] = u.short_name except Exception: if self.verbosity != 0: - print(f"{bcolors.WARNING}Failed to Parse email and id" - f" for {bcolors.UNDERLINE}{u.short_name}{bcolors.ENDC}{bcolors.ENDC}") + print( + f"{bcolors.WARNING}Failed to Parse email and id" + f" for {bcolors.UNDERLINE}{u.short_name}{bcolors.ENDC}{bcolors.ENDC}" + ) - def link_assignment(self, - assignment_id: int # assignment id, found at the url of assignmnet tab - ) -> canvasapi.assignment.Assignment: # target assignment + def link_assignment( + self, assignment_id: int # assignment id, found at the url of assignmnet tab + ) -> canvasapi.assignment.Assignment: # target assignment """Link a Canvas assignment for grading. Fetches the assignment by ID and stores it for subsequent grading @@ -170,12 +175,13 @@ def link_assignment(self, self.assignment = assignment return assignment - def post_grade(self, - student_id: int, # canvas student id of student. found in self.email_to_canvas_id - grade: float, # grade of that assignment - text_comment="", # text comment of the submission. Can feed - force=False, # whether force to post grade for all students. If False (default), it will skip post for the same score. - ) -> canvasapi.submission.Submission: # created submission + def post_grade( + self, + student_id: int, # canvas student id of student. found in self.email_to_canvas_id + grade: float, # grade of that assignment + text_comment="", # text comment of the submission. Can feed + force=False, # whether force to post grade for all students. If False (default), it will skip post for the same score. + ) -> canvasapi.submission.Submission: # created submission """Post a grade and optional comment to the linked Canvas assignment. Submits a grade for a specific student on the currently linked @@ -198,24 +204,23 @@ def post_grade(self, submission = self.assignment.get_submission(student_id) if not force and submission.score == grade: if self.verbosity != 0: - print(f"Grade for {bcolors.OKGREEN+self.canvas_id_to_email[student_id]+bcolors.ENDC} did not change.\n" - f"{bcolors.OKCYAN}Skipped{bcolors.ENDC}.\n" - ) + print( + f"Grade for {bcolors.OKGREEN+self.canvas_id_to_email[student_id]+bcolors.ENDC} did not change.\n" + f"{bcolors.OKCYAN}Skipped{bcolors.ENDC}.\n" + ) return edited = submission.edit( - submission={ - 'posted_grade': grade - }, comment={ - 'text_comment': text_comment - } + submission={"posted_grade": grade}, comment={"text_comment": text_comment} ) if self.verbosity != 0: - print(f"Grade for {bcolors.OKGREEN+self.canvas_id_to_email[student_id]+bcolors.ENDC} Posted!") + print( + f"Grade for {bcolors.OKGREEN+self.canvas_id_to_email[student_id]+bcolors.ENDC} Posted!" + ) return edited - def get_email_by_name(self, - name_fussy: str # search by first name or last name of a student - ) -> str: # email of a search student + def get_email_by_name( + self, name_fussy: str # search by first name or last name of a student + ) -> str: # email of a search student """Look up a student's email prefix by a partial name match. Performs a case-insensitive substring search against all student @@ -237,10 +242,9 @@ def get_email_by_name(self, return email raise ValueError(f"Name {name_fussy} Not Found.") - - def set_group_category(self, - category_name: str # the target group category - ) -> canvasapi.group.GroupCategory: # target group category object + def set_group_category( + self, category_name: str # the target group category + ) -> canvasapi.group.GroupCategory: # target group category object """Set the active group category and fetch its groups. Selects a group category (group set) by name and retrieves all @@ -261,22 +265,25 @@ def set_group_category(self, try: self.group_category = self.group_categories[category_name] except KeyError: - raise KeyError(f"{category_name} did not found in the group categories. " - f"Try to create one with CanvasGroup.create_group_category") + raise KeyError( + f"{category_name} did not found in the group categories. " + f"Try to create one with CanvasGroup.create_group_category" + ) if self.verbosity != 0: print(f"Setting Group Category... ") self.groups = list(self.group_category.get_groups()) self.group_to_emails = { - group.name: [ - u.login_id for u in list(group.get_users()) - ] for group in self.groups} + group.name: [u.login_id for u in list(group.get_users())] + for group in self.groups + } if self.verbosity != 0: print(f"Group Category: {bcolors.OKGREEN+category_name+bcolors.ENDC} Set!") return self.group_category - def get_groups(self, - category_name="" # the target group category. If not provided, will look for self.group_category - ) -> dict: # {group_name: [student_emails]} + def get_groups( + self, + category_name="", # the target group category. If not provided, will look for self.group_category + ) -> dict: # {group_name: [student_emails]} """Get groups and their members in the current or specified category. Returns a dictionary mapping group names to lists of member email @@ -310,7 +317,7 @@ def get_course(self): """ return self.course - def get_group_categories(self) -> dict: # return a name / group category object + def get_group_categories(self) -> dict: # return a name / group category object """List all group categories (group sets) in the current course. Fetches every group category from the Canvas course and caches @@ -323,9 +330,10 @@ def get_group_categories(self) -> dict: # return a name / group category object self.group_categories = {cat.name: cat for cat in categories} return {cat.name: cat for cat in categories} - def create_group_category(self, - params: dict # the parameter of canvas group category API @ [this link](https://canvas.instructure.com/doc/api/group_categories.html#method.group_categories.create) - ) -> canvasapi.group.GroupCategory: # the generated group category object + def create_group_category( + self, + params: dict, # the parameter of canvas group category API @ [this link](https://canvas.instructure.com/doc/api/group_categories.html#method.group_categories.create) + ) -> canvasapi.group.GroupCategory: # the generated group category object """Create a new group category (group set) in the current course. Args: @@ -339,9 +347,10 @@ def create_group_category(self, self.group_category = self.course.create_group_category(**params) return self.group_category - def create_group(self, - params: dict, #the parameter of canvas group create API at [this link](https://canvas.instructure.com/doc/api/groups.html#method.groups.create) - ) -> canvasapi.group.Group: # the generated target group object + def create_group( + self, + params: dict, # the parameter of canvas group create API at [this link](https://canvas.instructure.com/doc/api/groups.html#method.groups.create) + ) -> canvasapi.group.Group: # the generated target group object """Create a group under the currently active group category. Args: @@ -356,17 +365,24 @@ def create_group(self, ValueError: If no group category has been set or created. """ if self.group_category is None: - raise ValueError("Have you specified or create a group category (group set)?") + raise ValueError( + "Have you specified or create a group category (group set)?" + ) group = self.group_category.create_group(**params) if self.verbosity != 0: - print(f"In Group Set: {bcolors.OKBLUE+self.group_category.name+bcolors.ENDC},") + print( + f"In Group Set: {bcolors.OKBLUE+self.group_category.name+bcolors.ENDC}," + ) print(f"Group {bcolors.OKGREEN+params['name']+bcolors.ENDC} Created!") return group - def join_canvas_group(self, - group: canvasapi.group.Group, # the group that students will join - group_members:[str], # list of group member's SIS Login (email prefix, before the @.) - ) -> [str]: # list of unsuccessful join + def join_canvas_group( + self, + group: canvasapi.group.Group, # the group that students will join + group_members: [ + str + ], # list of group member's SIS Login (email prefix, before the @.) + ) -> [str]: # list of unsuccessful join """Add students to a Canvas group by their email prefixes. Iterates over the provided member list and creates a group @@ -387,17 +403,22 @@ def join_canvas_group(self, canvas_id = self.email_to_canvas_id[group_member] group.create_membership(canvas_id) if self.verbosity != 0: - print(f"Member {bcolors.OKGREEN}{group_member}{bcolors.ENDC} Joined group {bcolors.OKGREEN}{group.name}{bcolors.ENDC}") + print( + f"Member {bcolors.OKGREEN}{group_member}{bcolors.ENDC} Joined group {bcolors.OKGREEN}{group.name}{bcolors.ENDC}" + ) except KeyError as e: unsuccessful_join.append(group_member) - print(f"Error adding student {bcolors.WARNING+group_member+bcolors.ENDC} \n into group {group.name}") + print( + f"Error adding student {bcolors.WARNING+group_member+bcolors.ENDC} \n into group {group.name}" + ) print(e) return unsuccessful_join - def fetch_username_from_quiz(self, - quiz_id: int, # quiz id of the username quiz - col_index=7, # canvas quiz generated csv's question field column index - ) -> dict: # {SIS Login ID: github username} dictionary + def fetch_username_from_quiz( + self, + quiz_id: int, # quiz id of the username quiz + col_index=7, # canvas quiz generated csv's question field column index + ) -> dict: # {SIS Login ID: github username} dictionary """Extract GitHub usernames from a Canvas quiz student analysis. Downloads the student analysis report for the specified quiz, @@ -414,17 +435,18 @@ def fetch_username_from_quiz(self, A dict mapping student email prefixes (SIS Login IDs) to their submitted GitHub usernames. """ - header = {'Authorization': 'Bearer ' + self.API_KEY} + header = {"Authorization": "Bearer " + self.API_KEY} quiz = self.course.get_quiz(quiz_id) if self.verbosity != 0: - print(f"Quiz: {bcolors.OKGREEN+quiz.title+bcolors.ENDC} " - f"fetch! \nGenerating Student Analaysis..." - ) + print( + f"Quiz: {bcolors.OKGREEN+quiz.title+bcolors.ENDC} " + f"fetch! \nGenerating Student Analaysis..." + ) report = quiz.create_report("student_analysis") progress_url = report.progress_url completed = False while not completed: - status = requests.get(progress_url, headers = header).json() + status = requests.get(progress_url, headers=header).json() if self.verbosity != 0: self._progress(status["completion"]) time.sleep(0.1) @@ -441,10 +463,11 @@ def fetch_username_from_quiz(self, col = list(df.columns) # rename column if self.verbosity != 0: - print(f"The Question asked is {bcolors.OKBLUE}{col[col_index]}{bcolors.ENDC}. \n" - f"Make sure this is the correct question where you asked student for their GitHub id.\n" - f"If you need to change the index of columns, change the col_index argument of this call." - ) + print( + f"The Question asked is {bcolors.OKBLUE}{col[col_index]}{bcolors.ENDC}. \n" + f"Make sure this is the correct question where you asked student for their GitHub id.\n" + f"If you need to change the index of columns, change the col_index argument of this call." + ) col[col_index] = "GitHub Username" df.columns = col small = df[["id", "GitHub Username"]].copy() @@ -452,10 +475,11 @@ def fetch_username_from_quiz(self, small = small[["email", "GitHub Username"]].set_index("email") return small.to_dict()["GitHub Username"] - def _check_single_github_username(self, - email:str, # Student email - github_username:str, # student input we want to test - ) -> bool: # whether the username is valid + def _check_single_github_username( + self, + email: str, # Student email + github_username: str, # student input we want to test + ) -> bool: # whether the username is valid "Check a single GitHub username on GitHub" if self.credentials_fp is None: raise ValueError("Credentials not set. Set it via self.auth_canvas") @@ -468,16 +492,19 @@ def _check_single_github_username(self, try: self.github.get_user(github_username) except Exception as e: - print(f"User: {bcolors.WARNING+github_username+bcolors.ENDC} Not Found on GitHub") + print( + f"User: {bcolors.WARNING+github_username+bcolors.ENDC} Not Found on GitHub" + ) return False return True - def check_github_usernames(self, - github_usernames:dict, # {email: github username} of student inputs, generated from self.fetch_username_from_quiz - send_canvas_email=False, # whether send a reminder for students who have an invalid GitHub username - send_undone_reminder=False, # send quiz undone reminder using canvas email - quiz_url="", # include a quiz url in the conversation for student to quickly complete the quiz. - ) -> dict: # {email: github username} of unreasonable GitHub id + def check_github_usernames( + self, + github_usernames: dict, # {email: github username} of student inputs, generated from self.fetch_username_from_quiz + send_canvas_email=False, # whether send a reminder for students who have an invalid GitHub username + send_undone_reminder=False, # send quiz undone reminder using canvas email + quiz_url="", # include a quiz url in the conversation for student to quickly complete the quiz. + ) -> dict: # {email: github username} of unreasonable GitHub id """Batch validate GitHub usernames and optionally notify students. Checks each GitHub username against the GitHub API. Optionally @@ -506,11 +533,12 @@ def check_github_usernames(self, self.create_conversation( self.email_to_canvas_id[email], subject="Unidentifiable GitHub Username", - body=(f"Hi {email}, \n Your GitHub Username: {github_username} " - f"is unidentifiable on github.com. \n Please complete the quiz GitHub Username Quiz again.\n" - f"{quiz_url} \n" - f"Thank You." - ) + body=( + f"Hi {email}, \n Your GitHub Username: {github_username} " + f"is unidentifiable on github.com. \n Please complete the quiz GitHub Username Quiz again.\n" + f"{quiz_url} \n" + f"Thank You." + ), ) if self.verbosity != 0: print(f"{bcolors.OKGREEN}Notification Sent!{bcolors.ENDC}") @@ -519,37 +547,38 @@ def check_github_usernames(self, for email in self.email_to_canvas_id.keys(): if email not in submitted: if self.verbosity != 0: - print(f"Student {bcolors.WARNING}{email}{bcolors.ENDC} did not" - f" submit their github username." - ) + print( + f"Student {bcolors.WARNING}{email}{bcolors.ENDC} did not" + f" submit their github username." + ) # means student did not submit the quiz if send_canvas_email: self.create_conversation( self.email_to_canvas_id[email], subject="GitHub Username Quiz Not Completed", - body=(f"Hi {email}, \n You did not complete the GitHub Quiz." - f"\n Please complete the quiz GitHub Username Quiz ASAP\n" - f"{quiz_url} \n" - f"Thank You." - ) + body=( + f"Hi {email}, \n You did not complete the GitHub Quiz." + f"\n Please complete the quiz GitHub Username Quiz ASAP\n" + f"{quiz_url} \n" + f"Thank You." + ), ) if self.verbosity != 0: print(f"{bcolors.OKGREEN}Notification Sent!{bcolors.ENDC}") return unsuccessful - def _progress(self, - percentage:int # percentage of the progress - ): - sys.stdout.write('\r') + def _progress(self, percentage: int): # percentage of the progress + sys.stdout.write("\r") # the exact output you're looking for: - sys.stdout.write("[%-20s] %d%%" % ('='*int(percentage//5), percentage)) + sys.stdout.write("[%-20s] %d%%" % ("=" * int(percentage // 5), percentage)) sys.stdout.flush() - def assign_canvas_group(self, - group_name: str, # group name, display on canvas - group_members:[str], # list of group member's SIS Login - in_group_category: str, # specify which group category the group belongs to - ) -> (canvasapi.group.Group, [str]): # list of unsuccessful join + def assign_canvas_group( + self, + group_name: str, # group name, display on canvas + group_members: [str], # list of group member's SIS Login + in_group_category: str, # specify which group category the group belongs to + ) -> (canvasapi.group.Group, [str]): # list of unsuccessful join """Create a Canvas group and assign members to it. Sets the group category, creates a new group, and adds the @@ -572,11 +601,12 @@ def assign_canvas_group(self, print(f"Group {bcolors.OKGREEN+group_name+bcolors.ENDC} created!") return group, unsuccessful_join - def create_conversation(self, - recipients:int, # recipient ids. These may be user ids or course/group ids prefixed with 'course_' or 'group_' respectively. - subject:str, # subject of the conversation - body:str, # The message to be sent - ) -> canvasapi.conversation.Conversation: # created conversation + def create_conversation( + self, + recipients: int, # recipient ids. These may be user ids or course/group ids prefixed with 'course_' or 'group_' respectively. + subject: str, # subject of the conversation + body: str, # The message to be sent + ) -> canvasapi.conversation.Conversation: # created conversation """Send a Canvas message (conversation) to a student. Creates a new conversation in the context of the current course. diff --git a/CanvasGroupy/github.py b/CanvasGroupy/github.py index 62aa250..50c6af1 100644 --- a/CanvasGroupy/github.py +++ b/CanvasGroupy/github.py @@ -1,4 +1,4 @@ -__all__ = ['bcolors', 'GitHubGroup'] +__all__ = ["bcolors", "GitHubGroup"] from github import Github import github @@ -8,16 +8,18 @@ import glob from pprint import pprint + class bcolors: - HEADER = '\033[95m' - OKBLUE = '\033[94m' - OKCYAN = '\033[96m' - OKGREEN = '\033[92m' - WARNING = '\033[93m' - FAIL = '\033[91m' - ENDC = '\033[0m' - BOLD = '\033[1m' - UNDERLINE = '\033[4m' + HEADER = "\033[95m" + OKBLUE = "\033[94m" + OKCYAN = "\033[96m" + OKGREEN = "\033[92m" + WARNING = "\033[93m" + FAIL = "\033[91m" + ENDC = "\033[0m" + BOLD = "\033[1m" + UNDERLINE = "\033[4m" + class GitHubGroup: """Manage GitHub organization repositories, teams, and collaborators. @@ -32,11 +34,12 @@ class GitHubGroup: verbosity: Controls output verbosity (0 = silent, 1 = print status). """ - def __init__(self, - credentials_fp="", # the file path to the credential json - org="", # the organization name - verbosity=1 # Controls the verbosity: 0=silent, 1=print status - ): + def __init__( + self, + credentials_fp="", # the file path to the credential json + org="", # the organization name + verbosity=1, # Controls the verbosity: 0=silent, 1=print status + ): """Initialize a GitHubGroup instance and optionally authenticate. Args: @@ -55,9 +58,10 @@ def __init__(self, if org != "": self.set_org(org) - def auth_github(self, - credentials_fp: str # the personal access token generated at GitHub Settings - ): + def auth_github( + self, + credentials_fp: str, # the personal access token generated at GitHub Settings + ): """Authenticate with GitHub using a credentials file. Reads the GitHub personal access token from the JSON credentials @@ -78,12 +82,12 @@ def auth_github(self, # check authorization _ = self.github.get_user().get_repos()[0] if self.verbosity != 0: - print(f"Successfully Authenticated. " - f"GitHub account: {bcolors.OKGREEN} {self.github.get_user().login} {bcolors.ENDC}") + print( + f"Successfully Authenticated. " + f"GitHub account: {bcolors.OKGREEN} {self.github.get_user().login} {bcolors.ENDC}" + ) - def set_org(self, - org: str # the target organization name - ): + def set_org(self, org: str): # the target organization name """Set the target GitHub organization. Retrieves the organization object by name and stores it for @@ -98,15 +102,18 @@ def set_org(self, """ self.org = self.github.get_organization(org) if self.verbosity != 0: - print(f"Target Organization Set: {bcolors.OKGREEN} {self.org.login} {bcolors.ENDC}") - - def create_repo(self, - repo_name: str, # repository name - repo_template="", # template repository that new repo will use. If empty string, an empty repo will be created. Put in the format of "/" - private=True, # visibility of the created repository - description="", # description for the GitHub repository - personal_account=False, # create repos in personal GitHub account - ) -> github.Repository.Repository: + print( + f"Target Organization Set: {bcolors.OKGREEN} {self.org.login} {bcolors.ENDC}" + ) + + def create_repo( + self, + repo_name: str, # repository name + repo_template="", # template repository that new repo will use. If empty string, an empty repo will be created. Put in the format of "/" + private=True, # visibility of the created repository + description="", # description for the GitHub repository + personal_account=False, # create repos in personal GitHub account + ) -> github.Repository.Repository: """Create a repository, either blank or from a template. Creates a new repository under the target organization (or @@ -138,9 +145,7 @@ def create_repo(self, parent = self.org if repo_template == "": return parent.create_repo( - name=repo_name, - private=private, - description=description + name=repo_name, private=private, description=description ) # create from template return parent.create_repo_from_template( @@ -150,9 +155,9 @@ def create_repo(self, description=description, ) - def get_repo(self, - repo_full_name: str # full name of the target repository - ) -> github.Repository.Repository: + def get_repo( + self, repo_full_name: str # full name of the target repository + ) -> github.Repository.Repository: """Get a repository by its full name. Attempts to fetch the repository directly by full name. If that @@ -174,9 +179,9 @@ def get_repo(self, except Exception: return self.org.get_repo(repo_full_name) - def get_org_repo(self, - repo_full_name: str # full name of the target repository - ) -> github.Repository.Repository: + def get_org_repo( + self, repo_full_name: str # full name of the target repository + ) -> github.Repository.Repository: """Get a repository within the target organization. Args: @@ -192,10 +197,7 @@ def get_org_repo(self, """ return self.org.get_repo(repo_full_name) - - def get_team(self, - team_slug:str # team slug of the team - ) -> github.Team.Team: + def get_team(self, team_slug: str) -> github.Team.Team: # team slug of the team """Get a team by its slug within the target organization. Args: @@ -209,14 +211,17 @@ def get_team(self, github.UnknownObjectException: If the team slug is not found. """ if self.org is None: - raise ValueError("The organization has not been set. Please set it via g.set_org") + raise ValueError( + "The organization has not been set. Please set it via g.set_org" + ) return self.org.get_team_by_slug(team_slug) - def rename_files(self, - repo: github.Repository.Repository, # the repository that we want to rename file - og_filename: str, # old file name - new_filename: str # new file name - ): + def rename_files( + self, + repo: github.Repository.Repository, # the repository that we want to rename file + og_filename: str, # old file name + new_filename: str, # new file name + ): """Rename a file in a repository by creating a copy and deleting the original. This performs a rename by committing the file content under the @@ -231,15 +236,18 @@ def rename_files(self, repo.create_file(new_filename, "rename files", file.decoded_content) repo.delete_file(og_filename, "delete old files", file.sha) if self.verbosity != 0: - print(f"File Successfully Renamed from " - f" {bcolors.OKCYAN} {og_filename} {bcolors.ENDC} " - f" to {bcolors.OKGREEN} {new_filename} {bcolors.ENDC}") - - def add_collaborator(self, - repo: github.Repository.Repository, # target repository - collaborator:str, # GitHub username of the collaborator - permission:str # `pull`, `push` or `admin` - ): + print( + f"File Successfully Renamed from " + f" {bcolors.OKCYAN} {og_filename} {bcolors.ENDC} " + f" to {bcolors.OKGREEN} {new_filename} {bcolors.ENDC}" + ) + + def add_collaborator( + self, + repo: github.Repository.Repository, # target repository + collaborator: str, # GitHub username of the collaborator + permission: str, # `pull`, `push` or `admin` + ): """Add a collaborator to a repository with the specified permission. Args: @@ -253,14 +261,17 @@ def add_collaborator(self, except Exception as e: print(f"{bcolors.WARNING}Add Failed for {collaborator}{bcolors.ENDC}") if self.verbosity != 0: - print(f"Added Collaborator: {bcolors.OKGREEN} {collaborator} {bcolors.ENDC}" - f" to: {bcolors.OKGREEN} {repo.name} {bcolors.ENDC} with " - f"permission: {bcolors.OKGREEN} {permission} {bcolors.ENDC}") - - def remove_collaborator(self, - repo: github.Repository.Repository, # target repository - collaborator:str, # GitHub username of the collaborator - ): + print( + f"Added Collaborator: {bcolors.OKGREEN} {collaborator} {bcolors.ENDC}" + f" to: {bcolors.OKGREEN} {repo.name} {bcolors.ENDC} with " + f"permission: {bcolors.OKGREEN} {permission} {bcolors.ENDC}" + ) + + def remove_collaborator( + self, + repo: github.Repository.Repository, # target repository + collaborator: str, # GitHub username of the collaborator + ): """Remove a collaborator from a repository. Args: @@ -269,9 +280,10 @@ def remove_collaborator(self, """ repo.remove_from_collaborators(collaborator) - def resend_invitations(self, - repo: github.Repository.Repository, # target repository - ) -> [github.NamedUser.NamedUser]: # list of re-invited user + def resend_invitations( + self, + repo: github.Repository.Repository, # target repository + ) -> [github.NamedUser.NamedUser]: # list of re-invited user """Resend pending collaboration invitations for a repository. Revokes each pending invitation and re-invites the user with @@ -291,15 +303,19 @@ def resend_invitations(self, for p in pendings: repo.remove_invitation(p.id) if self.verbosity != 0: - print(f"{bcolors.WARNING}{bcolors.UNDERLINE}{p.invitee.login}{bcolors.ENDC} {bcolors.FAIL}Invite Revoked {bcolors.ENDC}") + print( + f"{bcolors.WARNING}{bcolors.UNDERLINE}{p.invitee.login}{bcolors.ENDC} {bcolors.FAIL}Invite Revoked {bcolors.ENDC}" + ) self.add_collaborator(repo, p.invitee.login, p.permissions) if self.verbosity != 0: - print(f"{bcolors.OKGREEN} Invite Resent to {p.invitee.login} {bcolors.ENDC}") + print( + f"{bcolors.OKGREEN} Invite Resent to {p.invitee.login} {bcolors.ENDC}" + ) return users - def resent_invitations_team_repos(self, - team_slug: str # team slug (name) under the org - ): + def resent_invitations_team_repos( + self, team_slug: str # team slug (name) under the org + ): """Resend pending invitations for all repositories under a team. Iterates over every repository associated with the team and @@ -317,14 +333,17 @@ def resent_invitations_team_repos(self, try: _ = self.resend_invitations(repo) except Exception as e: - print(f"{bcolors.WARNING}Make sure to have proper rights to the target repo{bcolors.ENDC}\n") + print( + f"{bcolors.WARNING}Make sure to have proper rights to the target repo{bcolors.ENDC}\n" + ) print(e) - def add_team(self, - repo: github.Repository.Repository, # target repository - team_slug: str, # team slug (name) - permission:str # `pull`, `push` or `admin` - ): + def add_team( + self, + repo: github.Repository.Repository, # target repository + team_slug: str, # team slug (name) + permission: str, # `pull`, `push` or `admin` + ): """Add a team to a repository with the specified permission. Args: @@ -337,15 +356,18 @@ def add_team(self, team.add_to_repos(repo) team.update_team_repository(repo, permission) if self.verbosity != 0: - print(f"Team {bcolors.OKGREEN} {team.name} {bcolors.ENDC} " - f"added to {bcolors.OKGREEN} {repo.name} {bcolors.ENDC} " - f"with permission {bcolors.OKGREEN} {permission} {bcolors.ENDC}") - - def create_feedback_dir(self, - repo: github.Repository.Repository, # target repository - template_fp: str, - destination="feedback" # directory path of the template file. - ): + print( + f"Team {bcolors.OKGREEN} {team.name} {bcolors.ENDC} " + f"added to {bcolors.OKGREEN} {repo.name} {bcolors.ENDC} " + f"with permission {bcolors.OKGREEN} {permission} {bcolors.ENDC}" + ) + + def create_feedback_dir( + self, + repo: github.Repository.Repository, # target repository + template_fp: str, + destination="feedback", # directory path of the template file. + ): """Create a local feedback directory populated from template files. Copies all files from the template directory into a @@ -368,15 +390,17 @@ def create_feedback_dir(self, with open(f"{destination}/{repo.name}/{head}", "w+") as f: f.write(file) if self.verbosity != 0: - print(f"File {bcolors.OKGREEN}{head}{bcolors.ENDC} " - f"created at {bcolors.OKGREEN}{destination}/{repo.name}{bcolors.ENDC}" - ) - - def create_issue(self, - repo: github.Repository.Repository, # target repository - title: str, # title of the issue, - content: str # content of the issue - ) -> github.Issue.Issue: # open issue + print( + f"File {bcolors.OKGREEN}{head}{bcolors.ENDC} " + f"created at {bcolors.OKGREEN}{destination}/{repo.name}{bcolors.ENDC}" + ) + + def create_issue( + self, + repo: github.Repository.Repository, # target repository + title: str, # title of the issue, + content: str, # content of the issue + ) -> github.Issue.Issue: # open issue """Create a GitHub issue in the target repository. Args: @@ -393,10 +417,11 @@ def create_issue(self, print(f"Issue {bcolors.OKGREEN}{title}{bcolors.ENDC} Created!") return issue - def create_issue_from_md(self, - repo: github.Repository.Repository, # target repository, - md_fp: str # file path of the feedback markdown file - ) -> github.Issue.Issue: # open issue + def create_issue_from_md( + self, + repo: github.Repository.Repository, # target repository, + md_fp: str, # file path of the feedback markdown file + ) -> github.Issue.Issue: # open issue """Create a GitHub issue from a markdown file. Reads the markdown file, uses the first line (without the ``#`` @@ -417,10 +442,11 @@ def create_issue_from_md(self, content = md return self.create_issue(repo, title, content) - def release_feedback(self, - md_filename: str, # feedback markdown file name - feedback_dir="feedback", # feedback directory contains the markdown files - ): + def release_feedback( + self, + md_filename: str, # feedback markdown file name + feedback_dir="feedback", # feedback directory contains the markdown files + ): """Release feedback via GitHub issues to all groups. Iterates over every subdirectory in the feedback directory, @@ -443,21 +469,24 @@ def release_feedback(self, except Exception: print(f"Repo: {bcolors.WARNING}{repo_name} NOT FOUND!{bcolors.ENDC}") continue - self.create_issue_from_md(repo, os.path.join(feedback_dir, repo_name, md_filename)) - - def create_group_repo(self, - repo_name: str, # group repository name - collaborators: [str], # list of collaborators GitHub id - permission: str, # the permission of collaborators. `pull`, `push` or `admin` - rename_files=dict(), # dictionary of files renames {:} - repo_template="", # If empty string, an empty repo will be created. Put in the format of "/" - private=True, # visibility of the created repository - description="", # description for the GitHub repository - team_slug="", # team slug, add to this repo - team_permission="", # team permission to this repository `pull`, `push` or `admin` - feedback_dir=False, # whether to create a feedback directory for each repository created - feedback_template_fp="", # the directory of the feedback template - ) -> github.Repository.Repository: # created repository + self.create_issue_from_md( + repo, os.path.join(feedback_dir, repo_name, md_filename) + ) + + def create_group_repo( + self, + repo_name: str, # group repository name + collaborators: [str], # list of collaborators GitHub id + permission: str, # the permission of collaborators. `pull`, `push` or `admin` + rename_files=dict(), # dictionary of files renames {:} + repo_template="", # If empty string, an empty repo will be created. Put in the format of "/" + private=True, # visibility of the created repository + description="", # description for the GitHub repository + team_slug="", # team slug, add to this repo + team_permission="", # team permission to this repository `pull`, `push` or `admin` + feedback_dir=False, # whether to create a feedback directory for each repository created + feedback_template_fp="", # the directory of the feedback template + ) -> github.Repository.Repository: # created repository """Create a group repository with collaborators and team permissions. Creates a repository (optionally from a template), renames files @@ -495,7 +524,9 @@ def create_group_repo(self, """ repo = self.create_repo(repo_name, repo_template, private, description) if self.verbosity != 0: - print(f"Repo {bcolors.OKGREEN} {repo.name} {bcolors.ENDC} Created... Wait for 3 sec to updates") + print( + f"Repo {bcolors.OKGREEN} {repo.name} {bcolors.ENDC} Created... Wait for 3 sec to updates" + ) time.sleep(3) for og_name, new_name in rename_files.items(): self.rename_files(repo, og_name, new_name) @@ -504,7 +535,9 @@ def create_group_repo(self, if team_slug != "": self.add_team(repo, team_slug, team_permission) if self.verbosity != 0: - print(f"Group Repo: {bcolors.OKGREEN} {repo_name} {bcolors.ENDC} successfuly created!") + print( + f"Group Repo: {bcolors.OKGREEN} {repo_name} {bcolors.ENDC} successfuly created!" + ) print(f"Repo URL: https://github.com/{self.org.login}/{repo_name}") if feedback_dir: if feedback_template_fp == "": diff --git a/CanvasGroupy/grading.py b/CanvasGroupy/grading.py index 32cf000..5402486 100644 --- a/CanvasGroupy/grading.py +++ b/CanvasGroupy/grading.py @@ -1,20 +1,22 @@ -__all__ = ['bcolors', 'Grading'] +__all__ = ["bcolors", "Grading"] from . import * import github import canvasapi from ast import literal_eval + class bcolors: - HEADER = '\033[95m' - OKBLUE = '\033[94m' - OKCYAN = '\033[96m' - OKGREEN = '\033[92m' - WARNING = '\033[93m' - FAIL = '\033[91m' - ENDC = '\033[0m' - BOLD = '\033[1m' - UNDERLINE = '\033[4m' + HEADER = "\033[95m" + OKBLUE = "\033[94m" + OKCYAN = "\033[96m" + OKGREEN = "\033[92m" + WARNING = "\033[93m" + FAIL = "\033[91m" + ENDC = "\033[0m" + BOLD = "\033[1m" + UNDERLINE = "\033[4m" + class Grading: """Orchestrate grading between GitHub and Canvas LMS. @@ -28,10 +30,11 @@ class Grading: cg: An authenticated CanvasGroup instance. """ - def __init__(self, - ghg:GitHubGroup=None, # authenticated GitHub object - cg:CanvasGroup=None, # authenticated canvas object - ): + def __init__( + self, + ghg: GitHubGroup = None, # authenticated GitHub object + cg: CanvasGroup = None, # authenticated canvas object + ): """Initialize a Grading instance with GitHub and Canvas clients. Args: @@ -43,10 +46,11 @@ def __init__(self, self.ghg = ghg self.cg = cg - def create_issue_from_md(self, - repo:github.Repository.Repository, # target repository to create issue - md_fp: str # file path of the feedback markdown file - ) -> github.Issue.Issue: # open issue + def create_issue_from_md( + self, + repo: github.Repository.Repository, # target repository to create issue + md_fp: str, # file path of the feedback markdown file + ) -> github.Issue.Issue: # open issue """Create a GitHub issue from a markdown file. Delegates to the underlying GitHubGroup instance to read the @@ -61,10 +65,11 @@ def create_issue_from_md(self, """ return self.ghg.create_issue_from_md(repo, md_fp) - def fetch_issue(self, - repo:github.Repository.Repository, # target repository to fetch issue - component:str, # the component of the project grading, let it be proposal/checkpoint/final. Need to match the issue's title - ) -> github.Issue.Issue: + def fetch_issue( + self, + repo: github.Repository.Repository, # target repository to fetch issue + component: str, # the component of the project grading, let it be proposal/checkpoint/final. Need to match the issue's title + ) -> github.Issue.Issue: """Fetch a specific issue by matching the component name in its title. Searches all issues in the repository and returns the first one @@ -87,10 +92,11 @@ def fetch_issue(self, return issue raise ValueError(f"Issue related to {component} did not found.") - def parse_score_from_issue(self, - repo:github.Repository.Repository, # target repository to create issue - component:str, # The component of the project grading, let it be proposal/checkpoint/final. Need to match the issue's title - ) -> int: # the fetched score of that component + def parse_score_from_issue( + self, + repo: github.Repository.Repository, # target repository to create issue + component: str, # The component of the project grading, let it be proposal/checkpoint/final. Need to match the issue's title + ) -> int: # the fetched score of that component """Parse the numeric score from a GitHub issue grading template. Fetches the issue matching the given component and scans its @@ -115,16 +121,19 @@ def parse_score_from_issue(self, if "Score =" in line and "[comment]" not in line: score = literal_eval(line.split("=")[1]) return score - raise ValueError(f"Score Parse Error. please check the score format on github. \n" - f"Issue URL: {issue.url}") - - def update_canvas_score(self, - group_name:str, # target group name on a canvas group - assignment_id, # assignment id of the related component - score:float, # score of that component - issue:github.Issue.Issue=None, - post=False, # whether to post score via api. for testing purposes - ): + raise ValueError( + f"Score Parse Error. please check the score format on github. \n" + f"Issue URL: {issue.url}" + ) + + def update_canvas_score( + self, + group_name: str, # target group name on a canvas group + assignment_id, # assignment id of the related component + score: float, # score of that component + issue: github.Issue.Issue = None, + post=False, # whether to post score via api. for testing purposes + ): """Post a score to Canvas for all members of a group. Links the assignment, iterates over every member in the @@ -156,19 +165,18 @@ def update_canvas_score(self, text_comment += f"\nView at {issue.url.replace('https://api.github.com/repos', 'https://github.com')}" if post: self.cg.post_grade( - student_id=student_id, - grade=score, - text_comment=text_comment + student_id=student_id, grade=score, text_comment=text_comment ) else: print(f"{bcolors.WARNING}Post Disable{bcolors.ENDC}") print(f"For student: {member}, the score is {score}") print(f"Comments: {text_comment}") - def check_graded(self, - repo:github.Repository.Repository, # target repository to grade - component:str, # The component of the project grading, let it be proposal/checkpoint/final. Need to match the issue's title - ) -> bool: # Whether the repo is graded. + def check_graded( + self, + repo: github.Repository.Repository, # target repository to grade + component: str, # The component of the project grading, let it be proposal/checkpoint/final. Need to match the issue's title + ) -> bool: # Whether the repo is graded. """Check if a project component has been graded. Parses the score from the GitHub issue template. If the score @@ -186,19 +194,22 @@ def check_graded(self, """ score = self.parse_score_from_issue(repo, component) if score is ...: - print(f"{bcolors.WARNING}{repo.name}'s {component} Not Graded. {bcolors.ENDC}") + print( + f"{bcolors.WARNING}{repo.name}'s {component} Not Graded. {bcolors.ENDC}" + ) return False print(f"{bcolors.OKGREEN}{repo.name}'s {component} Graded. {bcolors.ENDC}") return True - def grade_project(self, - repo:github.Repository.Repository, # target repository to grade - component:str, # The component of the project grading, let it be proposal/checkpoint/final. Need to match the issue's title - assignment_id:int, # assignment id that link to that component of the project - canvas_group_name:dict=None, # mapping from GitHub repo name to Group name. If not specified, the repository name will be used. - canvas_group_category:str=None, # canvas group category (set) - post:bool=False, # whether to post score via api. For testing purposes - ): + def grade_project( + self, + repo: github.Repository.Repository, # target repository to grade + component: str, # The component of the project grading, let it be proposal/checkpoint/final. Need to match the issue's title + assignment_id: int, # assignment id that link to that component of the project + canvas_group_name: dict = None, # mapping from GitHub repo name to Group name. If not specified, the repository name will be used. + canvas_group_category: str = None, # canvas group category (set) + post: bool = False, # whether to post score via api. For testing purposes + ): """Grade a project component end-to-end. Parses the score from the GitHub issue, maps the repository to diff --git a/tests/conftest.py b/tests/conftest.py index 30e5932..1b312b5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,11 +4,11 @@ import pytest from unittest.mock import MagicMock, patch - # --------------------------------------------------------------------------- # Credential fixtures # --------------------------------------------------------------------------- + @pytest.fixture def credentials(tmp_path): """Create a temporary credentials.json and return its path.""" @@ -30,6 +30,7 @@ def bad_credentials(tmp_path): # Canvas mock fixtures # --------------------------------------------------------------------------- + def _make_mock_user(email_prefix, canvas_id, short_name=None): """Helper to create a mock Canvas user object.""" user = MagicMock() @@ -91,6 +92,7 @@ def mock_canvas_api(mock_course, mock_group_category): # GitHub mock fixtures # --------------------------------------------------------------------------- + @pytest.fixture def mock_github_api(): """Patch PyGithub's Github class and return (MockGithub, github_instance, mock_org).""" @@ -116,6 +118,7 @@ def mock_github_api(): # Grading fixtures # --------------------------------------------------------------------------- + @pytest.fixture def mock_issue(): """Mock GitHub issue with a parseable score.""" diff --git a/tests/test_assign.py b/tests/test_assign.py index bafcf56..4621d63 100644 --- a/tests/test_assign.py +++ b/tests/test_assign.py @@ -17,10 +17,12 @@ def loaded_ag(mock_services): """AssignGroup with two groups already loaded.""" ghg, cg = mock_services ag = AssignGroup(ghg=ghg, cg=cg) - df = pd.DataFrame({ - "group_name": ["Group1", "Group1", "Group2"], - "student_id": ["alice", "bob", "carol"], - }) + df = pd.DataFrame( + { + "group_name": ["Group1", "Group1", "Group2"], + "student_id": ["alice", "bob", "carol"], + } + ) ag.load_groups(df) return ag, ghg, cg @@ -29,10 +31,12 @@ class TestLoadGroups: def test_load_groups_from_dataframe(self, mock_services): ghg, cg = mock_services ag = AssignGroup(ghg=ghg, cg=cg) - df = pd.DataFrame({ - "group_name": ["Group1", "Group1", "Group2", "Group2"], - "student_id": ["alice", "bob", "carol", "dave"], - }) + df = pd.DataFrame( + { + "group_name": ["Group1", "Group1", "Group2", "Group2"], + "student_id": ["alice", "bob", "carol", "dave"], + } + ) ag.load_groups(df) assert ag.groups == { "Group1": ["alice", "bob"], @@ -43,7 +47,9 @@ def test_load_groups_from_csv(self, mock_services, tmp_path): ghg, cg = mock_services ag = AssignGroup(ghg=ghg, cg=cg) csv_path = tmp_path / "groups.csv" - csv_path.write_text("group_name,student_id\nGroup1,alice\nGroup1,bob\nGroup2,carol\n") + csv_path.write_text( + "group_name,student_id\nGroup1,alice\nGroup1,bob\nGroup2,carol\n" + ) ag.load_groups(str(csv_path)) assert ag.groups == { "Group1": ["alice", "bob"], @@ -65,10 +71,12 @@ def test_load_groups_requires_columns(self, mock_services): def test_constructor_groups_parameter_auto_loads(self, mock_services): ghg, cg = mock_services - df = pd.DataFrame({ - "group_name": ["Team1", "Team1", "Team2"], - "student_id": ["alice", "bob", "carol"], - }) + df = pd.DataFrame( + { + "group_name": ["Team1", "Team1", "Team2"], + "student_id": ["alice", "bob", "carol"], + } + ) ag = AssignGroup(ghg=ghg, cg=cg, groups=df) assert ag.groups == { "Team1": ["alice", "bob"], @@ -78,10 +86,12 @@ def test_constructor_groups_parameter_auto_loads(self, mock_services): def test_load_groups_warns_on_numeric_student_ids(self, mock_services): ghg, cg = mock_services ag = AssignGroup(ghg=ghg, cg=cg) - df = pd.DataFrame({ - "group_name": ["Group1", "Group1"], - "student_id": ["12345", "67890"], - }) + df = pd.DataFrame( + { + "group_name": ["Group1", "Group1"], + "student_id": ["12345", "67890"], + } + ) with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") ag.load_groups(df) diff --git a/tests/test_canvas.py b/tests/test_canvas.py index c886f11..c46ef01 100644 --- a/tests/test_canvas.py +++ b/tests/test_canvas.py @@ -12,7 +12,9 @@ def test_auth_loads_token_and_creates_canvas(self, credentials, mock_canvas_api) cg = CanvasGroup(verbosity=0) cg.auth_canvas(credentials) - MockCanvas.assert_called_once_with("https://canvas.ucsd.edu", "fake-canvas-token") + MockCanvas.assert_called_once_with( + "https://canvas.ucsd.edu", "fake-canvas-token" + ) canvas_instance.get_activity_stream_summary.assert_called_once() assert cg.API_KEY == "fake-canvas-token" @@ -32,7 +34,9 @@ def test_custom_api_url(self, credentials): MockCanvas.return_value = MagicMock() cg = CanvasGroup(API_URL="https://custom.canvas.edu", verbosity=0) cg.auth_canvas(credentials) - MockCanvas.assert_called_once_with("https://custom.canvas.edu", "fake-canvas-token") + MockCanvas.assert_called_once_with( + "https://custom.canvas.edu", "fake-canvas-token" + ) class TestSetCourse: @@ -175,7 +179,9 @@ def test_join_canvas_group_returns_failures(self, credentials, mock_canvas_api): unsuccessful = cg.join_canvas_group(group, ["alice", "nonexistent_student"]) assert "nonexistent_student" in unsuccessful - def test_assign_canvas_group_creates_and_joins(self, credentials, mock_canvas_api, mock_group_category): + def test_assign_canvas_group_creates_and_joins( + self, credentials, mock_canvas_api, mock_group_category + ): cg = self._setup_cg(credentials, mock_canvas_api) new_group = MagicMock() @@ -219,7 +225,9 @@ def test_link_assignment(self, credentials, mock_canvas_api): assert result.name == "Homework 1" def test_post_grade(self, credentials, mock_canvas_api): - cg, mock_assignment = self._setup_cg_with_assignment(credentials, mock_canvas_api) + cg, mock_assignment = self._setup_cg_with_assignment( + credentials, mock_canvas_api + ) mock_submission = MagicMock() mock_submission.score = 0 # different from new grade @@ -232,7 +240,9 @@ def test_post_grade(self, credentials, mock_canvas_api): ) def test_post_grade_skips_same_score(self, credentials, mock_canvas_api): - cg, mock_assignment = self._setup_cg_with_assignment(credentials, mock_canvas_api) + cg, mock_assignment = self._setup_cg_with_assignment( + credentials, mock_canvas_api + ) mock_submission = MagicMock() mock_submission.score = 95.0 # same as new grade @@ -243,7 +253,9 @@ def test_post_grade_skips_same_score(self, credentials, mock_canvas_api): assert result is None def test_post_grade_force_overrides_skip(self, credentials, mock_canvas_api): - cg, mock_assignment = self._setup_cg_with_assignment(credentials, mock_canvas_api) + cg, mock_assignment = self._setup_cg_with_assignment( + credentials, mock_canvas_api + ) mock_submission = MagicMock() mock_submission.score = 95.0 # same score, but force=True diff --git a/tests/test_github.py b/tests/test_github.py index 029a2ab..e8581f7 100644 --- a/tests/test_github.py +++ b/tests/test_github.py @@ -201,7 +201,9 @@ def test_create_issue(self, credentials, mock_github_api): repo.create_issue.return_value = mock_issue result = ghg.create_issue(repo, "Bug Report", "There is a bug") - repo.create_issue.assert_called_once_with(title="Bug Report", body="There is a bug") + repo.create_issue.assert_called_once_with( + title="Bug Report", body="There is a bug" + ) assert result == mock_issue def test_create_issue_from_md(self, credentials, mock_github_api, tmp_path): @@ -242,7 +244,9 @@ def test_release_feedback(self, credentials, mock_github_api, tmp_path): mock_org.get_repo.assert_called_with("team-alpha") mock_repo.create_issue.assert_called_once() - def test_release_feedback_skips_missing_repos(self, credentials, mock_github_api, tmp_path): + def test_release_feedback_skips_missing_repos( + self, credentials, mock_github_api, tmp_path + ): ghg = GitHubGroup(verbosity=0) ghg.auth_github(credentials) ghg.set_org("TestOrg") @@ -298,7 +302,9 @@ def _setup_ghg(self, credentials, mock_github_api): return ghg, mock_org @patch("time.sleep", return_value=None) - def test_creates_repo_and_adds_collaborators(self, mock_sleep, credentials, mock_github_api): + def test_creates_repo_and_adds_collaborators( + self, mock_sleep, credentials, mock_github_api + ): ghg, mock_org = self._setup_ghg(credentials, mock_github_api) mock_repo = MagicMock() @@ -315,7 +321,9 @@ def test_creates_repo_and_adds_collaborators(self, mock_sleep, credentials, mock assert mock_repo.add_to_collaborators.call_count == 2 @patch("time.sleep", return_value=None) - def test_creates_from_template_with_renames(self, mock_sleep, credentials, mock_github_api): + def test_creates_from_template_with_renames( + self, mock_sleep, credentials, mock_github_api + ): ghg, mock_org = self._setup_ghg(credentials, mock_github_api) _, github_instance, _ = mock_github_api diff --git a/tests/test_grading.py b/tests/test_grading.py index 152ca52..1afbdb0 100644 --- a/tests/test_grading.py +++ b/tests/test_grading.py @@ -44,10 +44,7 @@ def test_ignores_comment_lines(self): """Score lines with [comment] should be skipped.""" issue = MagicMock() issue.title = "Checkpoint Feedback" - issue.body = ( - "[comment]: # (Score = ...)\n" - "Score = 9.0\n" - ) + issue.body = "[comment]: # (Score = ...)\n" "Score = 9.0\n" repo = MagicMock() repo.get_issues.return_value = [issue] @@ -208,6 +205,7 @@ def test_sets_group_category_if_provided(self, mock_ungraded_issue): # Simulate set_group_category actually setting the attribute def _set_gc(name): cg.group_category = MagicMock(name=name) + cg.set_group_category.side_effect = _set_gc repo = MagicMock() diff --git a/uv.lock b/uv.lock index d9f5af1..c7be32f 100644 --- a/uv.lock +++ b/uv.lock @@ -126,6 +126,7 @@ dependencies = [ { name = "numpy" }, { name = "pandas" }, { name = "pygithub" }, + { name = "requests" }, ] [package.optional-dependencies] @@ -148,6 +149,7 @@ requires-dist = [ { name = "pandas" }, { name = "pygithub" }, { name = "pytest", marker = "extra == 'dev'" }, + { name = "requests" }, ] provides-extras = ["dev", "docs"] From 4f3b3ae9be580b73f7b114aa89e59285422c0427 Mon Sep 17 00:00:00 2001 From: scott-yj-yang Date: Fri, 27 Feb 2026 16:54:44 -0800 Subject: [PATCH 24/24] ci: add black formatting check to CI workflow Co-Authored-By: Claude Opus 4.6 --- .github/workflows/test.yaml | 2 ++ pyproject.toml | 1 + 2 files changed, 3 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index df86382..c8cd748 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -16,5 +16,7 @@ jobs: run: uv python install ${{ matrix.python-version }} - name: Install dependencies run: uv sync --all-extras + - name: Check formatting + run: uv run black --check CanvasGroupy/ tests/ - name: Run tests run: uv run pytest tests/ -v diff --git a/pyproject.toml b/pyproject.toml index 2a13da0..1e0a7f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,7 @@ dependencies = [ [project.optional-dependencies] dev = [ "pytest", + "black", ] docs = [ "mkdocs-material",