diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e4a62b..ea0e1c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,9 +23,6 @@ jobs: with: submodules: false coverage: codecov - libraries: | - apt: - - libopenjp2-7 envs: | - linux: py310 - linux: codestyle @@ -38,22 +35,10 @@ jobs: libraries: | apt: - libopenjp2-7 - brew: - - openjpeg + - graphviz envs: | - macos: py38 - windows: py39 - online: - needs: [test] - uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@main - with: - submodules: false - coverage: codecov - libraries: | - apt: - - libopenjp2-7 - envs: | - - linux: py39-online - linux: build_docs publish: # Build wheels when pushing to any branch except main diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 42b79ab..0a8342e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,26 +10,31 @@ repos: - id: autoflake args: ['--in-place', '--remove-all-unused-imports', '--remove-unused-variable'] exclude: ".*(.fits|.fts|.fit|.txt|tca.*|extern.*|.rst|.md|__init__.py|docs/conf.py)$" - - repo: https://github.com/psf/black - rev: 22.3.0 - hooks: - - id: black - exclude: ".*(.fits|.fts|.fit|.txt|.csv)$" - - repo: https://github.com/timothycrosley/isort - rev: 5.10.1 - hooks: - - id: isort - exclude: ".*(.fits|.fts|.fit|.txt|.csv)$" - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.2.0 - hooks: - - id: check-ast - - id: check-case-conflict - - id: trailing-whitespace - exclude: ".*(.fits|.fts|.fit|.txt|.csv)$" - - id: mixed-line-ending - exclude: ".*(.fits|.fts|.fit|.txt|.csv)$" - - id: end-of-file-fixer - exclude: ".*(.fits|.fts|.fit|.txt|.csv)$" - - id: check-yaml - - id: debug-statements + - repo: https://github.com/psf/black + rev: 22.6.0 + hooks: + - id: black + exclude: ".*(.fits|.fts|.fit|.txt|.csv)$" + - repo: https://github.com/timothycrosley/isort + rev: 5.10.1 + hooks: + - id: isort + exclude: ".*(.fits|.fts|.fit|.txt|.csv)$" + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: check-ast + - id: check-case-conflict + - id: trailing-whitespace + exclude: ".*(.fits|.fts|.fit|.txt|.csv)$" + - id: mixed-line-ending + exclude: ".*(.fits|.fts|.fit|.txt|.csv)$" + - id: end-of-file-fixer + exclude: ".*(.fits|.fts|.fit|.txt|.csv)$" + - id: check-yaml + - id: debug-statements + - repo: https://github.com/pre-commit/mirrors-mypy + rev: 'v0.961' + hooks: + - id: mypy + additional_dependencies: [types-requests==2.28.0] diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 0aeaeee..042ed4c 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -3,6 +3,9 @@ build: os: ubuntu-20.04 tools: python: "3.9" + apt_packages: + - libopenjp2-7 + - graphviz sphinx: builder: html diff --git a/docs/api.rst b/docs/api.rst index 1b63e0a..1cb72c9 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3,5 +3,10 @@ API Reference ************* .. automodapi:: hvpy - :inherited-members: - :include-all-objects: + +.. automodapi:: hvpy.core + +.. automodapi:: hvpy.io + +.. automodapi:: hvpy.parameters + :allowed-package-names: hvpy.api_groups diff --git a/docs/conf.py b/docs/conf.py index 9dbc17b..939661a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -14,12 +14,12 @@ # -- General configuration --------------------------------------------------- extensions = [ "sphinx.ext.autodoc", + "sphinx.ext.napoleon", "sphinx.ext.intersphinx", "sphinx.ext.todo", "sphinx.ext.coverage", "sphinx.ext.inheritance_diagram", "sphinx.ext.viewcode", - "sphinx.ext.napoleon", "sphinx.ext.doctest", "sphinx.ext.mathjax", "sphinx_automodapi.automodapi", @@ -31,6 +31,14 @@ # Enable nitpicky mode, which forces links to be non-broken nitpicky = True +# This is not used. See docs/nitpick-exceptions file for the actual listing. +nitpick_ignore = [] +for line in open("nitpick-exceptions"): + if line.strip() == "" or line.startswith("#"): + continue + dtype, target = line.split(None, 1) + target = target.strip() + nitpick_ignore.append((dtype, target)) # -- Options for intersphinx extension ----------------------------------------- # Example configuration for intersphinx: refer to the Python standard library. diff --git a/docs/nitpick-exceptions b/docs/nitpick-exceptions new file mode 100644 index 0000000..b39e5e2 --- /dev/null +++ b/docs/nitpick-exceptions @@ -0,0 +1,5 @@ +py:class optional +py:class pydantic.main.BaseModel +py:class pydantic.utils.Representation +py:class rtype +py:obj typing.Literal[, , ] diff --git a/hvpy/api_groups/jpeg2000/get_jp2_image.py b/hvpy/api_groups/jpeg2000/get_jp2_image.py index 23fe191..da7e210 100644 --- a/hvpy/api_groups/jpeg2000/get_jp2_image.py +++ b/hvpy/api_groups/jpeg2000/get_jp2_image.py @@ -7,13 +7,11 @@ class getJP2ImageInputParameters(HvpyParameters): """ - Handles the input parameters of the `getJP2Image API. - - `__. + Handles the input parameters of the getJP2Image API. Attributes ---------- - date : datetime + date : datetime.datetime Desired date/time of the JP2 image. sourceId : int Unique image datasource identifier. @@ -21,6 +19,10 @@ class getJP2ImageInputParameters(HvpyParameters): Returns a JPIP URI instead of the binary data of the image if set to `True`, defaults to `False`. json : bool, optional Returns the JSON if set to `True`, defaults to `False`. + + References + ---------- + * ``__ """ date: datetime @@ -29,19 +31,19 @@ class getJP2ImageInputParameters(HvpyParameters): Json: bool = Field(False, alias="json") @validator("date") - def convert_date_to_isoformat(cls, v): + def convert_date_to_isoformat(cls, v) -> str: """ Converts the date from a datetime object to a string in the ISO format. """ return v.isoformat() + "Z" - def get_output_type(self): + def get_output_type(self) -> OutputType: """ Returns the output type of the API call. """ if self.Json and self.jpip: - return OutputType.Json + return OutputType.JSON elif not self.Json and self.jpip: - return OutputType.String + return OutputType.STRING else: - return OutputType.Raw + return OutputType.RAW diff --git a/hvpy/core.py b/hvpy/core.py index 6e9a840..770ff41 100644 --- a/hvpy/core.py +++ b/hvpy/core.py @@ -1,9 +1,13 @@ +from typing import Any, Dict, Union + import requests from hvpy.io import HvpyParameters, OutputType +__all__ = ["execute_api_call", "parse_response"] + -def parse_response(response: requests.Response, output_parameters: OutputType): +def parse_response(response: requests.Response, output_type: OutputType) -> Union[bytes, str, Dict[str, Any]]: """ Parses the response from the API call based on the output type. @@ -11,37 +15,38 @@ def parse_response(response: requests.Response, output_parameters: OutputType): ---------- response : requests.Response The response from the API call. - output_parameters : OutputType + output_type : hvpy.io.OutputType The output type. Returns ------- - binary | str | json + Union[bytes, str, Dict[str, Any]] The parsed response. """ - - if output_parameters == OutputType.Raw: + if output_type == OutputType.RAW: return response.content - elif output_parameters == OutputType.String: + elif output_type == OutputType.STRING: return response.content.decode("utf-8") - elif output_parameters == OutputType.Json: + elif output_type == OutputType.JSON: return response.json() + else: + raise ValueError(f"Unknown output type: {output_type}") -def execute_api_call(input_parameters: HvpyParameters): +def execute_api_call(input_parameters: HvpyParameters) -> Union[bytes, str, Dict[str, Any]]: """ Executes the API call and returns a parsed response. Parameters ---------- - input_parameters : HvpyParameters + input_parameters : hvpy.io.HvpyParameters The input parameters. Returns ------- - Parsed response from the API. + Union[bytes, str, Dict[str, Any]] + Parsed response from the API. """ - response = requests.get(input_parameters.url, params=input_parameters.dict()) response.raise_for_status() return parse_response(response, input_parameters.get_output_type()) diff --git a/hvpy/io.py b/hvpy/io.py index 9362e4a..e87af04 100644 --- a/hvpy/io.py +++ b/hvpy/io.py @@ -1,31 +1,57 @@ from enum import Enum, auto +from typing import Any, Dict -from pydantic import BaseModel +from pydantic.main import BaseModel + +__all__ = ["HvpyParameters", "OutputType"] BASE_URL = "https://api.helioviewer.org/v2/" +class OutputType(Enum): + RAW = auto() + """ + Defines the RAW output type. + """ + STRING = auto() + """ + Defines the STRING output type. + """ + JSON = auto() + """ + Defines the JSON output type. + """ + + class HvpyParameters(BaseModel): - def dict(self): - """ - Pydantic doesn't allow using lowercase 'json' as a field, so we - override it. - """ + """ + Base model for all Helioviewer API parameters. + """ + + def dict(self) -> Dict[str, Any]: # type: ignore + # pydantic doesn't allow using lowercase 'json' as a field d = super().dict() if "Json" in d: d["json"] = d["Json"] del d["Json"] return d - def get_output_type(self): - return OutputType.Raw + def get_output_type(self) -> OutputType: + """ + Works out the return type of the API call. + + This by default is RAW, subclasses should redefine this. + + Returns + ------- + hvpy.io.OutputType + Output type based on the model. + """ + return OutputType.RAW @property - def url(self): + def url(self) -> str: + """ + Final API endpoint URL. + """ return BASE_URL + self.__class__.__name__[:-15] + "/" - - -class OutputType(Enum): - Raw = auto() - String = auto() - Json = auto() diff --git a/hvpy/parameters.py b/hvpy/parameters.py new file mode 100644 index 0000000..6c9bbdf --- /dev/null +++ b/hvpy/parameters.py @@ -0,0 +1,3 @@ +from hvpy.api_groups.jpeg2000.get_jp2_image import getJP2ImageInputParameters + +__all__ = ["getJP2ImageInputParameters"] diff --git a/hvpy/tests/test_core.py b/hvpy/tests/test_core.py new file mode 100644 index 0000000..79cf6e4 --- /dev/null +++ b/hvpy/tests/test_core.py @@ -0,0 +1,8 @@ +import pytest + +from hvpy.core import parse_response + + +def test_wrong_input_type(): + with pytest.raises(ValueError, match="Unknown output type: 42"): + parse_response(None, 42) diff --git a/hvpy/tests/test_io.py b/hvpy/tests/test_io.py index 2c295e6..deb0ec5 100644 --- a/hvpy/tests/test_io.py +++ b/hvpy/tests/test_io.py @@ -3,7 +3,7 @@ def test_default_get_output_type_is_raw(): params = HvpyParameters() - assert params.get_output_type() == OutputType.Raw + assert params.get_output_type() == OutputType.RAW def test_url_property(): diff --git a/pyproject.toml b/pyproject.toml index 56653b3..02d9d97 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,3 +26,11 @@ exclude = ''' )/ ) ''' + +[tool.mypy] +python_version = "3.10" +ignore_missing_imports = true +exclude = [ + 'setup.py', + 'docs/conf.py', +] diff --git a/setup.cfg b/setup.cfg index 6fe75b7..3c615f7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,7 +3,7 @@ name = hvpy author = The Helioviewer Project author_email = contact@helioviewer.org license = BSD 2-Clause -license_file = licenses/LICENSE.rst +license_files = licenses/LICENSE.rst url = https://helioviewer.org/ description = Helioviewer Python API Wrapper long_description = file: README.rst