Skip to content

Commit 1c185f2

Browse files
authored
#99: Remove extra fields from response schemas (#101)
* Remove template and template_id from ProjectResponse schema * Use only active projects in integration tests * Disable change password integration test because it has crucial consequences * Remove only_admins_see_billable_rates, is_shared from Workspace response * Add note about creating venv for local development * Update nox version to support python 3.13 * Fix test min boundary * Exclude init files from coverage report
1 parent f1321fd commit 1c185f2

15 files changed

+37
-54
lines changed

README.md

+5
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@ if __name__ == "__main__":
114114

115115
`poetry` is required during local setup.
116116

117+
* Install minimal supported `Python` version using `pyenv` - `pyenv install 3.8`
118+
* Activate it for current project - `pyenv local 3.8`
119+
* Create virtual environment - `python -m venv .venv`. If `Python version is not minimal` then
120+
IDE suggestions will be incorrect and `pre-commit` hooks will not be working.
121+
117122
Run `poetry install --no-root` to setup local environment. `pre-commit install` is also advisable.
118123

119124

poetry.lock

+14-14
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "toggl_python"
3-
version = "0.3.1"
3+
version = "0.3.2"
44
description = "Typed `Toggl API` Python wrapper with pre-validation to avoid extra network usage."
55
authors = ["Evrone <[email protected]>"]
66
maintainers = ["Nifadev Vadim <[email protected]>"]
@@ -40,12 +40,12 @@ pydantic = {extras = ["email"], version = "^2.9.2"}
4040

4141
[tool.poetry.group.dev.dependencies]
4242
pytest = "^8.3.3"
43-
nox = "^2024.4.15"
4443
respx = "^0.21.1"
4544
ruff = "^0.5.7"
4645
pre-commit = "3.5.0"
4746
faker = "^28.4.1"
4847
pytest-cov = "^5.0.0"
48+
nox = "^2024.10.9"
4949

5050
[build-system]
5151
requires = ["poetry-core"]
@@ -110,3 +110,6 @@ markers = [
110110
"integration: make API calls during testing (deselect with '-m \"not integration\"')",
111111
]
112112
addopts = "--cov=toggl_python --cov-fail-under=95"
113+
114+
[tool.coverage.run]
115+
omit =["__init__.py"]

tests/factories/project.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ def project_request_factory() -> Dict[str, Union[str, bool, int]]:
2626
"is_shared": fake.boolean(),
2727
"name": str(fake.uuid4()),
2828
"start_date": start_date.isoformat(),
29-
"template": fake.boolean(),
3029
}
3130

3231
if fake.boolean():
@@ -62,7 +61,6 @@ def project_response_factory(
6261
"fixed_fee": fake.random_int() if fake.boolean() else None,
6362
"id": fake.random_int(),
6463
"is_private": fake.boolean(),
65-
"is_shared": fake.boolean(),
6664
"name": fake.word(),
6765
"rate": fake.random_int() if fake.boolean() else None,
6866
"rate_last_updated": datetime_repr_factory(timezone) if fake.boolean() else None,
@@ -72,7 +70,7 @@ def project_response_factory(
7270
"start_date": fake.past_date().isoformat(),
7371
"status": fake.word() if fake.boolean() else None,
7472
"template": fake.null_boolean(),
75-
"template_id": fake.random_int(),
73+
"template_id": fake.random_int() if fake.boolean() else None,
7674
"wid": workspace_id or fake.random_int(),
7775
"workspace_id": workspace_id or fake.random_int(),
7876
}

tests/factories/workspace.py

-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ def workspace_response_factory(
5353
"name": fake.text(max_nb_chars=139),
5454
"only_admins_may_create_projects": fake.boolean(),
5555
"only_admins_may_create_tags": fake.boolean(),
56-
"only_admins_see_billable_rates": fake.boolean(),
5756
"only_admins_see_team_dashboard": fake.boolean(),
5857
"organization_id": 8364520,
5958
"premium": fake.boolean(),

tests/integration/test_project.py

-17
Original file line numberDiff line numberDiff line change
@@ -199,23 +199,6 @@ def test_get_projects__with_page_and_per_page(i_authed_workspace: Workspace) ->
199199
_ = i_authed_workspace.delete_project(workspace_id, last_created_project.id)
200200

201201

202-
def test_get_projects__only_templates(i_authed_workspace: Workspace) -> None:
203-
workspace_id = int(os.environ["WORKSPACE_ID"])
204-
template_project = i_authed_workspace.create_project(
205-
workspace_id, template=True, name=fake.uuid4()
206-
)
207-
usual_project = i_authed_workspace.create_project(workspace_id, active=True, name=fake.uuid4())
208-
209-
result = i_authed_workspace.get_projects(workspace_id, only_templates=True)
210-
211-
project_ids = {project.id for project in result}
212-
assert usual_project.id not in project_ids
213-
assert template_project.id in project_ids
214-
215-
_ = i_authed_workspace.delete_project(workspace_id, template_project.id)
216-
_ = i_authed_workspace.delete_project(workspace_id, usual_project.id)
217-
218-
219202
def test_get_projects__sort_field_and_sort_order(i_authed_workspace: Workspace) -> None:
220203
workspace_id = int(os.environ["WORKSPACE_ID"])
221204
project_suffix_name = fake.word()

tests/integration/test_time_entry.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def test_create_time_entry__all_fields(i_authed_workspace: Workspace) -> None:
5454
workspace_id = int(os.environ["WORKSPACE_ID"])
5555
request_body = time_entry_extended_request_factory(workspace_id)
5656
expected_result = set(MeTimeEntryResponse.model_fields.keys())
57-
project = i_authed_workspace.create_project(workspace_id, name=str(fake.uuid4()))
57+
project = i_authed_workspace.create_project(workspace_id, name=str(fake.uuid4()), active=True)
5858

5959
result = i_authed_workspace.create_time_entry(
6060
workspace_id,

tests/integration/test_user.py

+1
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ def test_update_me__unavailable_default_workspace_id(i_authed_user: CurrentUser)
110110
_ = i_authed_user.update_me(default_workspace_id=invalid_default_workspace_id)
111111

112112

113+
@pytest.mark.skip(reason="Changes actual user password, run this test only when necessary")
113114
def test_change_password__ok(i_authed_user: CurrentUser) -> None:
114115
current_password = os.environ["TOGGL_PASSWORD"]
115116
new_password = fake.password()

tests/integration/test_workspace.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def test_get_workspace_by_id(i_authed_workspace: Workspace) -> None:
2525
assert result.model_fields_set == expected_result
2626

2727

28-
def test_get_workspaces__without_query_params(i_authed_workspace: Workspace)-> None:
28+
def test_get_workspaces__without_query_params(i_authed_workspace: Workspace) -> None:
2929
expected_result = set(WorkspaceResponse.model_fields.keys())
3030

3131
result = i_authed_workspace.list()
@@ -35,7 +35,12 @@ def test_get_workspaces__without_query_params(i_authed_workspace: Workspace)-> N
3535

3636
def test_update(i_authed_workspace: Workspace) -> None:
3737
workspace_id = int(os.environ["WORKSPACE_ID"])
38-
excluded_fields = {"admins", "only_admins_may_create_tags"}
38+
excluded_fields = {
39+
"admins",
40+
"only_admins_may_create_tags",
41+
"reports_collapse",
42+
"only_admins_see_team_dashboard",
43+
}
3944
full_request_body = workspace_request_factory(exclude=excluded_fields)
4045
random_param = fake.random_element(full_request_body.keys())
4146
request_body = {random_param: full_request_body[random_param]}

tests/responses/workspace_get.py

-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
"name": "test workspace",
2121
"only_admins_may_create_projects": False,
2222
"only_admins_may_create_tags": False,
23-
"only_admins_see_billable_rates": False,
2423
"only_admins_see_team_dashboard": False,
2524
"organization_id": 8364520,
2625
"premium": False,

tests/test_project.py

-1
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,6 @@ def test_bulk_edit_projects__empty_operations(authed_workspace: Workspace) -> No
375375
(BulkEditProjectsFieldNames.is_private.value, fake.boolean()),
376376
(BulkEditProjectsFieldNames.project_name.value, fake.uuid4()),
377377
(BulkEditProjectsFieldNames.start_date.value, fake.date()),
378-
(BulkEditProjectsFieldNames.template.value, fake.boolean()),
379378
],
380379
)
381380
def test_bulk_edit_time_entries__ok(

tests/test_workspace.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def test_get_workspaces__too_old_since_value(
9292
argnames="workspace_name, error_message",
9393
argvalues=(
9494
("", "String should have at least 1 character"),
95-
(fake.pystr(min_chars=140, max_chars=200), "String should have at most 140 character"),
95+
(fake.pystr(min_chars=141, max_chars=200), "String should have at most 140 character"),
9696
),
9797
)
9898
def test_update__invalid_workspace_name(

toggl_python/entities/workspace.py

+2-6
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def update(
6161
"""Allow to update Workspace instance fields which are available on free plan.
6262
6363
Request body parameters `default_hourly_rate`, `default_currency`, `rounding`,
64-
`rounding_minutes`, `only_admins_see_billable_rates`, `projects_billable_by_default`,
64+
`rounding_minutes`, `projects_billable_by_default`,
6565
`rate_change_mode`, `project_private_by_default`, `projects_enforce_billable` are
6666
available only on paid plan. That is why they are not listed in method arguments.
6767
"""
@@ -82,7 +82,7 @@ def update(
8282
response_body = response.json()
8383
return WorkspaceResponse.model_validate(response_body)
8484

85-
def create_project( # noqa: PLR0913 - Too many arguments in function definition
85+
def create_project(
8686
self,
8787
workspace_id: int,
8888
active: Optional[bool] = None,
@@ -96,8 +96,6 @@ def create_project( # noqa: PLR0913 - Too many arguments in function definition
9696
is_shared: Optional[bool] = None,
9797
name: Optional[str] = None,
9898
start_date: Union[date, str, None] = None,
99-
template: Optional[bool] = None,
100-
template_id: Optional[int] = None,
10199
) -> ProjectResponse:
102100
"""Allow to update Project instance fields which are available on free plan.
103101
@@ -119,8 +117,6 @@ def create_project( # noqa: PLR0913 - Too many arguments in function definition
119117
is_shared=is_shared,
120118
name=name,
121119
start_date=start_date,
122-
template=template,
123-
template_id=template_id,
124120
)
125121
request_body = request_body_schema.model_dump(
126122
mode="json", exclude_none=True, exclude_unset=True

toggl_python/schemas/project.py

-4
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ class ProjectResponse(BaseSchema):
2828
fixed_fee: Optional[int]
2929
id: int
3030
is_private: bool
31-
is_shared: bool
3231
name: str
3332
rate: Optional[int]
3433
rate_last_updated: Optional[datetime]
@@ -89,8 +88,6 @@ class CreateProjectRequest(BaseSchema):
8988
is_shared: Optional[bool] = None
9089
name: Optional[str] = None
9190
start_date: Optional[date] = None
92-
template: Optional[bool] = None
93-
template_id: Optional[int] = None
9491

9592
@field_serializer("start_date", "end_date", when_used="json")
9693
def serialize_datetimes(self, value: Optional[date]) -> Optional[str]:
@@ -129,4 +126,3 @@ class BulkEditProjectsFieldNames(str, Enum):
129126
is_private = "is_private"
130127
project_name = "name"
131128
start_date = "start_date"
132-
template = "template"

toggl_python/schemas/workspace.py

-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ class WorkspaceResponseBase(BaseSchema):
2424
name: str
2525
only_admins_may_create_projects: bool
2626
only_admins_may_create_tags: bool
27-
only_admins_see_billable_rates: bool
2827
only_admins_see_team_dashboard: bool
2928
organization_id: int
3029
premium: bool

0 commit comments

Comments
 (0)