diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6b76334..e6cb838 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,22 +2,26 @@ name: Lint and Test on: push: - branches: [ main ] + branches: [ main, new-1.x.x ] pull_request: branches: [ main, new-1.x.x ] concurrency: - group: ${{ github.workflow }}-${{ github.ref_name || github.ref }} + group: ${{ github.workflow }}-${{ github.ref_name | github.ref }} cancel-in-progress: true +env: + PYTHON_VERSIONS: ['3.8', '3.9', '3.10', '3.11', '3.12'] + + jobs: package-lint: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ${{ env.PYTHON_VERSIONS }} fail-fast: false steps: - uses: actions/checkout@v4 @@ -36,7 +40,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ${{ env.PYTHON_VERSIONS }} fail-fast: false steps: - uses: actions/checkout@v4 @@ -63,7 +67,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ${{ env.PYTHON_VERSIONS }} fail-fast: false steps: - uses: actions/checkout@v4 @@ -84,14 +88,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: 3.12 - cache: 'pip' # caching pip dependencies - name: Install dependencies run: | - echo "TODO: add docs dependencies" - # python -m pip install -e .[docs] + python -m pip install -e .[docs] - name: TODO run: | echo "TODO: add docs tests" @@ -100,14 +99,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: 3.12 - cache: 'pip' # caching pip dependencies - name: Install dependencies run: | - echo "TODO: add docs dependencies" - # python -m pip install -e .[docs] + python -m pip install -e .[docs] - name: TODO run: | echo "TODO: add docs preview" diff --git a/.gitignore b/.gitignore index fdfff23..964e696 100644 --- a/.gitignore +++ b/.gitignore @@ -132,3 +132,7 @@ dmypy.json # Pyre type checker .pyre/ + +# Specified files +docs/ignore_me__production.md +docs/ignore_me__system_setup.md \ No newline at end of file diff --git a/Makefile b/Makefile index 6ef3ba5..aebee56 100644 --- a/Makefile +++ b/Makefile @@ -52,12 +52,15 @@ format: # Build the documentation docs-build: @echo "[make] Building the documentation..." - @echo "TODO" + mike deploy `cat VERSION` + mike set-default `cat VERSION` # Serve the documentation in development mode docs-serve: @echo "[make] Serve the documentation..." - @echo "TODO" + mike deploy `cat VERSION` + mike set-default `cat VERSION` + mike serve .PHONY: all help test-unit test-integration test-docstests test lint format docs-build docs-serve diff --git a/checker/__main__.py b/checker/__main__.py index 19dfb6e..f9285c3 100644 --- a/checker/__main__.py +++ b/checker/__main__.py @@ -1,6 +1,5 @@ from __future__ import annotations -import json import os import sys from pathlib import Path @@ -11,7 +10,7 @@ from checker.tester import Tester from checker.utils import print_info -from .configs import CheckerConfig, DeadlinesConfig, TaskConfig +from .configs import CheckerConfig, DeadlinesConfig from .exceptions import BadConfig, TestingError @@ -280,27 +279,5 @@ def grade( print_info("TESTING PASSED", color="green") -@cli.command(hidden=True) -@click.argument("output_folder", type=ClickReadableDirectory, default=".") -@click.pass_context -def schema( - ctx: click.Context, - output_folder: Path, -) -> None: - """ - Generate json schema for the checker configs. - """ - checker_schema = CheckerConfig.get_json_schema() - deadlines_schema = DeadlinesConfig.get_json_schema() - task_schema = TaskConfig.get_json_schema() - - with open(output_folder / "schema-checker.json", "w") as f: - json.dump(checker_schema, f, indent=2) - with open(output_folder / "schema-deadlines.json", "w") as f: - json.dump(deadlines_schema, f, indent=2) - with open(output_folder / "schema-task.json", "w") as f: - json.dump(task_schema, f, indent=2) - - if __name__ == "__main__": cli() diff --git a/checker/configs/checker.py b/checker/configs/checker.py index 4147c85..9981ccf 100644 --- a/checker/configs/checker.py +++ b/checker/configs/checker.py @@ -1,7 +1,7 @@ from __future__ import annotations from enum import Enum -from typing import Any, Union, Optional +from typing import Any, Union from pydantic import AnyUrl, Field, RootModel, ValidationError, field_validator @@ -14,10 +14,9 @@ class CheckerStructureConfig(CustomBaseModel): - # Note: use Optional/Union[...] instead of ... | None as pydantic does not support | in older python versions - ignore_patterns: Optional[list[str]] = None - private_patterns: Optional[list[str]] = None - public_patterns: Optional[list[str]] = None + ignore_patterns: list[str] | None = None + private_patterns: list[str] | None = None + public_patterns: list[str] | None = None # TODO: add check "**" is not allowed @@ -52,14 +51,13 @@ class FailType(Enum): name: str run: str - # Note: use Optional/Union[...] instead of ... | None as pydantic does not support | in older python versions - args: dict[str, Union[ParamType, TTemplate]] = Field(default_factory=dict) + args: dict[str, ParamType | TTemplate] = Field(default_factory=dict) - run_if: Union[bool, TTemplate, None] = None + run_if: bool | TTemplate | None = None fail: FailType = FailType.FAST # save pipline stage result to context under this key - register_output: Optional[str] = None + register_output: str | None = None class CheckerTestingConfig(CustomBaseModel): diff --git a/checker/configs/deadlines.py b/checker/configs/deadlines.py index 14e38cd..c91d608 100644 --- a/checker/configs/deadlines.py +++ b/checker/configs/deadlines.py @@ -3,7 +3,7 @@ import sys from datetime import datetime, timedelta from enum import Enum -from typing import cast, Union, Optional +from typing import cast if sys.version_info < (3, 8): from pytz import ( @@ -26,12 +26,11 @@ class DeadlinesType(Enum): class DeadlinesSettingsConfig(CustomBaseModel): timezone: str - # Note: use Optional/Union[...] instead of ... | None as pydantic does not support | in older python versions deadlines: DeadlinesType = DeadlinesType.HARD - max_submissions: Optional[int] = None + max_submissions: int | None = None submission_penalty: float = 0 - task_url: Optional[AnyUrl] = None # $GROUP_NAME $TASK_NAME vars are available + task_url: AnyUrl | None = None # $GROUP_NAME $TASK_NAME vars are available @field_validator("task_url") @classmethod @@ -66,8 +65,7 @@ class DeadlinesTaskConfig(CustomBaseModel): bonus: int = 0 special: int = 0 - # Note: use Optional/Union[...] instead of ... | None as pydantic does not support | in older python versions - url: Optional[AnyUrl] = None + url: AnyUrl | None = None @property def name(self) -> str: @@ -79,10 +77,9 @@ class DeadlinesGroupConfig(CustomBaseModel): enabled: bool = True - # Note: use Optional/Union[...] instead of ... | None as pydantic does not support | in older python versions start: datetime - steps: dict[float, Union[datetime, timedelta]] = Field(default_factory=dict) - end: Union[datetime, timedelta, None] = None + steps: dict[float, datetime | timedelta] = Field(default_factory=dict) + end: datetime | timedelta | None = None tasks: list[DeadlinesTaskConfig] = Field(default_factory=list) diff --git a/checker/configs/task.py b/checker/configs/task.py index 0ee5601..e271de1 100644 --- a/checker/configs/task.py +++ b/checker/configs/task.py @@ -1,7 +1,5 @@ from __future__ import annotations -from typing import Optional - from pydantic import Field, model_validator from .checker import ( @@ -17,8 +15,7 @@ class TaskConfig(CustomBaseModel, YamlLoaderMixin['TaskConfig']): version: int # if config exists, version is always present - # Note: use Optional[...] instead of ... | None as pydantic does not support | in older python versions - structure: Optional[CheckerStructureConfig] = None - parameters: Optional[CheckerParametersConfig] = None - task_pipeline: Optional[list[PipelineStageConfig]] = None - report_pipeline: Optional[list[PipelineStageConfig]] = None + structure: CheckerStructureConfig | None = None + parameters: CheckerParametersConfig | None = None + task_pipeline: list[PipelineStageConfig] | None = None + report_pipeline: list[PipelineStageConfig] | None = None diff --git a/checker/configs/utils.py b/checker/configs/utils.py index 2c2760a..cb4d231 100644 --- a/checker/configs/utils.py +++ b/checker/configs/utils.py @@ -1,5 +1,3 @@ -from __future__ import annotations - import re from pathlib import Path from typing import Any, Generic, TypeVar, Type, Protocol @@ -42,6 +40,5 @@ def to_yaml(self: T, path: Path) -> None: with path.open("w") as f: yaml.dump(self.model_dump(), f) - @classmethod - def get_json_schema(cls: type[T]) -> dict[str, Any]: - return cls.model_json_schema() + def get_json_schema(self: T) -> dict[str, Any]: + return self.model_json_schema() diff --git a/checker/plugins/__init__.py b/checker/plugins/__init__.py index 4eb3d51..f9dd01d 100644 --- a/checker/plugins/__init__.py +++ b/checker/plugins/__init__.py @@ -6,6 +6,7 @@ import sys from collections.abc import Sequence from pathlib import Path +from typing import Type from .base import PluginABC, PluginOutput # noqa: F401 diff --git a/checker/plugins/aggregate.py b/checker/plugins/aggregate.py index d9888cc..eddf9d9 100644 --- a/checker/plugins/aggregate.py +++ b/checker/plugins/aggregate.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Literal, Union +from typing import Literal from ..exceptions import PluginExecutionFailed from .base import PluginABC, PluginOutput @@ -13,7 +13,7 @@ class AggregatePlugin(PluginABC): class Args(PluginABC.Args): scores: list[float] - weights: Union[list[float], None] = None # as pydantic does not support | in older python versions + weights: list[float] | None = None strategy: Literal["mean", "sum", "min", "max", "product"] = "mean" # TODO: validate for weights: len weights should be equal to len scores # TODO: validate not empty scores diff --git a/checker/plugins/manytask.py b/checker/plugins/manytask.py index ff74e79..75e2df7 100644 --- a/checker/plugins/manytask.py +++ b/checker/plugins/manytask.py @@ -1,7 +1,5 @@ from __future__ import annotations -from typing import Union - from .base import PluginABC, PluginOutput @@ -11,8 +9,8 @@ class AggregatePlugin(PluginABC): name = "report_score_manytask" class Args(PluginABC.Args): - origin: Union[str, None] = None # as pydantic does not support | in older python versions - patterns: Union[list[str], None] = None # as pydantic does not support | in older python versions + origin: str | None = None + patterns: list[str] | None = None username: str task_name: str score: float # TODO: validate score is in [0, 1] (bonus score?) diff --git a/checker/plugins/scripts.py b/checker/plugins/scripts.py index b8dc303..43d4399 100644 --- a/checker/plugins/scripts.py +++ b/checker/plugins/scripts.py @@ -1,7 +1,5 @@ from __future__ import annotations -from typing import Union - from pydantic import Field from ..exceptions import PluginExecutionFailed @@ -15,8 +13,8 @@ class RunScriptPlugin(PluginABC): class Args(PluginABC.Args): origin: str - script: Union[str, list[str]] # as pydantic does not support | in older python versions - timeout: Union[float, None] = None # as pydantic does not support | in older python versions + script: str | list[str] + timeout: float | None = None isolate: bool = False env_whitelist: list[str] = Field(default_factory=lambda: ["PATH"]) diff --git a/docs/3_plugins.md b/docs/3_plugins.md index 743ec89..9e1f76c 100644 --- a/docs/3_plugins.md +++ b/docs/3_plugins.md @@ -10,18 +10,18 @@ You can refer to the [course-template](https://github.com/manytask/course-templa Plugin is a single stage of the pipeline, have arguments, return exclusion result. In a nutshell, it is a Python class overriding abstract class `checker.plugins.PluginABC`: -::: checker.plugins.PluginABC +::: checker.plugins.base.PluginABC handler: python Note that each plugin should override `checker.plugins.PluginABC.Args` class to provide arguments validation. Otherwise, empty arguments will be passed to `run` method. -::: checker.plugins.PluginABC.Args +::: checker.plugins.base.PluginABC.Args handler: python Each plugin output `checker.plugins.PluginOutput` class when executed successfully. -::: checker.plugins.PluginOutput +::: checker.plugins.base.PluginOutput handler: python In case of error, `checker.exceptions.PluginExecutionFailed` have to be raised. diff --git a/docs/4_usage.md b/docs/4_usage.md index 84ba082..cabd387 100644 --- a/docs/4_usage.md +++ b/docs/4_usage.md @@ -3,15 +3,21 @@ This section describes advanced usage of the checker. -## CLI +[//]: # (## CLI) -The main checker functionality is available via CLI. +[//]: # () +[//]: # (The main checker functionality is available via CLI.) -::: mkdocs-click - :module: checker.__main__ - :command: cli - :list_subcommands: True - :style: table +[//]: # () +[//]: # (::: mkdocs-click) + +[//]: # ( :module: checker.__main__) + +[//]: # ( :command: cli) + +[//]: # ( :list_subcommands: True) + +[//]: # ( :style: table) ## Docker diff --git a/docs/6_changelog.md b/docs/6_changelog.md index e69de29..dda7a1e 100644 --- a/docs/6_changelog.md +++ b/docs/6_changelog.md @@ -0,0 +1,4 @@ +{% + include-markdown "../CHANGELOG.md" + heading-offset=0 +%} \ No newline at end of file diff --git a/docs/images/logo-manytask.png b/docs/images/logo-manytask.png new file mode 100644 index 0000000..da7da0b Binary files /dev/null and b/docs/images/logo-manytask.png differ diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..1361910 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,4 @@ +{% + include-markdown "../README.md" + heading-offset=0 +%} \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..b44d91c --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,90 @@ +site_name: Checker +site_description: Python CLI script to run build and run tests against students solutions +site_url: https://manytask.github.io/checker/ + +docs_dir: ./docs +site_dir: ./site + +theme: + name: material + + palette: + # Palette toggle for light mode + - media: "(prefers-color-scheme: dark)" + scheme: default + toggle: + icon: material/lightbulb + name: Switch to dark mode + primary: teal + accent: purple + + # Palette toggle for dark mode + - media: "(prefers-color-scheme: light)" + scheme: slate + toggle: + icon: material/lightbulb + name: Switch to light mode + primary: teal + accent: lime + + features: + - navigation.tabs + - navigation.sections + - navigation.top + - search.suggest + - search.highlight + - content.tabs.link + - content.code.copy + - content.code.annotation + + language: en + + font: + text: Roboto + code: Roboto Mono + + icon: + repo: fontawesome/brands/github + + favicon: images/logo-manytask.png + logo: images/logo-manytask.png + +validation: + omitted_files: warn + absolute_links: warn + unrecognized_links: info + +extra: + version: + provider: mike + +repo_name: manytask/checker +repo_url: https://github.com/manytask/checker + +nav: + - Overview: index.md + - Concepts: 0_concepts.md + - Getting started: 1_getting_started.md + - Configuration: 2_configuration.md + - Plugins: 3_plugins.md + - Usage: 4_usage.md + - Development: 5_development.md + - Changelog: 6_changelog.md + +markdown_extensions: + - pymdownx.details + - pymdownx.superfences + - pymdownx.highlight: + pygments_lang_class: true + - pymdownx.extra + - pymdownx.tabbed: + alternate_style: true + - mkdocs_click + +plugins: + - mike: + alias_type: symlink + canonical_version: latest + - search + - include-markdown + - mkdocstrings \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 3c7e186..36b5416 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ dynamic = ["version"] [project.urls] Source = "https://github.com/yandexdataschool/checker" +Documentation = "https://manytask.github.io/checker/" [project.optional-dependencies] test = [ @@ -54,6 +55,17 @@ test = [ "types-PyYAML >=6.0.0,<7.0.0", "wheel >=0.40.0", ] +docs = [ + "mike >=1.1.0,<3.0.0", + "mkdocs >=1.4.0,<2.0.0", + "mkdocs-autorefs ==0.5.0", + "mkdocs-click >=0.8.0", + "mkdocs-include-markdown-plugin >=4.0.0,<7.0.0", + "mkdocs-material >=9.0.0,<10.0.0", + "mkdocs-material-extensions ==1.3.1", + "mkdocstrings ==0.24.0", + "mkdocstrings-python ==1.7.5", +] [tool.setuptools.dynamic] version = {file = "VERSION"} @@ -65,9 +77,6 @@ checker = "checker.__main__:cli" exclude = ["tests*"] -# --------------------------------------------------------------------------- # - - [tool.mypy] no_incremental = true ignore_missing_imports = true diff --git a/tests/conftest.py b/tests/conftest.py index ac1cfda..beeebee 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,3 @@ -from __future__ import annotations - import pytest