diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint_and_mypy.yml
similarity index 77%
rename from .github/workflows/pylint.yml
rename to .github/workflows/pylint_and_mypy.yml
index 66e6edb..b08e64a 100644
--- a/.github/workflows/pylint.yml
+++ b/.github/workflows/pylint_and_mypy.yml
@@ -1,4 +1,4 @@
-name: Pylint
+name: Pylint and MyPy
on: [push]
@@ -17,8 +17,11 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
- pip install pylint
pip install -r requirements.txt
+ pip install pylint mypy types-requests types-pytz
- name: Analysing the code with pylint
run: |
pylint $(git ls-files '*.py')
+ - name: Type checking with mypy
+ run: |
+ mypy util generator
diff --git a/README.md b/README.md
index bd2657b..8032996 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
[简体中文](README_zh-CN.md) | English
- [](https://github.com/WCY-dt/my-github-2024/actions/workflows/deploy.yml) [](https://2024.ch3nyang.top)
+ [](https://github.com/WCY-dt/my-github-2024/actions/workflows/pylint.yml) [](https://github.com/WCY-dt/my-github-2024/actions/workflows/deploy.yml) [](https://2024.ch3nyang.top)
👉 Try it now: https://2024.ch3nyang.top
diff --git a/README_zh-CN.md b/README_zh-CN.md
index b638f04..b43da0e 100644
--- a/README_zh-CN.md
+++ b/README_zh-CN.md
@@ -5,7 +5,7 @@
[English](README.md) | 简体中文
- [](https://github.com/WCY-dt/my-github-2024/actions/workflows/deploy.yml) [](https://2024.ch3nyang.top)
+ [](https://github.com/WCY-dt/my-github-2024/actions/workflows/pylint_and_mypy.yml) [](https://github.com/WCY-dt/my-github-2024/actions/workflows/deploy.yml) [](https://2024.ch3nyang.top)
👉 立即体验: https://2024.ch3nyang.top
diff --git a/generator/fetch.py b/generator/fetch.py
index 7e728f0..f3adb73 100644
--- a/generator/fetch.py
+++ b/generator/fetch.py
@@ -9,6 +9,7 @@
import logging
from log.logging_config import setup_logging
+from util import Github
setup_logging()
@@ -43,7 +44,7 @@
}
-def fetch_github(github: object, year: int, skip_fetch: bool = False) -> tuple:
+def fetch_github(github: Github, year: int, skip_fetch: bool = False) -> tuple:
"""
Fetches and processes GitHub data for a given year.
diff --git a/util/__init__.py b/util/__init__.py
index 79a6b8b..26e375f 100644
--- a/util/__init__.py
+++ b/util/__init__.py
@@ -10,6 +10,7 @@
import logging
from datetime import timedelta
from datetime import timezone as dt_timezone
+from typing import Any
import pytz
@@ -27,7 +28,7 @@ class Github:
Attributes:
access_token (str): The Github access token.
username (str): The Github username.
- timezone (pytz.timezone): The timezone.
+ timezone (pytz.BaseTzInfo): The timezone.
data (dict | list | None): The fetched data.
Methods:
@@ -54,6 +55,8 @@ class Github:
result: Get the result data.
"""
+ data: dict[str, Any] | None
+
def __init__(
self, access_token: str, username: str, timezone: str = "Asia/Shanghai"
) -> None:
@@ -67,10 +70,10 @@ def __init__(
"""
self.access_token: str = access_token
self.username: str = username
- self.timezone: pytz.timezone = self._parse_timezone(timezone)
+ self.timezone: pytz.BaseTzInfo = self._parse_timezone(timezone)
self.data = None
- def _parse_timezone(self, tz_str: str) -> pytz.timezone:
+ def _parse_timezone(self, tz_str: str) -> pytz.BaseTzInfo:
"""
Parse the timezone string to a pytz timezone object.
@@ -78,7 +81,7 @@ def _parse_timezone(self, tz_str: str) -> pytz.timezone:
tz_str (str): The timezone string.
Returns:
- pytz.timezone: The pytz timezone object.
+ pytz.BaseTzInfo: The pytz timezone object.
"""
try:
return pytz.timezone(tz_str)
@@ -86,7 +89,7 @@ def _parse_timezone(self, tz_str: str) -> pytz.timezone:
if tz_str.startswith("+") or tz_str.startswith("-"):
hours_offset = int(tz_str)
timezone = dt_timezone(timedelta(hours=hours_offset))
- return pytz.timezone(timezone)
+ return pytz.timezone(str(timezone))
raise e
@@ -186,7 +189,8 @@ def filter_json(self, key: dict) -> "Github":
Returns:
Github: The Github object with the filtered data.
"""
- self.data = gh_filter.json_key(self.data, key)
+ if self.data is not None:
+ self.data = gh_filter.json_key(self.data, key)
return self
def count_all(self) -> "Github":
@@ -262,7 +266,7 @@ def write_to_file(self, filename: str) -> "Github":
f.write(json.dumps(self.result, indent=4))
return self
- def _get_result_structure(self, data: dict | list | None) -> dict:
+ def _get_result_structure(self, data: dict | list | None) -> dict | list | str:
"""
Get the result structure of the data.
@@ -270,7 +274,7 @@ def _get_result_structure(self, data: dict | list | None) -> dict:
data (dict | list): The data to get the result structure from.
Returns:
- dict: The result structure of the data.
+ dict | list | str: The result structure of the data.
"""
if isinstance(data, list):
return [self._get_result_structure(data[0])]
@@ -299,7 +303,7 @@ def result(self) -> dict | list | None:
return self.data
@result.setter
- def result(self, value: dict | list | None) -> None:
+ def result(self, value: dict[str, Any] | None) -> None:
"""
Set the result data.
diff --git a/util/gh_count.py b/util/gh_count.py
index 4e6f332..1089c54 100644
--- a/util/gh_count.py
+++ b/util/gh_count.py
@@ -102,9 +102,9 @@ def commits_types_number(data: dict) -> dict:
Returns:
dict: The data containing the number of commits of each type.
"""
- commits_types = {}
+ commits_types: dict[str, int] = {}
for repo in data["repos_details"]:
- repo_commits_types = {}
+ repo_commits_types: dict[str, int] = {}
for commit in repo["commits_details"]:
commit_type = _get_commit_type(commit["message"])
@@ -127,7 +127,7 @@ def commits_types_number(data: dict) -> dict:
def _count_monthly(items: list, key: str) -> dict:
- monthly_count = {}
+ monthly_count: dict[int, int] = {}
for item in items:
month = datetime.fromisoformat(item[key]).month
if month in monthly_count:
@@ -172,7 +172,7 @@ def commits_monthly_number(data: dict) -> dict:
def _count_weekly(items: list, key: str) -> dict:
- weekly_count = {}
+ weekly_count: dict[int, int] = {}
for item in items:
weekday = datetime.fromisoformat(item[key]).weekday()
if weekday in weekly_count:
@@ -182,7 +182,7 @@ def _count_weekly(items: list, key: str) -> dict:
return weekly_count
-def _fill_weekly_list(weekly_count: dict) -> dict:
+def _fill_weekly_list(weekly_count: dict) -> list:
weekly_list = []
for i in range(7):
if i in weekly_count:
@@ -202,7 +202,7 @@ def commits_weekdaily_number(data: dict) -> dict:
Returns:
dict: The data containing the number of commits in each weekday.
"""
- commits_weekly = {}
+ commits_weekly: dict[int, int] = {}
for repo in data["repos_details"]:
repo_commits_weekly = _count_weekly(repo["commits_details"], "created_time")
@@ -222,7 +222,7 @@ def commits_weekdaily_number(data: dict) -> dict:
def _count_daily(items: list, key: str) -> dict:
- daily_count = {}
+ daily_count: dict[str, int] = {}
for item in items:
date = datetime.fromisoformat(item[key]).date().isoformat()
if date in daily_count:
@@ -233,7 +233,7 @@ def _count_daily(items: list, key: str) -> dict:
def _fill_daily_list(daily_count: dict) -> dict:
- daily_list = {}
+ daily_list: dict[int, list[int]] = {}
for year in range(2000, datetime.now().year + 2):
days_in_year = (
366 if year % 4 == 0 and year % 100 != 0 or year % 400 == 0 else 365
@@ -280,7 +280,7 @@ def commits_daily_number(data: dict) -> dict:
def _count_hourly(items: list, key: str) -> dict:
- hourly_count = {}
+ hourly_count: dict[int, int] = {}
for item in items:
hour = datetime.fromisoformat(item[key]).hour
if hour in hourly_count:
@@ -310,7 +310,7 @@ def commits_hourly_number(data: dict) -> dict:
Returns:
dict: The data containing the number of commits in each hour.
"""
- commits_hourly = {}
+ commits_hourly: dict[int, int] = {}
for repo in data["repos_details"]:
repo_commits_hourly = _count_hourly(repo["commits_details"], "created_time")
@@ -355,8 +355,8 @@ def repos_languages_number(data: dict) -> dict:
Returns:
dict: The data containing the number of repositories using each language.
"""
- languages_num = {}
- repos_languages_count = {}
+ languages_num: dict[str, int] = {}
+ repos_languages_count: dict[str, int] = {}
for repo in data["repos_details"]:
for key, value in repo["languages_num"].items():
if key in languages_num:
diff --git a/util/gh_fetch.py b/util/gh_fetch.py
index cb784e0..19ec585 100644
--- a/util/gh_fetch.py
+++ b/util/gh_fetch.py
@@ -2,35 +2,35 @@
This module fetches the Github user information.
Functions:
- get_github_info(username: str, token: str, timezone: pytz.timezone, year: int) -> dict:
+ get_github_info(username: str, token: str, timezone: pytz.BaseTzInfo, year: int) -> dict:
Get the Github user information.
_get_response(url: str, token: str, per_page: int = 100, accept: str =
"application/vnd.github.v3+json", timeout: int = 10) -> requests.Response:
Get the response from the Github API.
- _parse_time(time_str: str, timezone: pytz.timezone) -> str:
+ _parse_time(time_str: str, timezone: pytz.BaseTzInfo) -> str:
Parse the time string to the timezone.
_paginate(func: callable) -> callable:
Paginate the data from the Github API.
- _get_user_issues(username: str, url: str, token: str, timezone: pytz.timezone,
+ _get_user_issues(username: str, url: str, token: str, timezone: pytz.BaseTzInfo,
year: int) -> list:
Get the user issues.
- _get_user_prs(username: str, url: str, token: str, timezone: pytz.timezone,
+ _get_user_prs(username: str, url: str, token: str, timezone: pytz.BaseTzInfo,
year: int) -> list:
Get the user pull requests.
- _get_user_repos(username: str, url: str, token: str, timezone: pytz.timezone,
+ _get_user_repos(username: str, url: str, token: str, timezone: pytz.BaseTzInfo,
year: int) -> list:
Get the user repositories.
_get_user_repo_languages(url: str, token: str) -> dict:
Get the user repository languages.
- _get_user_commits(username: str, url: str, token: str, timezone: pytz.timezone,
+ _get_user_commits(username: str, url: str, token: str, timezone: pytz.BaseTzInfo,
year: int) -> list:
Get the user commits.
"""
@@ -39,6 +39,7 @@
import time
import urllib.parse
from datetime import datetime
+from typing import Callable, Any
import pytz
import requests
@@ -49,13 +50,13 @@
setup_logging()
-def _parse_time(time_str: str, timezone: pytz.timezone) -> str:
+def _parse_time(time_str: str, timezone: pytz.BaseTzInfo) -> str:
"""
Parse the time string to the timezone.
Args:
time_str (str): The time string.
- timezone (pytz.timezone): The timezone.
+ timezone (pytz.BaseTzInfo): The timezone.
Returns:
str: The time string in the timezone.
@@ -79,19 +80,19 @@ def _parse_time(time_str: str, timezone: pytz.timezone) -> str:
return result
-def _paginate(func: callable) -> callable:
+def _paginate(func: Callable) -> Callable:
"""
Paginate the data from the Github API.
Args:
- func (callable): The function to paginate.
+ func (Callable): The function to paginate.
Returns:
- callable: The wrapper function to paginate the data.
+ Callable: The wrapper function to paginate the data.
"""
def wrapper(
- username: str, url: str, token: str, timezone: pytz.timezone, year: int
+ username: str, url: str | None, token: str, timezone: pytz.BaseTzInfo, year: int
) -> list:
"""
Wrapper function to paginate the data.
@@ -100,7 +101,7 @@ def wrapper(
username (str): The Github username.
url (str): The Github API URL to fetch the data.
token (str): The Github access token.
- timezone (pytz.timezone): The timezone.
+ timezone (pytz.BaseTzInfo): The timezone.
year (int): The year to fetch the data.
Returns:
@@ -121,7 +122,7 @@ def wrapper(
links = res.headers.get("Link")
if links and cont:
- next_url = None
+ next_url: str | None = None
for link in links.split(","):
if 'rel="next"' in link:
next_url = link[link.find("<") + 1 : link.find(">")]
@@ -164,7 +165,7 @@ def _get_response(
parsed_url = urllib.parse.urlparse(url)
query_params = urllib.parse.parse_qs(parsed_url.query)
- query_params["per_page"] = str(per_page)
+ query_params["per_page"] = [str(per_page)]
new_query_string = urllib.parse.urlencode(query_params, doseq=True)
url = urllib.parse.urlunparse(parsed_url._replace(query=new_query_string))
@@ -179,7 +180,10 @@ def _get_response(
response.raise_for_status()
except Exception as e:
logging.error("Failed to fetch data from %s: %s", url, e)
- logging.error("Response: %s", response.text)
+ if response is not None:
+ logging.error("Response: %s", response.text)
+ else:
+ logging.error("Response is None")
raise e
else:
return response
@@ -188,7 +192,7 @@ def _get_response(
def get_github_info(
- username: str, token: str, timezone: pytz.timezone, year: int
+ username: str, token: str, timezone: pytz.BaseTzInfo, year: int
) -> dict:
"""
Get the Github user information.
@@ -196,7 +200,7 @@ def get_github_info(
Args:
username (str): The Github username.
token (str): The Github access token.
- timezone (pytz.timezone): The timezone.
+ timezone (pytz.BaseTzInfo): The timezone.
year (int): The year to fetch the data.
Returns:
@@ -246,8 +250,8 @@ def get_github_info(
@_paginate
def _get_user_issues(
- username: str, url: str, token: str, timezone: pytz.timezone, year: int
-) -> list:
+ username: str, url: str, token: str, timezone: pytz.BaseTzInfo, year: int
+) -> tuple[list, requests.Response, bool]:
"""
Get the user issues.
@@ -255,21 +259,22 @@ def _get_user_issues(
username (str): The Github username.
url (str): The Github API URL.
token (str): The Github access token.
- timezone (pytz.timezone): The timezone.
+ timezone (pytz.BaseTzInfo): The timezone.
year (int): The year to fetch the data.
Returns:
- list: The user issues.
+ tuple[list, requests.Response, bool]:
+ The user issues, response, and continue flag.
"""
_ = username # Avoid pylint error
response = _get_response(url, token)
if response.status_code == 409:
- return [], response
+ return [], response, False
issues = response.json().get("items")
- issues_details = []
+ issues_details: list[dict[str, Any]] = []
for issue in issues:
created_time = _parse_time(issue.get("created_at"), timezone)
@@ -291,8 +296,8 @@ def _get_user_issues(
@_paginate
def _get_user_prs(
- username: str, url: str, token: str, timezone: pytz.timezone, year: int
-) -> list:
+ username: str, url: str, token: str, timezone: pytz.BaseTzInfo, year: int
+) -> tuple[list, requests.Response, bool]:
"""
Get the user pull requests.
@@ -300,20 +305,21 @@ def _get_user_prs(
username (str): The Github username.
url (str): The Github API URL.
token (str): The Github access token.
- timezone (pytz.timezone): The timezone.
+ timezone (pytz.BaseTzInfo): The timezone.
year (int): The year to fetch the data.
Returns:
- list: The user pull requests.
+ tuple[list, requests.Response, bool]:
+ The user pull requests, response, and continue flag.
"""
_ = username # Avoid pylint error
response = _get_response(url, token)
if response.status_code == 409:
- return [], response
+ return [], response, False
prs = response.json().get("items")
- prs_details = []
+ prs_details: list[dict[str, Any]] = []
for pr in prs:
created_time = _parse_time(pr.get("created_at"), timezone)
@@ -344,8 +350,8 @@ def _get_user_prs(
@_paginate
def _get_user_repos(
- username: str, url: str, token: str, timezone: pytz.timezone, year: int
-) -> list:
+ username: str, url: str, token: str, timezone: pytz.BaseTzInfo, year: int
+) -> tuple[list, requests.Response, bool]:
"""
Get the user repositories.
@@ -353,18 +359,19 @@ def _get_user_repos(
username (str): The Github username.
url (str): The Github API URL.
token (str): The Github access token.
- timezone (pytz.timezone): The timezone.
+ timezone (pytz.BaseTzInfo): The timezone.
year (int): The year to fetch the data.
Returns:
- list: The user repositories.
+ tuple[list, requests.Response, bool]:
+ The user repositories, response, and continue flag.
"""
response = _get_response(url, token)
if response.status_code == 409:
- return [], response
+ return [], response, False
repos = response.json()
- repos_details = []
+ repos_details: list[dict[str, Any]] = []
for repo in repos:
try:
@@ -422,8 +429,8 @@ def _get_user_repo_languages(url: str, token: str) -> dict:
@_paginate
def _get_user_commits(
- username: str, url: str, token: str, timezone: pytz.timezone, year: int
-) -> list:
+ username: str, url: str, token: str, timezone: pytz.BaseTzInfo, year: int
+) -> tuple[list, requests.Response, bool]:
"""
Get the user commits.
@@ -431,19 +438,20 @@ def _get_user_commits(
username (str): The Github username.
url (str): The Github API URL.
token (str): The Github access token.
- timezone (pytz.timezone): The timezone.
+ timezone (pytz.BaseTzInfo): The timezone.
year (int): The year to fetch the data.
Returns:
- list: The user commits.
+ tuple[list, requests.Response, bool]:
+ The user commits, response, and continue flag.
"""
response = _get_response(url, token)
if response.status_code == 409:
- return [], response
+ return [], response, False
response.raise_for_status()
commits = response.json()
- commits_details = []
+ commits_details: list[dict[str, Any]] = []
for commit in commits:
committer = None