Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mypy to CI job and type hints for one class. #404

Merged
merged 29 commits into from
Feb 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2f916de
Added mypy to config
eliotwrobson Mar 11, 2024
da49e8d
Mypy check is passing
eliotwrobson Mar 11, 2024
8721442
Adding hitns
eliotwrobson Mar 11, 2024
85bdcbc
Function annotation
eliotwrobson Mar 11, 2024
fc638b6
format
eliotwrobson Mar 11, 2024
ab683aa
Other hints
eliotwrobson Mar 11, 2024
bea0f4b
Added ignores
eliotwrobson Mar 11, 2024
82084c6
Name fix
eliotwrobson Mar 11, 2024
1f18ec8
dependency
eliotwrobson Mar 11, 2024
318aad1
typo fix
eliotwrobson Mar 11, 2024
6e90081
Merge branch 'main' into main
eliotwrobson Jan 25, 2025
ea4a375
Remove Ts
eliotwrobson Jan 25, 2025
29a9dbf
Change typing
eliotwrobson Jan 25, 2025
09874ed
Update requirements-style.txt
eliotwrobson Jan 25, 2025
524a365
Change import style
eliotwrobson Jan 25, 2025
80bcb4f
Add some more type hints
eliotwrobson Jan 25, 2025
8a4ea62
Update core.py
eliotwrobson Jan 25, 2025
d12ec2a
Update core.py
eliotwrobson Jan 25, 2025
0f85eb0
Update utils.py
eliotwrobson Jan 25, 2025
cd30d81
Move new type classes to their own typing module
santisoler Feb 18, 2025
7d9faed
Rename "Actions" for "Action"
santisoler Feb 18, 2025
1f6df40
Add typing submodule to API Reference in docs
santisoler Feb 18, 2025
9e72015
Rename FilePath and FilePathInput types
santisoler Feb 18, 2025
1595a9e
Minor improvements to docstrings
santisoler Feb 18, 2025
17906dc
Fix import order in pooch/utils.py
santisoler Feb 18, 2025
a033335
Fix pylint complain: no need for elif after return
santisoler Feb 18, 2025
b4b6b6a
Add extra requirements for type checks to environment.yml
santisoler Feb 18, 2025
89c3363
Use list instead of the deprecated List
santisoler Feb 24, 2025
ec9b746
Merge branch 'main' into main
santisoler Feb 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/workflows/style.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,25 @@ jobs:

- name: Check code style
run: make check-style lint

types:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.10"

- name: Install requirements
run: python -m pip install -r env/requirements-style.txt

- name: List installed packages
run: python -m pip freeze

- name: Check code style
run: make check-types
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ format:
black $(CHECK_STYLE)
burocrata --extension=py $(CHECK_STYLE)

check: check-format check-style
check: check-format check-style check-types

check-format:
black --check $(CHECK_STYLE)
Expand All @@ -43,6 +43,9 @@ check-format:
check-style:
flake8 $(CHECK_STYLE)

check-types:
mypy $(CHECK_STYLE)

lint:
pylint --jobs=0 $(LINT_FILES)

Expand Down
17 changes: 17 additions & 0 deletions doc/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,20 @@ Miscellaneous
:toctree: generated/

pooch.test

Typing
------

Custom classes for type annotations.
This module provides additional `PEP 484 <https://peps.python.org/pep-0484/>`_
type aliases used in ``pooch``'s codebase.

.. autosummary::
:toctree: generated/

pooch.typing.Action
pooch.typing.Downloader
pooch.typing.PathType
pooch.typing.PathInputType
pooch.typing.ParsedURL
pooch.typing.Processor
4 changes: 2 additions & 2 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
}

# Autosummary pages will be generated by sphinx-autogen instead of sphinx-build
autosummary_generate = []
autosummary_generate: list = []

# Otherwise, the Return parameter list looks different from the Parameters list
napoleon_use_rtype = False
Expand Down Expand Up @@ -77,7 +77,7 @@
html_static_path = ["_static"]
# CSS files are relative to the static path
html_css_files = ["style.css"]
html_extra_path = []
html_extra_path: list = []
html_show_sourcelink = False
html_show_sphinx = True
html_show_copyright = True
Expand Down
7 changes: 6 additions & 1 deletion env/requirements-style.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Style checks
# Style and type checks
black
flake8
pylint>=2.4
pathspec
burocrata
types-requests
types-tqdm
types-paramiko
mypy
pytest
6 changes: 5 additions & 1 deletion environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,14 @@ dependencies:
- sphinx==7.2.*
- sphinx-book-theme==1.1.*
- sphinx-design==0.5.*
# Style
# Style and types
- pathspec
- black>=20.8b1
- flake8
- pylint>=2.4
- mypy
- types-requests
- types-tqdm
- types-paramiko
- pip:
- burocrata
2 changes: 1 addition & 1 deletion pooch/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from .processors import Unzip, Untar, Decompress

# This file is generated automatically by setuptools_scm
from . import _version
from . import _version # type: ignore


# Add a "v" to the version number
Expand Down
103 changes: 57 additions & 46 deletions pooch/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
"""
The main Pooch class and a factory function for it.
"""

import os
import time
import contextlib
from pathlib import Path
import shlex
import shutil
from typing import Union, Optional, Any


from .hashes import hash_matches, file_hash
Expand All @@ -26,17 +28,18 @@
unique_file_name,
)
from .downloaders import DOIDownloader, choose_downloader, doi_to_repository
from .typing import PathType, PathInputType, Processor, Downloader, Action


def retrieve(
url,
known_hash,
fname=None,
path=None,
processor=None,
downloader=None,
progressbar=False,
):
url: str,
known_hash: Optional[str] = None,
fname: Optional[str] = None,
path: Optional[PathType] = None,
processor: Optional[Processor] = None,
downloader: Optional[Downloader] = None,
progressbar: bool = False,
) -> str:
"""
Download and cache a single file locally.

Expand Down Expand Up @@ -254,15 +257,15 @@ def retrieve(


def create(
path,
base_url,
version=None,
version_dev="master",
env=None,
registry=None,
urls=None,
retry_if_failed=0,
allow_updates=True,
path: PathInputType,
base_url: str,
version: Optional[str] = None,
version_dev: str = "master",
env: Optional[str] = None,
registry: Optional[dict] = None,
urls: Optional[dict] = None,
retry_if_failed: int = 0,
allow_updates: Union[bool, str] = True,
):
"""
Create a :class:`~pooch.Pooch` with sensible defaults to fetch data files.
Expand Down Expand Up @@ -479,13 +482,13 @@ class Pooch:

def __init__(
self,
path,
base_url,
registry=None,
urls=None,
retry_if_failed=0,
allow_updates=True,
):
path: PathType,
base_url: str,
registry: Optional[dict[str, str]] = None,
urls: Optional[dict[str, str]] = None,
retry_if_failed: int = 0,
allow_updates: bool = True,
) -> None:
self.path = path
self.base_url = base_url
if registry is None:
Expand All @@ -498,16 +501,22 @@ def __init__(
self.allow_updates = allow_updates

@property
def abspath(self):
def abspath(self) -> Path:
"Absolute path to the local storage"
return Path(os.path.abspath(os.path.expanduser(str(self.path))))

@property
def registry_files(self):
def registry_files(self) -> list[str]:
"List of file names on the registry"
return list(self.registry)

def fetch(self, fname, processor=None, downloader=None, progressbar=False):
def fetch(
self,
fname: str,
processor: Optional[Processor] = None,
downloader: Optional[Downloader] = None,
progressbar: bool = False,
) -> str:
"""
Get the absolute path to a file in the local storage.

Expand Down Expand Up @@ -600,15 +609,15 @@ def fetch(self, fname, processor=None, downloader=None, progressbar=False):

return str(full_path)

def _assert_file_in_registry(self, fname):
def _assert_file_in_registry(self, fname: str) -> None:
"""
Check if a file is in the registry and raise :class:`ValueError` if
it's not.
"""
if fname not in self.registry:
raise ValueError(f"File '{fname}' is not in the registry.")

def get_url(self, fname):
def get_url(self, fname: str) -> str:
"""
Get the full URL to download a file in the registry.

Expand All @@ -622,7 +631,7 @@ def get_url(self, fname):
self._assert_file_in_registry(fname)
return self.urls.get(fname, "".join([self.base_url, fname]))

def load_registry(self, fname):
def load_registry(self, fname: PathType) -> None:
"""
Load entries from a file and add them to the registry.

Expand All @@ -644,7 +653,7 @@ def load_registry(self, fname):
with contextlib.ExitStack() as stack:
if hasattr(fname, "read"):
# It's a file object
fin = fname
fin: Any = fname
else:
# It's a file path
fin = stack.enter_context(open(fname, encoding="utf-8"))
Expand Down Expand Up @@ -673,7 +682,7 @@ def load_registry(self, fname):
self.urls[file_name] = file_url
self.registry[file_name] = file_checksum.lower()

def load_registry_from_doi(self):
def load_registry_from_doi(self) -> None:
"""
Populate the registry using the data repository API

Expand Down Expand Up @@ -703,7 +712,7 @@ def load_registry_from_doi(self):
# Call registry population for this repository
return repository.populate_registry(self)

def is_available(self, fname, downloader=None):
def is_available(self, fname: str, downloader: Optional[Downloader] = None):
"""
Check availability of a remote file without downloading it.

Expand Down Expand Up @@ -740,7 +749,7 @@ def is_available(self, fname, downloader=None):
return available


def download_action(path, known_hash):
def download_action(path: Path, known_hash: Optional[str]) -> tuple[Action, str]:
"""
Determine the action that is needed to get the file on disk.

Expand All @@ -767,18 +776,20 @@ def download_action(path, known_hash):

"""
if not path.exists():
action = "download"
verb = "Downloading"
elif not hash_matches(str(path), known_hash):
action = "update"
verb = "Updating"
else:
action = "fetch"
verb = "Fetching"
return action, verb


def stream_download(url, fname, known_hash, downloader, pooch=None, retry_if_failed=0):
return "download", "Downloading"
if not hash_matches(str(path), known_hash):
return "update", "Updating"
return "fetch", "Fetching"


def stream_download(
url: str,
fname: Path,
known_hash: Optional[str],
downloader: Downloader,
pooch: Optional[Pooch] = None,
retry_if_failed: int = 0,
) -> None:
"""
Stream the file and check that its hash matches the known one.

Expand Down
6 changes: 4 additions & 2 deletions pooch/downloaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,17 @@

from .utils import parse_url

# Mypy doesn't like assigning None like this.
# Can just use a guard variable
try:
from tqdm import tqdm
except ImportError:
tqdm = None
tqdm = None # type: ignore

try:
import paramiko
except ImportError:
paramiko = None
paramiko = None # type: ignore


# Set the default timeout in seconds so it can be configured in a pinch for the
Expand Down
7 changes: 5 additions & 2 deletions pooch/tests/test_downloaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,18 @@

import pytest

# Mypy doesn't like assigning None like this.
# Can just use a guard variable

try:
import tqdm
except ImportError:
tqdm = None
tqdm = None # type: ignore

try:
import paramiko
except ImportError:
paramiko = None
paramiko = None # type: ignore

from .. import Pooch
from ..downloaders import (
Expand Down
Loading