diff --git a/pdm.lock b/pdm.lock index 5b50d08..53d19af 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "all", "dev"] strategy = ["cross_platform"] lock_version = "4.4.1" -content_hash = "sha256:828f8051de2c2a04cede8130abf86764edb38f0428cd1d52dd797bff43452afe" +content_hash = "sha256:17fdf74bf4b2980c66c106cebcb936921e671e8adaeb2f2e95d95f255704aa38" [[package]] name = "aiocache" @@ -80,6 +80,19 @@ files = [ {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, ] +[[package]] +name = "class-doc" +version = "0.2.6" +requires_python = ">=3.6,<4.0" +summary = "Extract attributes docstrings defined in various ways" +dependencies = [ + "more-itertools>=5.0.0", +] +files = [ + {file = "class-doc-0.2.6.tar.gz", hash = "sha256:f5e036ed9b7f6de528affdd9f038851910b342d4c1c1252983a55ff080b530e0"}, + {file = "class_doc-0.2.6-py3-none-any.whl", hash = "sha256:e6f2cea2dfbe93f76dee25de13d70dc0d2269698e8b849f751d98dc894c52ea5"}, +] + [[package]] name = "idna" version = "3.7" @@ -90,6 +103,16 @@ files = [ {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] +[[package]] +name = "more-itertools" +version = "10.3.0" +requires_python = ">=3.8" +summary = "More routines for operating on iterables, beyond itertools" +files = [ + {file = "more-itertools-10.3.0.tar.gz", hash = "sha256:e5d93ef411224fbcef366a6e8ddc4c5781bc6359d43412a65dd5964e46111463"}, + {file = "more_itertools-10.3.0-py3-none-any.whl", hash = "sha256:ea6a02e24a9161e51faad17a8782b92a0df82c12c1c8886fec7f0c3fa1a1b320"}, +] + [[package]] name = "multidict" version = "6.0.5" diff --git a/pyproject.toml b/pyproject.toml index e450218..603b1b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,30 +44,10 @@ Issues = "https://git.00dani.me/00dani/mpd-now-playable/issues" [project.scripts] mpd-now-playable = 'mpd_now_playable.cli:main' -[tool.pdm.scripts] -start = {call = 'mpd_now_playable.cli:main'} -lint = 'ruff check src/mpd_now_playable' -typecheck = 'mypy -p mpd_now_playable' -check = {composite = ['lint', 'typecheck'], keep_going = true} - -[tool.pdm.version] -source = "scm" -write_to = 'mpd_now_playable/__version__.py' -write_template = "__version__ = '{}'" - [build-system] requires = ["pdm-backend"] build-backend = "pdm.backend" -[tool.pdm.build] -excludes = ["**/.mypy_cache"] - -[tool.pdm.dev-dependencies] -dev = [ - "mypy>=1.7.1", - "ruff>=0.1.6", -] - [tool.mypy] mypy_path = 'stubs' @@ -101,3 +81,26 @@ mypy-init-return = true [tool.ruff.format] # I prefer tabs for accessibility reasons. indent-style = "tab" + +[tool.pdm.scripts] +start = {call = 'mpd_now_playable.cli:main'} +lint = 'ruff check src/mpd_now_playable' +typecheck = 'mypy -p mpd_now_playable' +check = {composite = ['lint', 'typecheck'], keep_going = true} + +[tool.pdm.version] +source = "scm" +write_to = 'mpd_now_playable/__version__.py' +write_template = "__version__ = '{}'" + +[tool.pdm.build] +excludes = ["**/.mypy_cache"] + +[tool.pdm.dev-dependencies] +dev = [ + "mypy>=1.7.1", + "ruff>=0.1.6", + "class-doc>=0.2.6", +] + + diff --git a/src/mpd_now_playable/config/config-v1.json b/src/mpd_now_playable/config/config-v1.json index a09d89d..c5d3fc3 100644 --- a/src/mpd_now_playable/config/config-v1.json +++ b/src/mpd_now_playable/config/config-v1.json @@ -13,7 +13,12 @@ "$ref": "#/definitions/URL" }, "cache": { - "$ref": "#/definitions/URL" + "allOf": [ + { + "$ref": "#/definitions/URL" + } + ], + "description": "A URL describing a cache service for mpd-now-playable to use. Supported protocols are memory://, redis://, and memcached://." }, "mpd": { "additionalProperties": false, @@ -24,14 +29,17 @@ "properties": { "host": { "default": "127.0.0.1", + "description": "The hostname or IP address of your MPD server. If you're running MPD on your local machine, you don't need to configure this.", "format": "hostname", "type": "string" }, "password": { + "description": "The password required to connect to your MPD instance, if you need one.", "type": "string" }, "port": { "default": 6600, + "description": "The port on which to connect to MPD. Unless you're managing multiple MPD servers on one machine for some reason, you probably haven't changed this from the default port, 6600.", "maximum": 65535, "minimum": 1, "type": "integer" diff --git a/src/mpd_now_playable/config/model.py b/src/mpd_now_playable/config/model.py index 7336486..88d197f 100644 --- a/src/mpd_now_playable/config/model.py +++ b/src/mpd_now_playable/config/model.py @@ -11,8 +11,14 @@ __all__ = ("MpdConfig", "Config") @dataclass(frozen=True) class MpdConfig: + #: The password required to connect to your MPD instance, if you need one. password: Optional[str] = optional() + #: The hostname or IP address of your MPD server. If you're running MPD + #: on your local machine, you don't need to configure this. host: Host = Host("127.0.0.1") + #: The port on which to connect to MPD. Unless you're managing multiple MPD + #: servers on one machine for some reason, you probably haven't changed this + #: from the default port, 6600. port: Port = Port(6600) @@ -22,5 +28,8 @@ class Config: default=URL("https://cdn.00dani.me/m/schemata/mpd-now-playable/config-v1.json"), metadata=alias("$schema"), ) + + #: A URL describing a cache service for mpd-now-playable to use. Supported + #: protocols are memory://, redis://, and memcached://. cache: Optional[URL] = optional() mpd: MpdConfig = field(default_factory=MpdConfig) diff --git a/src/mpd_now_playable/config/schema.py b/src/mpd_now_playable/config/schema.py index 314e925..307271b 100644 --- a/src/mpd_now_playable/config/schema.py +++ b/src/mpd_now_playable/config/schema.py @@ -3,13 +3,30 @@ from pathlib import Path from pprint import pp from typing import Any, Mapping +from apischema import schema, settings from apischema.json_schema import JsonSchemaVersion, deserialization_schema +from apischema.schemas import Schema +from class_doc import extract_docs_from_cls_obj from .model import Config +def field_base_schema(tp: type, name: str, alias: str) -> Schema | None: + desc_lines = extract_docs_from_cls_obj(tp).get(name, []) + if desc_lines: + print((tp, name, alias)) + return schema(description=" ".join(desc_lines)) + return None + + +settings.base_schema.field = field_base_schema + + def generate() -> Mapping[str, Any]: - return deserialization_schema(Config, version=JsonSchemaVersion.DRAFT_7) + return deserialization_schema( + Config, + version=JsonSchemaVersion.DRAFT_7, + ) def write() -> None: diff --git a/stubs/class_doc/__init__.pyi b/stubs/class_doc/__init__.pyi new file mode 100644 index 0000000..42aa3d2 --- /dev/null +++ b/stubs/class_doc/__init__.pyi @@ -0,0 +1 @@ +def extract_docs_from_cls_obj(cls: type) -> dict[str, list[str]]: ...