Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#99: Remove extra fields from response schemas #101

Merged
merged 11 commits into from
Jan 28, 2025
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.


Expand Down
28 changes: 14 additions & 14 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>"]
maintainers = ["Nifadev Vadim <[email protected]>"]
Expand Down Expand Up @@ -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"]
Expand Down Expand Up @@ -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"]
4 changes: 1 addition & 3 deletions tests/factories/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down Expand Up @@ -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,
Expand All @@ -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(),
}
Expand Down
1 change: 0 additions & 1 deletion tests/factories/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
17 changes: 0 additions & 17 deletions tests/integration/test_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/test_time_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions tests/integration/test_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
9 changes: 7 additions & 2 deletions tests/integration/test_workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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]}
Expand Down
1 change: 0 additions & 1 deletion tests/responses/workspace_get.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 0 additions & 1 deletion tests/test_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion tests/test_workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
8 changes: 2 additions & 6 deletions toggl_python/entities/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""
Expand All @@ -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,
Expand All @@ -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.

Expand All @@ -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
Expand Down
4 changes: 0 additions & 4 deletions toggl_python/schemas/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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]:
Expand Down Expand Up @@ -129,4 +126,3 @@ class BulkEditProjectsFieldNames(str, Enum):
is_private = "is_private"
project_name = "name"
start_date = "start_date"
template = "template"
1 change: 0 additions & 1 deletion toggl_python/schemas/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down