diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index c63587b0..f3d82ef4 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.8-bullseye +FROM python:3.10-bullseye ENV LANG=C.UTF-8 \ LC_ALL=C.UTF-8 diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 2478e811..0c25de2b 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: os: ["ubuntu-latest", "windows-latest"] - python-version: ["3.8"] + python-version: ["3.10"] runs-on: ${{ matrix.os }} steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b66be8b9..a15bbcf3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: build: strategy: matrix: - python-version: ["3.8"] + python-version: ["3.10"] runs-on: ubuntu-latest steps: @@ -44,7 +44,7 @@ jobs: needs: [build] runs-on: ubuntu-latest env: - PYTHON_VERSION: "3.8" + PYTHON_VERSION: "3.10" steps: - uses: actions/download-artifact@v3.0.2 with: diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index 10309c07..7107002f 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -13,7 +13,7 @@ jobs: concurrency: bump-version runs-on: ubuntu-latest env: - PYTHON_VERSION: "3.8" + PYTHON_VERSION: "3.10" outputs: bumped: ${{ steps.cz-bump.outputs.bumped }} revision: ${{ steps.cz-bump.outputs.revision }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 816e35e1..cc3cb6c9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ default_language_version: - python: python3.8 + python: python3.10 default_install_hook_types: [pre-commit, commit-msg] default_stages: [commit] diff --git a/.readthedocs.yaml b/.readthedocs.yaml index e664f54f..93928e33 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -17,9 +17,9 @@ python: - requirements: requirements.txt build: - os: ubuntu-20.04 + os: ubuntu-22.04 tools: - python: "3.8" + python: "3.10" apt_packages: - "doxygen" - "graphviz" # For dot graphs in doxygen diff --git a/pyproject.toml b/pyproject.toml index 021585b2..cee65138 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,6 @@ classifiers = [ ] dependencies = [ "GitPython>=3.1.30", - 'importlib_resources>=5.10.2;python_version<"3.9"', "PyGithub>=1.58.1", "sphinx>=5.3.0", "breathe>=4.34.0", @@ -34,7 +33,7 @@ dependencies = [ "pyyaml>=6.0", "fastjsonschema>=2.16" ] -requires-python = ">=3.8" +requires-python = ">=3.10" [project.urls] repository="https://github.com/RadeonOpenCompute/rocm-docs-core" @@ -65,7 +64,7 @@ where=["src"] rocm_docs = ["data/**/*", "rocm_docs_theme/**/*", "py.typed"] [tool.black] -target-version = ["py38"] +target-version = ["py310"] line-length = 80 color = true @@ -79,7 +78,7 @@ major_version_zero = true [tool.isort] # https://github.com/timothycrosley/isort/ -py_version = 38 +py_version = "310" line_length = 100 known_typing = ["typing", "types", "typing_extensions", "mypy", "mypy_extensions"] @@ -98,7 +97,7 @@ disallow_any_generics = true disallow_incomplete_defs = true implicit_reexport = false pretty = true -python_version = 3.8 +python_version = 3.10 show_column_numbers = true show_error_codes = true show_error_context = true @@ -115,5 +114,5 @@ warn_unused_ignores = true [tool.ruff] select = ["ARG", "F", "E", "W", "N", "D", "UP", "RET"] ignore = ["E501", "D203", "D213", "D4"] -target-version = "py38" +target-version = "py310" line-length = 80 diff --git a/requirements.txt b/requirements.txt index f181d679..0077723d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.8 +# This file is autogenerated by pip-compile with Python 3.10 # by the following command: # # pip-compile --all-extras pyproject.toml @@ -47,7 +47,7 @@ click-log==0.4.0 # via doxysphinx colorama==0.4.6 # via commitizen -commitizen==3.9.0 +commitizen==3.10.1 # via rocm-docs-core (pyproject.toml) cryptography==41.0.4 # via pyjwt @@ -63,7 +63,7 @@ docutils==0.19 # myst-parser # pydata-sphinx-theme # sphinx -doxysphinx==3.3.4 +doxysphinx==3.3.6 # via rocm-docs-core (pyproject.toml) fastjsonschema==2.18.1 # via rocm-docs-core (pyproject.toml) @@ -80,11 +80,7 @@ idna==3.4 imagesize==1.4.1 # via sphinx importlib-metadata==6.8.0 - # via - # commitizen - # sphinx -importlib-resources==5.12.0 ; python_version < "3.9" - # via rocm-docs-core (pyproject.toml) + # via commitizen isort==5.12.0 # via rocm-docs-core (pyproject.toml) jinja2==3.1.2 @@ -108,7 +104,7 @@ mdurl==0.1.2 # via markdown-it-py mpire==2.8.0 # via doxysphinx -mypy==1.5.1 +mypy==1.6.0 # via rocm-docs-core (pyproject.toml) mypy-extensions==1.0.0 # via @@ -133,9 +129,9 @@ platformdirs==3.11.0 # via # black # virtualenv -pre-commit==3.4.0 +pre-commit==3.5.0 # via rocm-docs-core (pyproject.toml) -prompt-toolkit==3.0.39 +prompt-toolkit==3.0.36 # via questionary pycparser==2.21 # via cffi @@ -161,8 +157,8 @@ pyparsing==3.1.1 # via doxysphinx pyproject-hooks==1.0.0 # via build -pytz==2023.3.post1 - # via babel +python-dateutil==2.8.2 + # via pygithub pyyaml==6.0.1 # via # commitizen @@ -170,7 +166,7 @@ pyyaml==6.0.1 # pre-commit # rocm-docs-core (pyproject.toml) # sphinx-external-toc -questionary==1.10.0 +questionary==2.0.1 # via commitizen requests==2.31.0 # via @@ -178,6 +174,8 @@ requests==2.31.0 # sphinx ruff==0.0.292 # via rocm-docs-core (pyproject.toml) +six==1.16.0 + # via python-dateutil smmap==5.0.1 # via gitdb snowballstemmer==2.2.0 @@ -195,6 +193,11 @@ sphinx==5.3.0 # sphinx-design # sphinx-external-toc # sphinx-notfound-page + # sphinxcontrib-applehelp + # sphinxcontrib-devhelp + # sphinxcontrib-htmlhelp + # sphinxcontrib-qthelp + # sphinxcontrib-serializinghtml sphinx-book-theme==1.0.1 # via rocm-docs-core (pyproject.toml) sphinx-copybutton==0.5.2 @@ -205,17 +208,17 @@ sphinx-external-toc==0.3.1 # via rocm-docs-core (pyproject.toml) sphinx-notfound-page==1.0.0 # via rocm-docs-core (pyproject.toml) -sphinxcontrib-applehelp==1.0.4 +sphinxcontrib-applehelp==1.0.7 # via sphinx -sphinxcontrib-devhelp==1.0.2 +sphinxcontrib-devhelp==1.0.5 # via sphinx -sphinxcontrib-htmlhelp==2.0.1 +sphinxcontrib-htmlhelp==2.0.4 # via sphinx sphinxcontrib-jsmath==1.0.1 # via sphinx -sphinxcontrib-qthelp==1.0.3 +sphinxcontrib-qthelp==1.0.6 # via sphinx -sphinxcontrib-serializinghtml==1.1.5 +sphinxcontrib-serializinghtml==1.1.9 # via sphinx termcolor==2.3.0 # via commitizen @@ -233,11 +236,13 @@ tqdm==4.66.1 typing-extensions==4.8.0 # via # black - # filelock # mypy # pydata-sphinx-theme + # pygithub urllib3==2.0.6 - # via requests + # via + # pygithub + # requests virtualenv==20.24.5 # via pre-commit wcwidth==0.2.8 @@ -247,9 +252,7 @@ wheel==0.41.2 wrapt==1.15.0 # via deprecated zipp==3.17.0 - # via - # importlib-metadata - # importlib-resources + # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: # pip diff --git a/src/rocm_docs/__init__.py b/src/rocm_docs/__init__.py index 89b72400..e2cc549d 100644 --- a/src/rocm_docs/__init__.py +++ b/src/rocm_docs/__init__.py @@ -4,13 +4,13 @@ that are using Read the Docs. """ -from typing import Dict, List, Optional, Union +from typing import TypeAlias import os from rocm_docs.core import setup -MaybePath = Union[str, os.PathLike, None] +MaybePath: TypeAlias = str | os.PathLike | None # Intentionally disabling the too-many-instance-attributes check in pylint @@ -33,17 +33,17 @@ class ROCmDocs: def __init__( self, project_name: str, - _: Optional[str] = None, + _: str | None = None, __: MaybePath = None, ) -> None: """Intialize ROCmDocs.""" self._project_name: str = project_name - self.extensions: List[str] = [] + self.extensions: list[str] = [] self.html_title: str self.html_theme: str - self.html_theme_options: Dict[str, Union[str, bool, List[str]]] = {} + self.html_theme_options: dict[str, str | (bool | list[str])] = {} self.doxygen_root: MaybePath = None - self.doxygen_project: Dict[str, Union[Optional[str], MaybePath]] = { + self.doxygen_project: dict[str, str | None | MaybePath] = { "name": None, "path": None, } @@ -59,7 +59,7 @@ def run_doxygen( self, doxygen_root: MaybePath = None, doxygen_path: MaybePath = None, - doxygen_file: Optional[str] = None, + doxygen_file: str | None = None, ) -> None: """Run doxygen as part of Sphinx by adding rocm_docs.doxygen.""" if "rocm_docs.doxygen" not in self.extensions: diff --git a/src/rocm_docs/core.py b/src/rocm_docs/core.py index a46be265..0697ce39 100644 --- a/src/rocm_docs/core.py +++ b/src/rocm_docs/core.py @@ -7,29 +7,23 @@ from __future__ import annotations -from typing import Any, Dict, Generic, List, Set, TypeVar, cast +from typing import Any, Generic, TypeVar, cast +import importlib.resources import inspect import os -import sys import urllib.parse from abc import ABC, abstractmethod from pathlib import Path import bs4 import git.repo -from pydata_sphinx_theme.utils import config_provided_by_user # type: ignore[import] +from pydata_sphinx_theme.utils import config_provided_by_user # type: ignore[import-untyped] from sphinx.application import Sphinx from sphinx.config import Config T = TypeVar("T") -# based on doxygen.py -if sys.version_info < (3, 9): - import importlib_resources -else: - import importlib.resources as importlib_resources - class _ConfigUpdater(Generic[T], ABC): def __init__(self, default: T) -> None: @@ -41,7 +35,7 @@ def __call__(self, key: str, app: Sphinx) -> None: pass -class _ConfigExtend(_ConfigUpdater[List[T]]): +class _ConfigExtend(_ConfigUpdater[list[T]]): def __call__(self, key: str, app: Sphinx) -> None: getattr(app.config, key).extend(self.default) @@ -52,12 +46,12 @@ def __call__(self, key: str, app: Sphinx) -> None: setattr(app.config, key, self.default) -class _ConfigUnion(_ConfigUpdater[Set[T]]): +class _ConfigUnion(_ConfigUpdater[set[T]]): def __call__(self, key: str, app: Sphinx) -> None: getattr(app.config, key).update(self.default) -class _ConfigMerge(_ConfigUpdater[Dict[str, Any]]): +class _ConfigMerge(_ConfigUpdater[dict[str, Any]]): def __call__(self, key: str, app: Sphinx) -> None: current_setting: dict[str, Any] = getattr(app.config, key) for item in self.default.items(): @@ -120,7 +114,7 @@ def _set_article_info(app: Sphinx, _: Config) -> None: return article_info = ( - importlib_resources.files("rocm_docs") + importlib.resources.files("rocm_docs") .joinpath("rocm_docs_theme/components/article-info.html") .read_text(encoding="utf-8") ) diff --git a/src/rocm_docs/doxygen.py b/src/rocm_docs/doxygen.py index 907bfb50..763f2c2c 100644 --- a/src/rocm_docs/doxygen.py +++ b/src/rocm_docs/doxygen.py @@ -2,8 +2,9 @@ from __future__ import annotations -from typing import Any, Dict, Union +from typing import Any, Union +import importlib.resources import importlib.util import os import shutil @@ -11,25 +12,17 @@ import sys from pathlib import Path -from pydata_sphinx_theme.utils import config_provided_by_user # type: ignore[import] +from pydata_sphinx_theme.utils import config_provided_by_user # type: ignore[import-untyped] from sphinx.application import Sphinx from sphinx.config import Config from sphinx.errors import ConfigError from rocm_docs import util -if sys.version_info < (3, 9): - # importlib.resources either doesn't exist or lacks the files() - # function, so use the PyPI version: - import importlib_resources -else: - # importlib.resources has files(), so use that: - import importlib.resources as importlib_resources - def _copy_files(app: Sphinx) -> None: """Insert additional files into workspace.""" - pkg = importlib_resources.files("rocm_docs") + pkg = importlib.resources.files("rocm_docs") Path(app.srcdir, "_doxygen").mkdir(exist_ok=True) util.copy_from_package( app, pkg / "data/_doxygen", "data/_doxygen", "_doxygen" @@ -194,7 +187,7 @@ def setup(app: Sphinx) -> dict[str, Any]: "path": Path(config.doxygen_root, "docBin", "xml"), }, rebuild="", - types=Dict[str, Union[None, str, "os.PathLike[Any]"]], + types=dict[str, Union[None, str, "os.PathLike[Any]"]], ) app.add_config_value("doxysphinx_enabled", False, rebuild="", types=bool) diff --git a/src/rocm_docs/formatting.py b/src/rocm_docs/formatting.py index 3895ac6e..7bac4d8e 100644 --- a/src/rocm_docs/formatting.py +++ b/src/rocm_docs/formatting.py @@ -2,9 +2,10 @@ from __future__ import annotations -from typing import Any, Generator, Iterable +from typing import Any import re +from collections.abc import Generator, Iterable from dataclasses import dataclass from pathlib import Path diff --git a/src/rocm_docs/projects.py b/src/rocm_docs/projects.py index 54e03821..0a752448 100644 --- a/src/rocm_docs/projects.py +++ b/src/rocm_docs/projects.py @@ -3,43 +3,35 @@ Remote loading of intersphinx_mapping from file, templating projects in toc.yml). """ -from typing import Any, Dict, List, Optional, Tuple, Union, cast +from typing import Any, Optional, TypeAlias, cast import functools +import importlib.resources import json import os import sys from dataclasses import dataclass from pathlib import Path -import fastjsonschema # type: ignore[import] +import fastjsonschema # type: ignore[import-untyped] import github import sphinx.util.logging import yaml -from pydata_sphinx_theme.utils import config_provided_by_user # type: ignore[import] +from pydata_sphinx_theme.utils import config_provided_by_user # type: ignore[import-untyped] from sphinx.application import Sphinx from sphinx.config import Config from rocm_docs import formatting, util -if sys.version_info < (3, 9): - # importlib.resources either doesn't exist or lacks the files() - # function, so use the PyPI version: - import importlib_resources - import importlib_resources.abc as importlib_abc +if sys.version_info < (3, 11): + import importlib.abc as importlib_abc else: - # importlib.resources has files(), so use that: - import importlib.resources as importlib_resources - - if sys.version_info < (3, 11): - import importlib.abc as importlib_abc - else: - import importlib.resources.abc as importlib_abc + import importlib.resources.abc as importlib_abc Traversable = importlib_abc.Traversable -Inventory = Union[str, None, Tuple[Union[str, None], ...]] -ProjectMapping = Tuple[str, Inventory] +Inventory: TypeAlias = str | None | tuple[str | None, ...] +ProjectMapping: TypeAlias = tuple[str, Inventory] DEFAULT_INTERSPHINX_REPOSITORY = "RadeonOpenCompute/rocm-docs-core" DEFAULT_INTERSPHINX_BRANCH = "develop" @@ -51,29 +43,29 @@ class InvalidMappingFileError(RuntimeError): """Mapping file has invalid format, or failed to validate.""" -ProjectItem = Union[str, None, List[Union[str, None]]] -ProjectEntry = Union[str, Dict[str, ProjectItem]] +ProjectItem: TypeAlias = str | list[str | None] | None +ProjectEntry: TypeAlias = str | dict[str, ProjectItem] @dataclass class _Project: target: str - inventory: List[Union[str, None]] + inventory: list[str | None] development_branch: str @staticmethod @functools.lru_cache - def yaml_schema() -> Dict[str, Any]: - base = importlib_resources.files("rocm_docs") / "data" + def yaml_schema() -> dict[str, Any]: + base = importlib.resources.files("rocm_docs") / "data" schema_file = base / "projects.schema.json" return cast( - Dict[str, Any], json.load(schema_file.open(encoding="utf-8")) + dict[str, Any], json.load(schema_file.open(encoding="utf-8")) ) @classmethod - def schema(cls) -> Dict[str, Any]: - return cast(Dict[str, Any], cls.yaml_schema()["$defs"]["project"]) + def schema(cls) -> dict[str, Any]: + return cast(dict[str, Any], cls.yaml_schema()["$defs"]["project"]) @classmethod def default_value(cls, prop: str) -> str: @@ -106,7 +98,7 @@ def get_static_version( cls, current_branch: str, current_project: Optional["_Project"], - ) -> Optional[str]: + ) -> str | None: """Returns a common static version if it exists. In some cases all remote projects will receive the same version, @@ -130,7 +122,7 @@ def get_static_version( return None - def evaluate(self, static_version: Optional[str]) -> None: + def evaluate(self, static_version: str | None) -> None: """Evaluate ${version} placeholders in the inventory and target values.""" version = ( static_version @@ -149,16 +141,14 @@ def mapping(self) -> ProjectMapping: return (self.target, tuple(self.inventory)) -def _create_projects( - project_yaml: Union[str, Traversable] -) -> Dict[str, _Project]: +def _create_projects(project_yaml: str | Traversable) -> dict[str, _Project]: contents = yaml.safe_load( project_yaml if isinstance(project_yaml, str) else project_yaml.open(encoding="utf-8") ) - data: Dict[str, Union[int, Dict[str, ProjectEntry]]] + data: dict[str, int | dict[str, ProjectEntry]] try: data = fastjsonschema.validate(_Project.yaml_schema(), contents) except fastjsonschema.exceptions.JsonSchemaValueException as err: @@ -174,8 +164,8 @@ def _create_projects( def _get_current_project( - projects: Dict[str, _Project], current_id: str -) -> Optional[_Project]: + projects: dict[str, _Project], current_id: str +) -> _Project | None: if current_id in projects: return projects[current_id] @@ -188,10 +178,10 @@ def _get_current_project( def _create_mapping( - projects: Dict[str, _Project], - current_project: Optional[_Project], + projects: dict[str, _Project], + current_project: _Project | None, current_branch: str, -) -> Dict[str, ProjectMapping]: +) -> dict[str, ProjectMapping]: static_version = _Project.get_static_version( current_branch, current_project ) @@ -230,11 +220,11 @@ def _fetch_projects( def _load_projects( remote_repository: str, remote_branch: str -) -> Dict[str, _Project]: +) -> dict[str, _Project]: projects_file_loc = "data/projects.yaml" def should_fetch_mappings( - remote_repository: Optional[str], remote_branch: Optional[str] + remote_repository: str | None, remote_branch: str | None ) -> bool: if not remote_repository: logger.info( @@ -255,7 +245,7 @@ def should_fetch_mappings( ) return True - projects: Optional[Dict[str, _Project]] = None + projects: dict[str, _Project] | None = None if should_fetch_mappings(remote_repository, remote_branch): try: remote_filepath = "src/rocm_docs/" + projects_file_loc @@ -274,15 +264,15 @@ def should_fetch_mappings( if projects is None: projects = _create_projects( - importlib_resources.files("rocm_docs") / projects_file_loc + importlib.resources.files("rocm_docs") / projects_file_loc ) return projects def _get_context( - repo_path: Path, mapping: Dict[str, ProjectMapping] -) -> Dict[str, Any]: + repo_path: Path, mapping: dict[str, ProjectMapping] +) -> dict[str, Any]: url, branch = util.get_branch(repo_path) return { "url": url, @@ -292,7 +282,7 @@ def _get_context( def _update_theme_configs( - app: Sphinx, current_project: Optional[_Project], current_branch: str + app: Sphinx, current_project: _Project | None, current_branch: str ) -> None: """Update configurations for use in theme.py""" latest_version = "5.7.1" @@ -329,7 +319,7 @@ def _update_config(app: Sphinx, _: Config) -> None: ) default = _create_mapping(projects, current_project, branch) - mapping: Dict[str, ProjectMapping] = app.config.intersphinx_mapping + mapping: dict[str, ProjectMapping] = app.config.intersphinx_mapping for key, value in default.items(): mapping.setdefault(key, value) @@ -349,12 +339,12 @@ def _update_config(app: Sphinx, _: Config) -> None: def _setup_projects_context( - app: Sphinx, _: str, __: str, context: Dict[str, Any], ___: Any + app: Sphinx, _: str, __: str, context: dict[str, Any], ___: Any ) -> None: context["projects"] = app.config.projects_context["projects"] -def setup(app: Sphinx) -> Dict[str, Any]: +def setup(app: Sphinx) -> dict[str, Any]: """Setup rocm_docs.projects as a sphinx extension.""" app.setup_extension("sphinx.ext.intersphinx") app.setup_extension("sphinx_external_toc") diff --git a/src/rocm_docs/theme.py b/src/rocm_docs/theme.py index debf6e48..51a916f7 100644 --- a/src/rocm_docs/theme.py +++ b/src/rocm_docs/theme.py @@ -1,11 +1,11 @@ """Module to use rocm-docs-core as a theme.""" -from typing import Any, Dict +from typing import Any from pathlib import Path import sphinx.util.logging -from pydata_sphinx_theme.utils import ( # type: ignore[import] +from pydata_sphinx_theme.utils import ( # type: ignore[import-untyped] config_provided_by_user, get_theme_options_dict, ) @@ -16,8 +16,8 @@ logger = sphinx.util.logging.getLogger(__name__) -def _update_repo_opts(srcdir: str, theme_opts: Dict[str, Any]) -> None: - default_branch_options: Dict[str, Any] = { +def _update_repo_opts(srcdir: str, theme_opts: dict[str, Any]) -> None: + default_branch_options: dict[str, Any] = { "use_edit_page_button": False, } try: @@ -37,7 +37,7 @@ def _update_repo_opts(srcdir: str, theme_opts: Dict[str, Any]) -> None: def _update_banner( - flavor: str, version_type: util.VersionType, theme_opts: Dict[str, Any] + flavor: str, version_type: util.VersionType, theme_opts: dict[str, Any] ) -> None: if flavor != "rocm": return @@ -98,7 +98,7 @@ def _update_theme_options(app: Sphinx) -> None: setattr(app.config, key, default) -def setup(app: Sphinx) -> Dict[str, Any]: +def setup(app: Sphinx) -> dict[str, Any]: """Set up the module as a Sphinx extension.""" app.add_js_file( "https://download.amd.com/js/analytics/analyticsinit.js", diff --git a/src/rocm_docs/util.py b/src/rocm_docs/util.py index 5f2fb6f1..15dc7205 100644 --- a/src/rocm_docs/util.py +++ b/src/rocm_docs/util.py @@ -18,13 +18,10 @@ from github.GithubException import UnknownObjectException from sphinx.application import Sphinx -if sys.version_info < (3, 9): - from importlib_resources.abc import Traversable +if sys.version_info < (3, 11): + from importlib.abc import Traversable else: - if sys.version_info < (3, 11): - from importlib.abc import Traversable - else: - from importlib.resources.abc import Traversable + from importlib.resources.abc import Traversable class VersionType(enum.Enum):