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. diff --git a/poetry.lock b/poetry.lock index 5a6097f..6e639e3 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" @@ -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] @@ -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 = "fbb409d6620f45293e78f187b88610da27195b7bad0456a1b3f361de08546bb6" diff --git a/pyproject.toml b/pyproject.toml index 53aa853..c4f7d57 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 "] @@ -40,12 +40,12 @@ pydantic = {extras = ["email"], version = "^2.9.2"} [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"] @@ -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"] diff --git a/tests/factories/project.py b/tests/factories/project.py index bb71589..013b739 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(): @@ -62,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, @@ -72,7 +70,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/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/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/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, 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() 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/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/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/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( diff --git a/toggl_python/entities/workspace.py b/toggl_python/entities/workspace.py index eae2deb..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. """ @@ -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..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] @@ -89,8 +88,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 +126,3 @@ class BulkEditProjectsFieldNames(str, Enum): is_private = "is_private" project_name = "name" start_date = "start_date" - template = "template" 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