Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ repos:
- id: pyright
- repo: local
hooks:
# Remove this once ty has proper pre-commit support
# https://github.com/astral-sh/ty/issues/269
- id: ty
name: ty
entry: uv run ty check
language: system
types: [python]
require_serial: true
pass_filenames: false
- id: mypy
name: mypy
entry: uv run --no-active mypy
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ dev = [
]
mypy = [
"mypy>=1.15.0",
"ty>=0.0.11",
"types-icalendar>=6.1.3.20250403",
"types-python-dateutil>=2.9.0.20241206",
]
Expand Down
14 changes: 13 additions & 1 deletion scripts/checks/pypi_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,23 @@
"""

import argparse
import platform
import sys
from pathlib import Path

import tomllib
from packaging.version import InvalidVersion, parse

if sys.version_info >= (3, 11):
import tomllib
else:
msg = (
"This script relies on the standard-library 'tomllib' module, which is only "
f"available starting with Python 3.11 (current: {platform.python_version()}). "
"Please run this script with Python 3.11 or newer (for example via a Python "
"3.11+ virtual environment or by configuring your tooling to use Python 3.11+)."
)
raise ImportError(msg)


def parse_args() -> argparse.Namespace:
"""Parse command line arguments.
Expand Down
2 changes: 1 addition & 1 deletion scripts/generate_v1_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def main() -> None:

if confirm("Do you want the token to be copied to the clipboard?", default=True):
# https://github.com/asweigart/pyperclip/issues/210
import pyperclip # type: ignore[import-untyped] # noqa: PLC0415
import pyperclip # type: ignore[import-untyped] # noqa: PLC0415 # ty: ignore[unused-ignore-comment]

pyperclip.copy(str(token_value))
logger.info("Token copied to clipboard.")
Expand Down
2 changes: 1 addition & 1 deletion src/pyticktick/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def emit(self, record: logging.LogRecord) -> None: # noqa: D102
# Find caller from where originated the logged message.
frame, depth = sys._getframe(6), 6 # noqa: SLF001
while frame and frame.f_code.co_filename == logging.__file__:
frame = frame.f_back # type: ignore[assignment]
frame = frame.f_back # type: ignore[assignment] # ty: ignore[unused-ignore-comment]
depth += 1

logger.opt(depth=depth, exception=record.exc_info).log(
Expand Down
4 changes: 3 additions & 1 deletion src/pyticktick/pydantic.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,7 @@ def update_model_config(model: type[BaseModel], **config_kwargs: Any) -> None:
for field in model.__pydantic_fields__.values():
_check_field_for_submodel(field.annotation, **config_kwargs)

model.model_config.update(ConfigDict(**config_kwargs)) # type: ignore[typeddict-item]
model.model_config.update(
ConfigDict(**config_kwargs) # type: ignore[typeddict-item] # ty: ignore[unused-ignore-comment]
)
model.model_rebuild(force=True)
2 changes: 1 addition & 1 deletion tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ def client() -> Client:
)
if not (env_file_path := Path(__file__).parents[2].joinpath(".env")).exists():
return Client()
return Client(_env_file=env_file_path) # pyright: ignore[reportCallIssue] # https://github.com/pydantic/pydantic/issues/3072
return Client(_env_file=env_file_path) # pyright: ignore[reportCallIssue] # ty: ignore[unknown-argument] # https://github.com/pydantic/pydantic/issues/3072
32 changes: 16 additions & 16 deletions tests/integration/v2/test_batch.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ def test_get_batch_v2( # noqa: PLR0912, PLR0915
assert data["title"] == t.title
assert data["project_id"] == t.project_id
if "tags" in data:
assert set(data["tags"]) == set(t.tags)
assert set(data["tags"]) == set(t.tags) # ty: ignore[invalid-argument-type]
if "desc" in data:
assert data["desc"] == t.desc
if "content" in data:
Expand Down Expand Up @@ -360,34 +360,34 @@ def test_get_batch_v2( # noqa: PLR0912, PLR0915
if "kind" in data:
assert data["kind"] == t.kind
if "items" in data:
assert len(data["items"]) == len(t.items)
for item_data in data["items"]:
assert len(data["items"]) == len(t.items) # ty: ignore[invalid-argument-type]
for item_data in data["items"]: # ty: ignore[not-iterable]
i = None
assert any(item_data["id"] == i.id for i in t.items)
assert any(item_data["id"] == i.id for i in t.items) # ty: ignore[invalid-argument-type]
for i in t.items:
if i.id == item_data["id"]:
if i.id == item_data["id"]: # ty: ignore[invalid-argument-type]
break
assert isinstance(i, ItemV2)
assert item_data["id"] == i.id
assert item_data["title"] == i.title
assert item_data["id"] == i.id # ty: ignore[invalid-argument-type]
assert item_data["title"] == i.title # ty: ignore[invalid-argument-type]
if "is_all_day" in item_data and data["is_all_day"] is None:
assert item_data["is_all_day"] == i.is_all_day
assert item_data["is_all_day"] == i.is_all_day # ty: ignore[invalid-argument-type]
if "time_zone" in item_data and data["time_zone"] is None:
assert item_data["time_zone"] == i.time_zone
assert item_data["time_zone"] == i.time_zone # ty: ignore[invalid-argument-type]
if "status" in item_data:
assert item_data["status"] == i.status
assert item_data["status"] == i.status # ty: ignore[invalid-argument-type]
if "reminders" in data:
assert isinstance(t.reminders, list)
assert len(data["reminders"]) == len(t.reminders)
for reminder_data in data["reminders"]:
assert len(data["reminders"]) == len(t.reminders) # ty: ignore[invalid-argument-type]
for reminder_data in data["reminders"]: # ty: ignore[not-iterable]
r = None
assert any(reminder_data["id"] == r.id for r in t.reminders)
assert any(reminder_data["id"] == r.id for r in t.reminders) # ty: ignore[invalid-argument-type]
for r in t.reminders:
if r.id == reminder_data["id"]:
if r.id == reminder_data["id"]: # ty: ignore[invalid-argument-type]
break
assert isinstance(r, TaskReminderV2)
assert reminder_data["id"] == r.id
assert reminder_data["trigger"] == r.trigger
assert reminder_data["id"] == r.id # ty: ignore[invalid-argument-type]
assert reminder_data["trigger"] == r.trigger # ty: ignore[invalid-argument-type]

if t.reminder is not None:
assert any(t.reminder == r.trigger for r in t.reminders)
Expand Down
40 changes: 20 additions & 20 deletions tests/integration/v2/test_closed.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from datetime import UTC, datetime
from datetime import datetime, timezone

import pytest
from pydantic import TypeAdapter
Expand Down Expand Up @@ -166,7 +166,7 @@ def test_get_project_all_completed_v2(generate_object_id, delete_projects, clien
assert data["title"] == t.title
assert data["project_id"] == t.project_id
if "tags" in data:
assert set(data["tags"]) == set(t.tags)
assert set(data["tags"]) == set(t.tags) # ty: ignore[invalid-argument-type]
if "desc" in data:
assert data["desc"] == t.desc
if "content" in data:
Expand Down Expand Up @@ -198,34 +198,34 @@ def test_get_project_all_completed_v2(generate_object_id, delete_projects, clien
if "kind" in data:
assert data["kind"] == t.kind
if "items" in data:
assert len(data["items"]) == len(t.items)
assert len(data["items"]) == len(t.items) # ty: ignore[invalid-argument-type]
i = None
for item_data in data["items"]:
assert any(item_data["id"] == i.id for i in t.items)
for item_data in data["items"]: # ty: ignore[not-iterable]
assert any(item_data["id"] == i.id for i in t.items) # ty: ignore[invalid-argument-type]
for i in t.items:
if i.id == item_data["id"]:
if i.id == item_data["id"]: # ty: ignore[invalid-argument-type]
break
assert isinstance(i, ItemV2)
assert item_data["id"] == i.id
assert item_data["title"] == i.title
assert item_data["id"] == i.id # ty: ignore[invalid-argument-type]
assert item_data["title"] == i.title # ty: ignore[invalid-argument-type]
if "is_all_day" in item_data and data["is_all_day"] is None:
assert item_data["is_all_day"] == i.is_all_day
assert item_data["is_all_day"] == i.is_all_day # ty: ignore[invalid-argument-type]
if "time_zone" in item_data and data["time_zone"] is None:
assert item_data["time_zone"] == i.time_zone
assert item_data["time_zone"] == i.time_zone # ty: ignore[invalid-argument-type]
if "status" in item_data:
assert item_data["status"] == i.status
assert item_data["status"] == i.status # ty: ignore[invalid-argument-type]
if "reminders" in data:
assert isinstance(t.reminders, list)
assert len(data["reminders"]) == len(t.reminders)
for reminder_data in data["reminders"]:
assert len(data["reminders"]) == len(t.reminders) # ty: ignore[invalid-argument-type]
for reminder_data in data["reminders"]: # ty: ignore[not-iterable]
r = None
assert any(reminder_data["id"] == r.id for r in t.reminders)
assert any(reminder_data["id"] == r.id for r in t.reminders) # ty: ignore[invalid-argument-type]
for r in t.reminders:
if r.id == reminder_data["id"]:
if r.id == reminder_data["id"]: # ty: ignore[invalid-argument-type]
break
assert isinstance(r, TaskReminderV2)
assert reminder_data["id"] == r.id
assert reminder_data["trigger"] == r.trigger
assert reminder_data["id"] == r.id # ty: ignore[invalid-argument-type]
assert reminder_data["trigger"] == r.trigger # ty: ignore[invalid-argument-type]

if t.reminder is not None:
assert any(t.reminder == r.trigger for r in t.reminders)
Expand Down Expand Up @@ -262,21 +262,21 @@ def test_get_project_all_wont_do_v2(generate_object_id, delete_projects, client)
"title": "test_get_project_all_wont_do_v2_TASK_A",
"project_id": project_data["id"],
"status": -1,
"completed_time": datetime.now(tz=UTC).isoformat(),
"completed_time": datetime.now(tz=timezone.utc).isoformat(),
},
{
"id": generate_object_id(),
"title": "test_get_project_all_wont_do_v2_TASK_B",
"project_id": project_data["id"],
"status": -1,
"completed_time": datetime.now(tz=UTC).isoformat(),
"completed_time": datetime.now(tz=timezone.utc).isoformat(),
},
{
"id": generate_object_id(),
"title": "test_get_project_all_wont_do_v2_TASK_C",
"project_id": project_data["id"],
"status": -1,
"completed_time": datetime.now(tz=UTC).isoformat(),
"completed_time": datetime.now(tz=timezone.utc).isoformat(),
},
]

Expand Down
22 changes: 11 additions & 11 deletions tests/integration/v2/test_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,14 +163,14 @@ def test_create_task_v2(generate_object_id, delete_projects, client, get_batch):

expected_task_0_items = [
{
"id": item["id"],
"title": item["title"],
"sortOrder": item["sort_order"],
"isAllDay": item["is_all_day"],
"timeZone": item["time_zone"],
"status": item["status"],
"id": item["id"], # ty: ignore[invalid-argument-type]
"title": item["title"], # ty: ignore[invalid-argument-type]
"sortOrder": item["sort_order"], # ty: ignore[invalid-argument-type]
"isAllDay": item["is_all_day"], # ty: ignore[invalid-argument-type]
"timeZone": item["time_zone"], # ty: ignore[invalid-argument-type]
"status": item["status"], # ty: ignore[invalid-argument-type]
}
for item in data[0]["items"]
for item in data[0]["items"] # ty: ignore[not-iterable]
]
for task in batch_tasks:
if task["title"] == data[0]["title"]:
Expand All @@ -180,12 +180,12 @@ def test_create_task_v2(generate_object_id, delete_projects, client, get_batch):
)

expected_task_1_reminders = [
{"id": reminder["id"], "trigger": reminder["trigger"]}
for reminder in data[1]["reminders"]
{"id": reminder["id"], "trigger": reminder["trigger"]} # ty: ignore[invalid-argument-type]
for reminder in data[1]["reminders"] # ty: ignore[not-iterable]
]
expected_task_2_reminders = [
{"id": reminder["id"], "trigger": reminder["trigger"]}
for reminder in data[2]["reminders"]
{"id": reminder["id"], "trigger": reminder["trigger"]} # ty: ignore[invalid-argument-type]
for reminder in data[2]["reminders"] # ty: ignore[not-iterable]
]
for task in batch_tasks:
if task["title"] == data[1]["title"]:
Expand Down
12 changes: 6 additions & 6 deletions tests/unit/test_pydantic.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ def test_update_model_config_nested():

data = PostBatchTaskV2.model_validate(dict_)
assert isinstance(data, PostBatchTaskV2)
assert data.extra_field == "value" # pyright: ignore[reportAttributeAccessIssue]
assert data.add[0].extra_nested_field == "value" # pyright: ignore[reportAttributeAccessIssue]
assert data.add[0].items[0].extra_nested_nested_field == "value" # pyright: ignore[reportOptionalSubscript,reportAttributeAccessIssue]
assert data.update[0].extra_nested_field == "value" # pyright: ignore[reportAttributeAccessIssue]
assert data.update[0].reminders[0].extra_extra_nested_field == "value" # pyright: ignore[reportOptionalSubscript,reportAttributeAccessIssue]
assert data.delete[0].extra_nested_field == "value" # pyright: ignore[reportAttributeAccessIssue]
assert data.extra_field == "value" # pyright: ignore[reportAttributeAccessIssue] # ty: ignore[unresolved-attribute]
assert data.add[0].extra_nested_field == "value" # pyright: ignore[reportAttributeAccessIssue] # ty: ignore[unresolved-attribute]
assert data.add[0].items[0].extra_nested_nested_field == "value" # pyright: ignore[reportOptionalSubscript,reportAttributeAccessIssue] # ty: ignore[possibly-missing-attribute, not-subscriptable]
assert data.update[0].extra_nested_field == "value" # pyright: ignore[reportAttributeAccessIssue] # ty: ignore[unresolved-attribute]
assert data.update[0].reminders[0].extra_extra_nested_field == "value" # pyright: ignore[reportOptionalSubscript,reportAttributeAccessIssue] # ty: ignore[possibly-missing-attribute, not-subscriptable]
assert data.delete[0].extra_nested_field == "value" # pyright: ignore[reportAttributeAccessIssue] # ty: ignore[unresolved-attribute]
2 changes: 1 addition & 1 deletion tests/unit/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def test_v1_token_initialize_invalid(
test_expiration = None

with pytest.raises(ValidationError):
TokenV1(value=test_value, expiration=test_expiration) # pyright: ignore[reportArgumentType]
TokenV1(value=test_value, expiration=test_expiration) # pyright: ignore[reportArgumentType] # ty: ignore[invalid-argument-type]


def test_v1_token_expiration():
Expand Down
Loading
Loading