Skip to content

Commit a7d4a8f

Browse files
add type checks (#694)
* pin dependencies * add doctest support * add mypy * adjust lint and format scripts * add types * bump min python 3.7 -> 3.8 * update type_check flags
1 parent dea7d2d commit a7d4a8f

File tree

18 files changed

+259
-183
lines changed

18 files changed

+259
-183
lines changed

.github/workflows/quality-checks.yml

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ on:
1313

1414
jobs:
1515

16-
check_quality:
16+
lint:
1717
runs-on: ubuntu-20.04
1818
strategy:
1919
matrix:
20-
python-version: [3.7, 3.8, 3.9, '3.10', 3.11]
20+
python-version: [3.8, 3.9, '3.10', 3.11]
2121
steps:
2222
- uses: actions/checkout@v3
2323
- name: Set up Python ${{ matrix.python-version }}
@@ -31,7 +31,42 @@ jobs:
3131
- name: Lint
3232
run: |
3333
scripts/lint
34-
34+
35+
type_check:
36+
runs-on: ubuntu-20.04
37+
strategy:
38+
matrix:
39+
python-version: [3.8, 3.9, '3.10', 3.11]
40+
steps:
41+
- uses: actions/checkout@v3
42+
- name: Set up Python ${{ matrix.python-version }}
43+
uses: actions/setup-python@v4
44+
with:
45+
python-version: ${{ matrix.python-version }}
46+
cache: 'pip'
47+
- name: Install Test dependencies
48+
run: |
49+
pip install -e .[test]
50+
- name: Type Check
51+
run: |
52+
scripts/type_check
53+
54+
test:
55+
runs-on: ubuntu-20.04
56+
strategy:
57+
matrix:
58+
python-version: [3.8, 3.9, '3.10', 3.11]
59+
steps:
60+
- uses: actions/checkout@v3
61+
- name: Set up Python ${{ matrix.python-version }}
62+
uses: actions/setup-python@v4
63+
with:
64+
python-version: ${{ matrix.python-version }}
65+
cache: 'pip'
66+
- name: Install Test dependencies
67+
run: |
68+
pip install -e .[test]
69+
3570
- name: Test
3671
run: |
3772
scripts/test

pyproject.toml

Lines changed: 46 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[build-system]
22
requires = [
3-
"setuptools>=68.0.0,<69",
4-
"wheel>=0.40.0,<0.41",
3+
"setuptools==68.0.0",
4+
"wheel==0.41.1",
55
]
66
build-backend = "setuptools.build_meta"
77

@@ -10,7 +10,7 @@ version="1.16.1"
1010
name = "icloudpd"
1111
description = "icloudpd is a command-line tool to download photos and videos from iCloud."
1212
readme = "README_PYPI.md"
13-
requires-python = ">=3.7,<3.12"
13+
requires-python = ">=3.8,<3.12"
1414
keywords = ["icloud", "photo"]
1515
license = {file="LICENSE.md"}
1616
authors=[
@@ -23,41 +23,49 @@ classifiers = [
2323
"License :: OSI Approved :: MIT License",
2424
]
2525
dependencies = [
26-
"requests>=2.28.2,<3",
27-
"schema>=0.7.5,<0.8",
28-
"click>=8.1.3,<9",
29-
"python-dateutil>=2.8.2,<3",
30-
"tqdm>=4.64.1,<5",
31-
"piexif>=1.1.3,<2",
32-
"urllib3>=1.26.14,<2",
26+
"requests==2.31.0",
27+
"schema==0.7.5",
28+
"click==8.1.6",
29+
"python-dateutil==2.8.2",
30+
"tqdm==4.66.0",
31+
"piexif==1.1.3",
32+
"urllib3==1.26.16",
3333
# from pyicloud_ipd
34-
"six>=1.16.0,<2",
35-
"tzlocal>=4.2,<5",
36-
"pytz>=2022.7.1,<2023",
37-
"certifi>=2022.12.7,<2023",
38-
"future>=0.18.3,<0.19",
39-
"keyring>=23.13.1,<24",
40-
"keyrings-alt>=4.2.0,<5"
34+
"six==1.16.0",
35+
"tzlocal==4.3.1",
36+
"pytz==2022.7.1",
37+
"certifi==2022.12.7",
38+
"future==0.18.3",
39+
"keyring==23.13.1",
40+
"keyrings-alt==4.2.0"
4141
]
4242

4343
[project.optional-dependencies]
4444
dev = [
45-
"twine>=4.0.0,<5",
46-
"pyinstaller>=5.7.0,<6",
47-
"wheel>=0.40.0,<0.41",
48-
"auditwheel>=5.4.0,<5.5"
45+
"twine==4.0.2",
46+
"pyinstaller==5.13.0",
47+
"wheel==0.41.1",
48+
"auditwheel==5.4.0"
4949
]
5050
test = [
51-
"pytest>=7.2.1,<8",
52-
"mock>=5.0.1,<6",
53-
"freezegun>=1.2.2,<2",
54-
"vcrpy>=4.2.1,<5",
55-
"pytest-cov>=4.0.0,<5",
56-
"pylint>=2.15.10,<3",
57-
"coveralls>=3.3.1,<4",
58-
"autopep8>=2.0.1,<3",
59-
"pytest-timeout>=2.1.0,<3",
60-
"pytest-xdist>=3.1.0,<4"
51+
"pytest==7.4.0",
52+
"mock==5.1.0",
53+
"freezegun==1.2.2",
54+
"vcrpy==4.4.0",
55+
"pytest-cov==4.1.0",
56+
"pylint==2.17.5",
57+
"coveralls==3.3.1",
58+
"autopep8==2.0.2",
59+
"pytest-timeout==2.1.0",
60+
"pytest-xdist==3.3.1",
61+
"mypy==1.5.0",
62+
"types-pytz==2022.7.1.2",
63+
"types-tzlocal==4.3.0.0",
64+
"types-requests==2.31.0.2",
65+
"types-six==1.16.0",
66+
"types-urllib3==1.26.16",
67+
"types-tqdm==4.66.0.1",
68+
"types-mock==5.1.0.1"
6169
]
6270

6371
[project.urls]
@@ -72,12 +80,18 @@ log_format = "%(levelname)-8s %(message)s"
7280
log_date_format = "%Y-%m-%d %H:%M:%S"
7381
timeout = 300
7482
testpaths = [
75-
"tests"
83+
"tests",
84+
"src" # needed for doctests
7685
]
7786
pythonpath = [
7887
"src"
7988
]
89+
addopts = "--doctest-modules"
8090

8191
[tool.setuptools.packages.find]
8292
where = ["src"] # list of folders that contain the packages (["."] by default)
8393
exclude = ["starters"]
94+
95+
[[tool.mypy.overrides]]
96+
module = ['piexif.*', 'future.*', 'vcr.*']
97+
ignore_missing_imports = true

scripts/format

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
#!/bin/bash
22
set -euo pipefail
3-
autopep8 -r --in-place --aggressive --aggressive icloudpd/
3+
echo "Running autopep8..."
4+
autopep8 -r --in-place --aggressive --aggressive src/ --exclude src/pyicloud_ipd

scripts/lint

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
#!/bin/bash
22
set -euo pipefail
33
echo "Running pylint..."
4-
python3 -m pylint src/icloudpd
4+
python3 -m pylint src --ignore-paths src/icloud,src/pyicloud_ipd,src/starters

scripts/run_all_checks

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ ROOT_DIR="$(realpath $(dirname "$0")/..)"
1111

1212
$CURRENT_DIR/format
1313
$CURRENT_DIR/lint
14+
$CURRENT_DIR/type_check
1415
$CURRENT_DIR/test

scripts/type_check

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
echo "Running mypy..."
4+
python3 -m mypy src tests
5+
# too strict now: --disallow-any-generics --disallow-untyped-defs --strict-equality --disallow-untyped-calls --warn-return-any

src/icloudpd/authentication.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Handles username/password authentication and two-step authentication"""
22

3+
import logging
34
import sys
45
import click
56
import pyicloud_ipd
@@ -12,15 +13,15 @@ class TwoStepAuthRequiredError(Exception):
1213
"""
1314

1415

15-
def authenticator(logger, domain):
16+
def authenticator(logger: logging.Logger, domain: str):
1617
"""Wraping authentication with domain context"""
1718
def authenticate_(
1819
username,
1920
password,
2021
cookie_directory=None,
2122
raise_error_on_2sa=False,
2223
client_id=None,
23-
):
24+
) -> pyicloud_ipd.PyiCloudService:
2425
"""Authenticate with iCloud username and password"""
2526
logger.debug("Authenticating...")
2627
while True:
@@ -49,7 +50,7 @@ def authenticate_(
4950
return authenticate_
5051

5152

52-
def request_2sa(icloud, logger):
53+
def request_2sa(icloud: pyicloud_ipd.PyiCloudService, logger: logging.Logger):
5354
"""Request two-step authentication. Prompts for SMS or device"""
5455
devices = icloud.trusted_devices
5556
devices_count = len(devices)

src/icloudpd/autodelete.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,32 @@
11
"""
22
Delete any files found in "Recently Deleted"
33
"""
4+
import logging
45
import os
56
from tzlocal import get_localzone
67
from icloudpd.paths import local_download_path
8+
import pyicloud_ipd
79

810

9-
def delete_file(logger, path) -> bool:
11+
def delete_file(logger: logging.Logger, path: str) -> bool:
1012
""" Actual deletion of files """
1113
os.remove(path)
1214
logger.info("Deleted %s", path)
1315
return True
1416

1517

16-
def delete_file_dry_run(logger, path) -> bool:
18+
def delete_file_dry_run(logger: logging.Logger, path: str) -> bool:
1719
""" Dry run deletion of files """
1820
logger.info("[DRY RUN] Would delete %s", path)
1921
return True
2022

2123

2224
def autodelete_photos(
23-
logger,
24-
dry_run,
25+
logger: logging.Logger,
26+
dry_run: bool,
2527
library_object,
26-
folder_structure,
27-
directory):
28+
folder_structure: str,
29+
directory: str):
2830
"""
2931
Scans the "Recently Deleted" folder and deletes any matching files
3032
from the download directory.
@@ -46,7 +48,7 @@ def autodelete_photos(
4648
date_path = folder_structure.format(created_date)
4749
download_dir = os.path.join(directory, date_path)
4850

49-
for size in [None, "original", "medium", "thumb"]:
51+
for size in ["small", "original", "medium", "thumb"]:
5052
path = os.path.normpath(
5153
local_download_path(
5254
media, size, download_dir))

0 commit comments

Comments
 (0)