From 5cadce1c244354f459417f987f82de727a407df3 Mon Sep 17 00:00:00 2001 From: Vadim Nifadev <36514612+nifadyev@users.noreply.github.com> Date: Mon, 20 Jan 2025 16:10:39 +0300 Subject: [PATCH 01/11] Remove template and template_id from ProjectResponse schema --- tests/factories/project.py | 3 +-- tests/integration/test_project.py | 17 ----------------- tests/test_project.py | 1 - toggl_python/entities/workspace.py | 6 +----- toggl_python/schemas/project.py | 3 --- 5 files changed, 2 insertions(+), 28 deletions(-) diff --git a/tests/factories/project.py b/tests/factories/project.py index bb71589..5c572d4 100644 --- a/tests/factories/project.py +++ b/tests/factories/project.py @@ -26,7 +26,6 @@ def project_request_factory() -> Dict[str, Union[str, bool, int]]: "is_shared": fake.boolean(), "name": str(fake.uuid4()), "start_date": start_date.isoformat(), - "template": fake.boolean(), } if fake.boolean(): @@ -72,7 +71,7 @@ def project_response_factory( "start_date": fake.past_date().isoformat(), "status": fake.word() if fake.boolean() else None, "template": fake.null_boolean(), - "template_id": fake.random_int(), + "template_id": fake.random_int() if fake.boolean() else None, "wid": workspace_id or fake.random_int(), "workspace_id": workspace_id or fake.random_int(), } diff --git a/tests/integration/test_project.py b/tests/integration/test_project.py index d723dcf..98c89f5 100644 --- a/tests/integration/test_project.py +++ b/tests/integration/test_project.py @@ -199,23 +199,6 @@ def test_get_projects__with_page_and_per_page(i_authed_workspace: Workspace) -> _ = i_authed_workspace.delete_project(workspace_id, last_created_project.id) -def test_get_projects__only_templates(i_authed_workspace: Workspace) -> None: - workspace_id = int(os.environ["WORKSPACE_ID"]) - template_project = i_authed_workspace.create_project( - workspace_id, template=True, name=fake.uuid4() - ) - usual_project = i_authed_workspace.create_project(workspace_id, active=True, name=fake.uuid4()) - - result = i_authed_workspace.get_projects(workspace_id, only_templates=True) - - project_ids = {project.id for project in result} - assert usual_project.id not in project_ids - assert template_project.id in project_ids - - _ = i_authed_workspace.delete_project(workspace_id, template_project.id) - _ = i_authed_workspace.delete_project(workspace_id, usual_project.id) - - def test_get_projects__sort_field_and_sort_order(i_authed_workspace: Workspace) -> None: workspace_id = int(os.environ["WORKSPACE_ID"]) project_suffix_name = fake.word() diff --git a/tests/test_project.py b/tests/test_project.py index d73126b..a125fd4 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -375,7 +375,6 @@ def test_bulk_edit_projects__empty_operations(authed_workspace: Workspace) -> No (BulkEditProjectsFieldNames.is_private.value, fake.boolean()), (BulkEditProjectsFieldNames.project_name.value, fake.uuid4()), (BulkEditProjectsFieldNames.start_date.value, fake.date()), - (BulkEditProjectsFieldNames.template.value, fake.boolean()), ], ) def test_bulk_edit_time_entries__ok( diff --git a/toggl_python/entities/workspace.py b/toggl_python/entities/workspace.py index eae2deb..7659c2b 100644 --- a/toggl_python/entities/workspace.py +++ b/toggl_python/entities/workspace.py @@ -82,7 +82,7 @@ def update( response_body = response.json() return WorkspaceResponse.model_validate(response_body) - def create_project( # noqa: PLR0913 - Too many arguments in function definition + def create_project( self, workspace_id: int, active: Optional[bool] = None, @@ -96,8 +96,6 @@ def create_project( # noqa: PLR0913 - Too many arguments in function definition is_shared: Optional[bool] = None, name: Optional[str] = None, start_date: Union[date, str, None] = None, - template: Optional[bool] = None, - template_id: Optional[int] = None, ) -> ProjectResponse: """Allow to update Project instance fields which are available on free plan. @@ -119,8 +117,6 @@ def create_project( # noqa: PLR0913 - Too many arguments in function definition is_shared=is_shared, name=name, start_date=start_date, - template=template, - template_id=template_id, ) request_body = request_body_schema.model_dump( mode="json", exclude_none=True, exclude_unset=True diff --git a/toggl_python/schemas/project.py b/toggl_python/schemas/project.py index 66ecec0..565d022 100644 --- a/toggl_python/schemas/project.py +++ b/toggl_python/schemas/project.py @@ -89,8 +89,6 @@ class CreateProjectRequest(BaseSchema): is_shared: Optional[bool] = None name: Optional[str] = None start_date: Optional[date] = None - template: Optional[bool] = None - template_id: Optional[int] = None @field_serializer("start_date", "end_date", when_used="json") def serialize_datetimes(self, value: Optional[date]) -> Optional[str]: @@ -129,4 +127,3 @@ class BulkEditProjectsFieldNames(str, Enum): is_private = "is_private" project_name = "name" start_date = "start_date" - template = "template" From e6a770b911d2f5d57c3352e7fdf32fc8da56749a Mon Sep 17 00:00:00 2001 From: Vadim Nifadev <36514612+nifadyev@users.noreply.github.com> Date: Mon, 20 Jan 2025 16:11:07 +0300 Subject: [PATCH 02/11] Use only active projects in integration tests --- tests/integration/test_time_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_time_entry.py b/tests/integration/test_time_entry.py index b0308a6..c9bbb6e 100644 --- a/tests/integration/test_time_entry.py +++ b/tests/integration/test_time_entry.py @@ -54,7 +54,7 @@ def test_create_time_entry__all_fields(i_authed_workspace: Workspace) -> None: workspace_id = int(os.environ["WORKSPACE_ID"]) request_body = time_entry_extended_request_factory(workspace_id) expected_result = set(MeTimeEntryResponse.model_fields.keys()) - project = i_authed_workspace.create_project(workspace_id, name=str(fake.uuid4())) + project = i_authed_workspace.create_project(workspace_id, name=str(fake.uuid4()), active=True) result = i_authed_workspace.create_time_entry( workspace_id, From f808a85a0be6683df840ef270eaf8dc1bd327676 Mon Sep 17 00:00:00 2001 From: Vadim Nifadev <36514612+nifadyev@users.noreply.github.com> Date: Mon, 20 Jan 2025 16:11:39 +0300 Subject: [PATCH 03/11] Disable change password integration test because it has crucial consequences --- tests/integration/test_user.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration/test_user.py b/tests/integration/test_user.py index 4eb8ad7..fd5f651 100644 --- a/tests/integration/test_user.py +++ b/tests/integration/test_user.py @@ -110,6 +110,7 @@ def test_update_me__unavailable_default_workspace_id(i_authed_user: CurrentUser) _ = i_authed_user.update_me(default_workspace_id=invalid_default_workspace_id) +@pytest.mark.skip(reason="Changes actual user password, run this test only when necessary") def test_change_password__ok(i_authed_user: CurrentUser) -> None: current_password = os.environ["TOGGL_PASSWORD"] new_password = fake.password() From 0b342121a4178d97466b5cae71d1d76883f0124c Mon Sep 17 00:00:00 2001 From: Vadim Nifadev <36514612+nifadyev@users.noreply.github.com> Date: Mon, 20 Jan 2025 16:12:11 +0300 Subject: [PATCH 04/11] Remove only_admins_see_billable_rates from WorkspaceResponseBase schema --- tests/factories/workspace.py | 1 - tests/responses/workspace_get.py | 1 - toggl_python/entities/workspace.py | 2 +- toggl_python/schemas/workspace.py | 1 - 4 files changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/factories/workspace.py b/tests/factories/workspace.py index ea93c24..eed2c74 100644 --- a/tests/factories/workspace.py +++ b/tests/factories/workspace.py @@ -53,7 +53,6 @@ def workspace_response_factory( "name": fake.text(max_nb_chars=139), "only_admins_may_create_projects": fake.boolean(), "only_admins_may_create_tags": fake.boolean(), - "only_admins_see_billable_rates": fake.boolean(), "only_admins_see_team_dashboard": fake.boolean(), "organization_id": 8364520, "premium": fake.boolean(), diff --git a/tests/responses/workspace_get.py b/tests/responses/workspace_get.py index c072a7c..8a85732 100644 --- a/tests/responses/workspace_get.py +++ b/tests/responses/workspace_get.py @@ -20,7 +20,6 @@ "name": "test workspace", "only_admins_may_create_projects": False, "only_admins_may_create_tags": False, - "only_admins_see_billable_rates": False, "only_admins_see_team_dashboard": False, "organization_id": 8364520, "premium": False, diff --git a/toggl_python/entities/workspace.py b/toggl_python/entities/workspace.py index 7659c2b..c5740c0 100644 --- a/toggl_python/entities/workspace.py +++ b/toggl_python/entities/workspace.py @@ -61,7 +61,7 @@ def update( """Allow to update Workspace instance fields which are available on free plan. Request body parameters `default_hourly_rate`, `default_currency`, `rounding`, - `rounding_minutes`, `only_admins_see_billable_rates`, `projects_billable_by_default`, + `rounding_minutes`, `projects_billable_by_default`, `rate_change_mode`, `project_private_by_default`, `projects_enforce_billable` are available only on paid plan. That is why they are not listed in method arguments. """ diff --git a/toggl_python/schemas/workspace.py b/toggl_python/schemas/workspace.py index 9819218..7c95502 100644 --- a/toggl_python/schemas/workspace.py +++ b/toggl_python/schemas/workspace.py @@ -24,7 +24,6 @@ class WorkspaceResponseBase(BaseSchema): name: str only_admins_may_create_projects: bool only_admins_may_create_tags: bool - only_admins_see_billable_rates: bool only_admins_see_team_dashboard: bool organization_id: int premium: bool From ae866c6c1477f69edcabf4245ccdc17fe094d93d Mon Sep 17 00:00:00 2001 From: Vadim Nifadev <36514612+nifadyev@users.noreply.github.com> Date: Mon, 20 Jan 2025 16:12:30 +0300 Subject: [PATCH 05/11] Add note about creating venv for local development --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index d2698ed..a807248 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,11 @@ if __name__ == "__main__": `poetry` is required during local setup. +* Install minimal supported `Python` version using `pyenv` - `pyenv install 3.8` +* Activate it for current project - `pyenv local 3.8` +* Create virtual environment - `python -m venv .venv`. If `Python version is not minimal` then +IDE suggestions will be incorrect and `pre-commit` hooks will not be working. + Run `poetry install --no-root` to setup local environment. `pre-commit install` is also advisable. From 12ff2a380f8162e4ced21d6084d74db2dc6dfebe Mon Sep 17 00:00:00 2001 From: Vadim Nifadev <36514612+nifadyev@users.noreply.github.com> Date: Mon, 20 Jan 2025 16:47:44 +0300 Subject: [PATCH 06/11] Update nox version to support python 3.13 --- poetry.lock | 16 ++++++++-------- pyproject.toml | 1 + 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5a6097f..ab1db80 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. [[package]] name = "annotated-types" @@ -452,18 +452,18 @@ files = [ [[package]] name = "nox" -version = "2024.4.15" +version = "2024.10.9" description = "Flexible test automation." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "nox-2024.4.15-py3-none-any.whl", hash = "sha256:6492236efa15a460ecb98e7b67562a28b70da006ab0be164e8821177577c0565"}, - {file = "nox-2024.4.15.tar.gz", hash = "sha256:ecf6700199cdfa9e5ea0a41ff5e6ef4641d09508eda6edb89d9987864115817f"}, + {file = "nox-2024.10.9-py3-none-any.whl", hash = "sha256:1d36f309a0a2a853e9bccb76bbef6bb118ba92fa92674d15604ca99adeb29eab"}, + {file = "nox-2024.10.9.tar.gz", hash = "sha256:7aa9dc8d1c27e9f45ab046ffd1c3b2c4f7c91755304769df231308849ebded95"}, ] [package.dependencies] -argcomplete = ">=1.9.4,<4.0" -colorlog = ">=2.6.1,<7.0.0" +argcomplete = ">=1.9.4,<4" +colorlog = ">=2.6.1,<7" packaging = ">=20.9" tomli = {version = ">=1", markers = "python_version < \"3.11\""} virtualenv = ">=20.14.1" @@ -881,4 +881,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.0" python-versions = "^3.8.18" -content-hash = "5865bb287555f625aa1b86681a3361ebcdbd92381a2087c255483ecdd4778308" +content-hash = "aeff843f8799f3ec27f7821c4ac3c4849ce2ac0dc786556754187dd561d37559" diff --git a/pyproject.toml b/pyproject.toml index 53aa853..e0c1b8f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,7 @@ httpx = {extras = ["http2"], version = "^0.27.2"} # use zoneinfo.available_timezones() backports-zoneinfo = {version = "^0.2.1", python = "3.8"} pydantic = {extras = ["email"], version = "^2.9.2"} +nox = "^2024.10.9" [tool.poetry.group.dev.dependencies] pytest = "^8.3.3" From b08fd682624ff45cb2a242771a9aeed01badf75b Mon Sep 17 00:00:00 2001 From: Vadim Nifadev <36514612+nifadyev@users.noreply.github.com> Date: Mon, 20 Jan 2025 16:47:56 +0300 Subject: [PATCH 07/11] Fix test min boundary --- tests/test_workspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_workspace.py b/tests/test_workspace.py index d189a9f..6891b97 100644 --- a/tests/test_workspace.py +++ b/tests/test_workspace.py @@ -92,7 +92,7 @@ def test_get_workspaces__too_old_since_value( argnames="workspace_name, error_message", argvalues=( ("", "String should have at least 1 character"), - (fake.pystr(min_chars=140, max_chars=200), "String should have at most 140 character"), + (fake.pystr(min_chars=141, max_chars=200), "String should have at most 140 character"), ), ) def test_update__invalid_workspace_name( From 5a773aef3aa6b78c7fba2e98a7fe8cd0c67d68f1 Mon Sep 17 00:00:00 2001 From: Vadim Nifadev <36514612+nifadyev@users.noreply.github.com> Date: Mon, 20 Jan 2025 17:00:06 +0300 Subject: [PATCH 08/11] Move nox back to dev dependencies --- poetry.lock | 14 +++++++------- pyproject.toml | 3 +-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/poetry.lock b/poetry.lock index ab1db80..6e639e3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -38,13 +38,13 @@ trio = ["trio (>=0.26.1)"] [[package]] name = "argcomplete" -version = "3.5.0" +version = "3.5.3" description = "Bash tab completion for argparse" optional = false python-versions = ">=3.8" files = [ - {file = "argcomplete-3.5.0-py3-none-any.whl", hash = "sha256:d4bcf3ff544f51e16e54228a7ac7f486ed70ebf2ecfe49a63a91171c76bf029b"}, - {file = "argcomplete-3.5.0.tar.gz", hash = "sha256:4349400469dccfb7950bb60334a680c58d88699bff6159df61251878dc6bf74b"}, + {file = "argcomplete-3.5.3-py3-none-any.whl", hash = "sha256:2ab2c4a215c59fd6caaff41a869480a23e8f6a5f910b266c1808037f4e375b61"}, + {file = "argcomplete-3.5.3.tar.gz", hash = "sha256:c12bf50eded8aebb298c7b7da7a5ff3ee24dffd9f5281867dfe1424b58c55392"}, ] [package.extras] @@ -113,13 +113,13 @@ files = [ [[package]] name = "colorlog" -version = "6.8.2" +version = "6.9.0" description = "Add colours to the output of Python's logging module." optional = false python-versions = ">=3.6" files = [ - {file = "colorlog-6.8.2-py3-none-any.whl", hash = "sha256:4dcbb62368e2800cb3c5abd348da7e53f6c362dda502ec27c560b2e58a66bd33"}, - {file = "colorlog-6.8.2.tar.gz", hash = "sha256:3e3e079a41feb5a1b64f978b5ea4f46040a94f11f0e8bbb8261e3dbbeca64d44"}, + {file = "colorlog-6.9.0-py3-none-any.whl", hash = "sha256:5906e71acd67cb07a71e779c47c4bcb45fb8c2993eebe9e5adcd6a6f1b283eff"}, + {file = "colorlog-6.9.0.tar.gz", hash = "sha256:bfba54a1b93b94f54e1f4fe48395725a3d92fd2a4af702f6bd70946bdc0c6ac2"}, ] [package.dependencies] @@ -881,4 +881,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.0" python-versions = "^3.8.18" -content-hash = "aeff843f8799f3ec27f7821c4ac3c4849ce2ac0dc786556754187dd561d37559" +content-hash = "fbb409d6620f45293e78f187b88610da27195b7bad0456a1b3f361de08546bb6" diff --git a/pyproject.toml b/pyproject.toml index e0c1b8f..1d1e7f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,16 +37,15 @@ httpx = {extras = ["http2"], version = "^0.27.2"} # use zoneinfo.available_timezones() backports-zoneinfo = {version = "^0.2.1", python = "3.8"} pydantic = {extras = ["email"], version = "^2.9.2"} -nox = "^2024.10.9" [tool.poetry.group.dev.dependencies] pytest = "^8.3.3" -nox = "^2024.4.15" respx = "^0.21.1" ruff = "^0.5.7" pre-commit = "3.5.0" faker = "^28.4.1" pytest-cov = "^5.0.0" +nox = "^2024.10.9" [build-system] requires = ["poetry-core"] From a794d501cb4812c55b3bca23b5ffedae8b0c80e9 Mon Sep 17 00:00:00 2001 From: Vadim Nifadev <36514612+nifadyev@users.noreply.github.com> Date: Mon, 20 Jan 2025 17:01:23 +0300 Subject: [PATCH 09/11] Update project version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1d1e7f5..96a1840 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "toggl_python" -version = "0.3.1" +version = "0.3.2" description = "Typed `Toggl API` Python wrapper with pre-validation to avoid extra network usage." authors = ["Evrone "] maintainers = ["Nifadev Vadim "] From ad30b1529d235c788cab5eb9bcec106ce30d789f Mon Sep 17 00:00:00 2001 From: Vadim Nifadev <36514612+nifadyev@users.noreply.github.com> Date: Tue, 28 Jan 2025 17:33:02 +0300 Subject: [PATCH 10/11] Remove field is_shared from Workspace response --- tests/factories/project.py | 1 - tests/integration/test_workspace.py | 9 +++++++-- toggl_python/schemas/project.py | 1 - 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/factories/project.py b/tests/factories/project.py index 5c572d4..013b739 100644 --- a/tests/factories/project.py +++ b/tests/factories/project.py @@ -61,7 +61,6 @@ def project_response_factory( "fixed_fee": fake.random_int() if fake.boolean() else None, "id": fake.random_int(), "is_private": fake.boolean(), - "is_shared": fake.boolean(), "name": fake.word(), "rate": fake.random_int() if fake.boolean() else None, "rate_last_updated": datetime_repr_factory(timezone) if fake.boolean() else None, diff --git a/tests/integration/test_workspace.py b/tests/integration/test_workspace.py index a68974f..c2bec9c 100644 --- a/tests/integration/test_workspace.py +++ b/tests/integration/test_workspace.py @@ -25,7 +25,7 @@ def test_get_workspace_by_id(i_authed_workspace: Workspace) -> None: assert result.model_fields_set == expected_result -def test_get_workspaces__without_query_params(i_authed_workspace: Workspace)-> None: +def test_get_workspaces__without_query_params(i_authed_workspace: Workspace) -> None: expected_result = set(WorkspaceResponse.model_fields.keys()) result = i_authed_workspace.list() @@ -35,7 +35,12 @@ def test_get_workspaces__without_query_params(i_authed_workspace: Workspace)-> N def test_update(i_authed_workspace: Workspace) -> None: workspace_id = int(os.environ["WORKSPACE_ID"]) - excluded_fields = {"admins", "only_admins_may_create_tags"} + excluded_fields = { + "admins", + "only_admins_may_create_tags", + "reports_collapse", + "only_admins_see_team_dashboard", + } full_request_body = workspace_request_factory(exclude=excluded_fields) random_param = fake.random_element(full_request_body.keys()) request_body = {random_param: full_request_body[random_param]} diff --git a/toggl_python/schemas/project.py b/toggl_python/schemas/project.py index 565d022..3619c9a 100644 --- a/toggl_python/schemas/project.py +++ b/toggl_python/schemas/project.py @@ -28,7 +28,6 @@ class ProjectResponse(BaseSchema): fixed_fee: Optional[int] id: int is_private: bool - is_shared: bool name: str rate: Optional[int] rate_last_updated: Optional[datetime] From 135ade7b6a924954006b5685f9a2b6477b4e59d8 Mon Sep 17 00:00:00 2001 From: Vadim Nifadev <36514612+nifadyev@users.noreply.github.com> Date: Tue, 28 Jan 2025 17:35:00 +0300 Subject: [PATCH 11/11] Exclude init files from coverage report --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 96a1840..c4f7d57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -110,3 +110,6 @@ markers = [ "integration: make API calls during testing (deselect with '-m \"not integration\"')", ] addopts = "--cov=toggl_python --cov-fail-under=95" + +[tool.coverage.run] +omit =["__init__.py"]