From 6a310c8b5dd4d971a102c68f7efd8930819d794f Mon Sep 17 00:00:00 2001 From: Moritz Pietzschke Date: Sat, 5 Nov 2022 11:15:39 +0100 Subject: [PATCH 01/10] add support for Projects; --- src/personio_py/__init__.py | 1 + src/personio_py/client.py | 60 ++++++++++++++++++++++++++++++++++++- src/personio_py/models.py | 43 ++++++++++++++++++++++++++ tests/test_project.py | 26 ++++++++++++++++ 4 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 tests/test_project.py diff --git a/src/personio_py/__init__.py b/src/personio_py/__init__.py index 40a11f6..f03cca1 100644 --- a/src/personio_py/__init__.py +++ b/src/personio_py/__init__.py @@ -31,5 +31,6 @@ ShortEmployee, Team, WorkSchedule, + Project ) from personio_py.client import Personio diff --git a/src/personio_py/client.py b/src/personio_py/client.py index 5ee9ed6..b6cdd90 100644 --- a/src/personio_py/client.py +++ b/src/personio_py/client.py @@ -10,7 +10,7 @@ import requests from requests import Response -from personio_py import Absence, AbsenceType, Attendance, DynamicMapping, Employee +from personio_py import Absence, AbsenceType, Attendance, DynamicMapping, Employee, Project from personio_py.errors import MissingCredentialsError, PersonioApiError, PersonioError from personio_py.models import PersonioResource from personio_py.search import SearchIndex @@ -437,6 +437,64 @@ def invalidate_index(self): """ self.search_index.invalidate() + def get_projects(self): + """ + Get a list of all projects used to attendances. + + + :return: list of ``Project`` records + """ + response = self.request_json(f'company/attendances/projects/') + projects = [Project.from_dict(d, self) for d in response['data']] + + return projects + + def create_project(self, project: Project) -> Project: + """ + Creates a project record on the Personio servers. + + :param project: The project object to be created + :raises PersonioError: If the project could not be created on the Personio servers + """ + data = project.to_body_params() + response = self.request_json('company/attendances/projects/', method='POST', data=data) + if response['success']: + project.id_ = response['data']['attributes']['id'] + return project + raise PersonioError("Could not create project") + + def update_project(self, project: Project) -> Project: + """ + Updates a project record on the Personio servers. + + :param project: The project object to be updated + :raises PersonioErrror: If the project could not be created on the Personio servers + """ + data = project.to_body_params() + response = self.request_json(f'company/attendances/projects/{project.id_}', method='PATCH', data=data) + if response['success']: + return project + raise PersonioError("Could not update project") + + def delete_project(self, project: Union[Project, int]) -> None: + """ + Deletes a project record on the Personio servers. + + :param project: The project object to be updated + :raises ValueError: If a query is required but not allowed + or the query does not provide exactly one result. + """ + if isinstance(project, int): + response = self.request_json(f'company/attendances/projects/{project.id_}', method='DELETE') + return response['success'] + elif isinstance(project, Project): + if project.id_ is not None: + return self.delete_project(project.id_) + else: + raise ValueError("Only a project with a project id can be deleted.") + else: + raise ValueError("project must be a Project object or an integer") + def _get_employee_metadata( self, path: str, resource_cls: Type[PersonioResourceType], employees: Union[int, List[int], Employee, List[Employee]], start_date: datetime = None, diff --git a/src/personio_py/models.py b/src/personio_py/models.py index 8b92b8c..9b16637 100644 --- a/src/personio_py/models.py +++ b/src/personio_py/models.py @@ -620,6 +620,49 @@ def to_body_params(self): data['comment'] = self.comment return data +class Project(WritablePersonioResource): + + _api_type_name = "Project" + _field_mapping_list = [ + # note: the id is actually not in the attributes dict, but one level higher + NumericFieldMapping('id', 'id_', int), + FieldMapping('name', 'name', str), + BooleanFieldMapping('active', 'active'), + DateTimeFieldMapping('created_at', 'created_at'), + DateTimeFieldMapping('updated_at', 'updated_at') + ] + + def __init__(self, client: 'Personio' = None, dynamic: Dict[str, Any] = None, + dynamic_raw: List['DynamicAttr'] = None, id_: int = None, name: str = None, active: bool = None, created_at: datetime = None, updated_at: datetime = None, **kwargs): + super().__init__(client=client, dynamic=dynamic, dynamic_raw=dynamic_raw, **kwargs) + self.id_ = id_ + self.name = name + self.active = active + self.created_at = created_at + self.updated_at = updated_at + + def _create(self, client: 'Personio' = None): + return get_client(self, client).create_project(self) + + def _delete(self, client: 'Personio' = None): + return get_client(self, client).delete_project(self) + + def _update(self, client: 'Personio'= None): + return get_client(self, client).update_project(self) + + def to_dict(self, nested=False) -> Dict[str, Any]: + # yes, this is weird an unnecessary, but that's how the api works + d = super().to_dict() + d['id'] = self.id_ + del d['attributes']['id'] + return d + + def to_body_params(self): + data = { + 'name': self.name, + 'active': self.active + } + return data class Attendance(WritablePersonioResource): diff --git a/tests/test_project.py b/tests/test_project.py new file mode 100644 index 0000000..a2d510b --- /dev/null +++ b/tests/test_project.py @@ -0,0 +1,26 @@ +from datetime import datetime + +from personio_py import Project + +project_dict = { + 'id': 1, + 'type': 'Project', + 'attributes': { + 'name': 'Project name', + 'active': True, + 'created_at': '1835-12-01T13:15:00+00:00', + 'updated_at': '1836-12-01T13:15:00+00:00', + } +} + + +def test_parse_project(): + project = Project.from_dict(project_dict) + assert project + assert project.name == 'Project name' + assert project.active == True + +def test_serialize_project(): + project = Project.from_dict(project_dict) + d = project.to_dict() + assert d == project_dict From a330562c47be2694aa996872330673f7d5c7ee84 Mon Sep 17 00:00:00 2001 From: Moritz Pietzschke Date: Sat, 5 Nov 2022 13:17:09 +0100 Subject: [PATCH 02/10] update changelog and readme; --- CHANGELOG.md | 1 + README.md | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c28df46..516ecde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased](https://github.com/at-gmbh/personio-py/compare/v0.2.2...HEAD) +* add support for Projects ([#30](https://github.com/at-gmbh/personio-py/pull/30)) ## [0.2.2](https://github.com/at-gmbh/personio-py/tree/v0.2.2) - 2022-07-04 diff --git a/README.md b/README.md index aac4a0d..b116313 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,11 @@ Available * [`POST /company/time-offs`](https://developer.personio.de/reference#post_company-time-offs): add absence data for the company employees * [`GET /company/time-offs/{id}`](https://developer.personio.de/reference#get_company-time-offs-id): get the absence entry with the specified ID * [`DELETE /company/time-offs/{id}`](https://developer.personio.de/reference#delete_company-time-offs-id): delete the absence entry with the specified ID +* [`GET /company/attendances/projects`](https://developer.personio.de/reference/get_company-attendances-projects): fetch projects for attendances +* [`POST /company/attendances/projects`](https://developer.personio.de/reference/post_company-attendances-projects): create a project for attendence +* [`DELETE /company/attendances/projects`](https://developer.personio.de/reference/delete_company-attendances-projects-id): delete a project for attendence +* [`PATCH /company/attendances/projects/{id}`](https://developer.personio.de/reference/patch_company-attendances-projects-id): update a project for attendence + Work in Progress From ff197e20e5f22c1e7e98e3e2be98759eebe01cdc Mon Sep 17 00:00:00 2001 From: Moritz Pietzschke Date: Sat, 5 Nov 2022 13:19:02 +0100 Subject: [PATCH 03/10] fix typos; --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b116313..4422bc9 100644 --- a/README.md +++ b/README.md @@ -97,9 +97,9 @@ Available * [`GET /company/time-offs/{id}`](https://developer.personio.de/reference#get_company-time-offs-id): get the absence entry with the specified ID * [`DELETE /company/time-offs/{id}`](https://developer.personio.de/reference#delete_company-time-offs-id): delete the absence entry with the specified ID * [`GET /company/attendances/projects`](https://developer.personio.de/reference/get_company-attendances-projects): fetch projects for attendances -* [`POST /company/attendances/projects`](https://developer.personio.de/reference/post_company-attendances-projects): create a project for attendence -* [`DELETE /company/attendances/projects`](https://developer.personio.de/reference/delete_company-attendances-projects-id): delete a project for attendence -* [`PATCH /company/attendances/projects/{id}`](https://developer.personio.de/reference/patch_company-attendances-projects-id): update a project for attendence +* [`POST /company/attendances/projects`](https://developer.personio.de/reference/post_company-attendances-projects): create a project for attendance +* [`DELETE /company/attendances/projects`](https://developer.personio.de/reference/delete_company-attendances-projects-id): delete a project for attendance +* [`PATCH /company/attendances/projects/{id}`](https://developer.personio.de/reference/patch_company-attendances-projects-id): update a project for attendance Work in Progress From 8b1098758030570196830070bd72160de499a0e8 Mon Sep 17 00:00:00 2001 From: Fateme Tardasti Date: Tue, 28 Mar 2023 18:40:29 +0200 Subject: [PATCH 04/10] Fix feature/project and Add test --- src/personio_py/client.py | 13 +++++----- tests/test_api_project.py | 52 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 tests/test_api_project.py diff --git a/src/personio_py/client.py b/src/personio_py/client.py index 403c0cc..e8ca241 100644 --- a/src/personio_py/client.py +++ b/src/personio_py/client.py @@ -37,6 +37,7 @@ class Personio: """base URL of the Personio HTTP API""" ATTENDANCE_URL = 'company/attendances' ABSENCE_URL = 'company/time-offs' + PROJECT_URL = 'company/attendances/projects/' def __init__(self, base_url: str = None, client_id: str = None, client_secret: str = None, dynamic_fields: List[DynamicMapping] = None): @@ -521,7 +522,7 @@ def get_projects(self): :return: list of ``Project`` records """ - response = self.request_json(f'company/attendances/projects/') + response = self.request_json(self.PROJECT_URL) projects = [Project.from_dict(d, self) for d in response['data']] return projects @@ -534,9 +535,9 @@ def create_project(self, project: Project) -> Project: :raises PersonioError: If the project could not be created on the Personio servers """ data = project.to_body_params() - response = self.request_json('company/attendances/projects/', method='POST', data=data) + response = self.request_json(self.PROJECT_URL, method='POST', data=data) if response['success']: - project.id_ = response['data']['attributes']['id'] + project.id_ = response['data']['id'] return project raise PersonioError("Could not create project") @@ -548,7 +549,7 @@ def update_project(self, project: Project) -> Project: :raises PersonioErrror: If the project could not be created on the Personio servers """ data = project.to_body_params() - response = self.request_json(f'company/attendances/projects/{project.id_}', method='PATCH', data=data) + response = self.request_json(f'{self.PROJECT_URL}{project.id_}', method='PATCH', data=data) if response['success']: return project raise PersonioError("Could not update project") @@ -562,8 +563,8 @@ def delete_project(self, project: Union[Project, int]) -> None: or the query does not provide exactly one result. """ if isinstance(project, int): - response = self.request_json(f'company/attendances/projects/{project.id_}', method='DELETE') - return response['success'] + response = self.request(f'{self.PROJECT_URL}{project}', method='DELETE') + return response elif isinstance(project, Project): if project.id_ is not None: return self.delete_project(project.id_) diff --git a/tests/test_api_project.py b/tests/test_api_project.py new file mode 100644 index 0000000..2985a27 --- /dev/null +++ b/tests/test_api_project.py @@ -0,0 +1,52 @@ +from .apitest_shared import * +from personio_py import Project + + +@skip_if_no_auth +def test_get_projects(): + create_test_project(name = "test project") + projects = personio.get_projects() + + assert len(projects)>0 + assert projects[0].name == "test project" + assert projects[0].active == False + personio.delete_project(projects[0]) + + +@skip_if_no_auth +def test_update_project(): + """ + Test the update of project records on the server. + """ + project = create_test_project(name="initial project", active=True) + project.name = "updated project" + updated_project = personio.update_project(project) + + assert updated_project.name == "updated project" + + project.active = False + updated_project = personio.update_project(project) + + assert updated_project.active == False + + personio.delete_project(project) + +@skip_if_no_auth +def test_delete_project_by_id(): + project = create_test_project(name="delete project by id", active=True) + response = personio.delete_project(project.id_) + + assert response.status_code == 204 + +@skip_if_no_auth +def test_delete_project_by_project_object(): + project = create_test_project(name="delete project by object", active=True) + response = personio.delete_project(project) + + assert response.status_code == 204 + +def create_test_project(name: str = 'project name', active: bool = False): + p = Project(name=name, active=active) + project = personio.create_project(p) + + return project \ No newline at end of file From 180d149da2b764e8ce45fb05b88a0edfcb8f637a Mon Sep 17 00:00:00 2001 From: Fateme Tardasti Date: Thu, 30 Mar 2023 09:34:46 +0200 Subject: [PATCH 05/10] Add mock tests --- src/personio_py/client.py | 6 +-- src/personio_py/models.py | 11 +++-- tests/mock_data.py | 73 +++++++++++++++++++++++++++ tests/test_api_project.py | 16 +++--- tests/test_mock_api_project.py | 90 ++++++++++++++++++++++++++++++++++ 5 files changed, 181 insertions(+), 15 deletions(-) create mode 100644 tests/test_mock_api_project.py diff --git a/src/personio_py/client.py b/src/personio_py/client.py index e8ca241..3260395 100644 --- a/src/personio_py/client.py +++ b/src/personio_py/client.py @@ -37,7 +37,7 @@ class Personio: """base URL of the Personio HTTP API""" ATTENDANCE_URL = 'company/attendances' ABSENCE_URL = 'company/time-offs' - PROJECT_URL = 'company/attendances/projects/' + PROJECT_URL = 'company/attendances/projects' def __init__(self, base_url: str = None, client_id: str = None, client_secret: str = None, dynamic_fields: List[DynamicMapping] = None): @@ -549,7 +549,7 @@ def update_project(self, project: Project) -> Project: :raises PersonioErrror: If the project could not be created on the Personio servers """ data = project.to_body_params() - response = self.request_json(f'{self.PROJECT_URL}{project.id_}', method='PATCH', data=data) + response = self.request_json(f'{self.PROJECT_URL}/{project.id_}', method='PATCH', data=data) if response['success']: return project raise PersonioError("Could not update project") @@ -563,7 +563,7 @@ def delete_project(self, project: Union[Project, int]) -> None: or the query does not provide exactly one result. """ if isinstance(project, int): - response = self.request(f'{self.PROJECT_URL}{project}', method='DELETE') + response = self.request(f'{self.PROJECT_URL}/{project}', method='DELETE') return response elif isinstance(project, Project): if project.id_ is not None: diff --git a/src/personio_py/models.py b/src/personio_py/models.py index 4788db3..9211c60 100644 --- a/src/personio_py/models.py +++ b/src/personio_py/models.py @@ -622,6 +622,7 @@ def to_body_params(self): data['comment'] = self.comment return data + class Project(WritablePersonioResource): _api_type_name = "Project" @@ -635,7 +636,9 @@ class Project(WritablePersonioResource): ] def __init__(self, client: 'Personio' = None, dynamic: Dict[str, Any] = None, - dynamic_raw: List['DynamicAttr'] = None, id_: int = None, name: str = None, active: bool = None, created_at: datetime = None, updated_at: datetime = None, **kwargs): + dynamic_raw: List['DynamicAttr'] = None, id_: int = None, name: str = None, + active: bool = None, created_at: datetime = None, updated_at: datetime = None, + **kwargs): super().__init__(client=client, dynamic=dynamic, dynamic_raw=dynamic_raw, **kwargs) self.id_ = id_ self.name = name @@ -649,7 +652,7 @@ def _create(self, client: 'Personio' = None): def _delete(self, client: 'Personio' = None): return get_client(self, client).delete_project(self) - def _update(self, client: 'Personio'= None): + def _update(self, client: 'Personio' = None): return get_client(self, client).update_project(self) def to_dict(self, nested=False) -> Dict[str, Any]: @@ -662,10 +665,10 @@ def to_dict(self, nested=False) -> Dict[str, Any]: def to_body_params(self): data = { 'name': self.name, - 'active': self.active - } + 'active': self.active} return data + class Attendance(WritablePersonioResource): _api_type_name = "AttendancePeriod" diff --git a/tests/mock_data.py b/tests/mock_data.py index 8cc877f..f53b7b1 100644 --- a/tests/mock_data.py +++ b/tests/mock_data.py @@ -1179,3 +1179,76 @@ } """ json_dict_attendance_delete = json.loads(json_string_attendance_delete) + +json_string_project_rms = """ +{ + "success": true, + "data": [ + { + "id": 238751, + "type": "Project", + "attributes": { + "name": "conwik project", + "active": true, + "created_at": "2019-03-01T16:00:00", + "updated_at": "2020-03-02T16:11:35" + } + }, { + "id": 238750, + "type": "Project", + "attributes": { + "name": "mock project", + "active": true, + "created_at": "2023-03-28T16:11:35", + "updated_at": "2023-03-28T16:11:35" + } + }, { + "id": 238752, + "type": "Project", + "attributes": { + "name": "alianz project", + "active": false, + "created_at": "2020-10-10T10:11:35", + "updated_at": "2023-02-22T17:11:35" + } + } + ] +} +""" +json_dict_project_rms = json.loads(json_string_project_rms) + +json_string_project_create = """ +{ + "success":true, + "data":{ + "id": 83648600, + "message": "success" + } +} +""" +json_dict_project_create = json.loads(json_string_project_create) + +json_string_project_delete = """ +{ + "text":"", + "status_code": 204 +} +""" +json_dict_project_delete = json.loads(json_string_project_delete) + +json_string_project_update = """ +{ + "success": true, + "data": { + "id": 238760, + "type": "Project", + "attributes": { + "name": "updated mock project", + "active": true, + "created_at": "2023-03-28T16:11:35", + "updated_at": "2023-03-28T16:11:52" + } + } +} +""" +json_dict_project_update = json.loads(json_string_project_update) diff --git a/tests/test_api_project.py b/tests/test_api_project.py index 2985a27..6c88193 100644 --- a/tests/test_api_project.py +++ b/tests/test_api_project.py @@ -6,12 +6,12 @@ def test_get_projects(): create_test_project(name = "test project") projects = personio.get_projects() - + assert len(projects)>0 - assert projects[0].name == "test project" - assert projects[0].active == False - personio.delete_project(projects[0]) - + assert projects[-1].name == "test project" + assert projects[-1].active == False + personio.delete_project(projects[-1]) + @skip_if_no_auth def test_update_project(): @@ -28,10 +28,10 @@ def test_update_project(): updated_project = personio.update_project(project) assert updated_project.active == False - + personio.delete_project(project) -@skip_if_no_auth +@skip_if_no_auth def test_delete_project_by_id(): project = create_test_project(name="delete project by id", active=True) response = personio.delete_project(project.id_) @@ -49,4 +49,4 @@ def create_test_project(name: str = 'project name', active: bool = False): p = Project(name=name, active=active) project = personio.create_project(p) - return project \ No newline at end of file + return project diff --git a/tests/test_mock_api_project.py b/tests/test_mock_api_project.py new file mode 100644 index 0000000..48c5110 --- /dev/null +++ b/tests/test_mock_api_project.py @@ -0,0 +1,90 @@ +import re +from datetime import datetime + +import responses + +from personio_py import Project +from tests.mock_data import ( + json_dict_project_rms, json_dict_project_update, json_dict_project_delete, + json_dict_project_create +) +from tests.test_mock_api import compare_labeled_attributes, mock_personio + + +@responses.activate +def test_create_project(): + mock_create_project() + personio = mock_personio() + + project = Project( + client=personio, + name="project one", + active=True, + created_at=datetime.strptime("2023-03-28T16:24:36", "%Y-%m-%dT%H:%M:%S"), + updated_at=datetime.strptime("2023-03-28T16:24:36", "%Y-%m-%dT%H:%M:%S") + ) + project.create() + assert project.id_ + +@responses.activate +def test_get_project(): + mock_projects() + # configure personio & get absences for alan + personio = mock_personio() + projects = personio.get_projects() + # validate + assert len(projects) == 3 + selection = [a for a in projects if "conwik" in a.name.lower()] + assert len(selection) == 1 + release = selection[0] # an instance of Project + assert release.active == True + assert release.created_at == datetime(2019,3,1,16,0,0) + assert release.updated_at == datetime(2020,3,2,16,11,35) + assert release.id_ == 238751 + # validate serialization + source_dict = json_dict_project_rms['data'][0] + target_dict = release.to_dict() + compare_labeled_attributes(source_dict, target_dict) + +@responses.activate +def test_update_projects(): + mock_projects() + mock_update_project() + personio = mock_personio() + projects = personio.get_projects() + projects_to_update = projects[0] + projects_to_update.active = False + updated_project = personio.update_project(projects_to_update) + assert updated_project.active == False + + +@responses.activate +def test_delete_attendances(): + mock_projects() + mock_delete_project() + personio = mock_personio() + projects = personio.get_projects() + project_to_delete = projects[0] + response = personio.delete_project(project_to_delete) + assert response.status_code == 204 + +def mock_projects(): + # mock the get absences endpoint (with different array offsets) + responses.add( + responses.GET, re.compile('https://api.personio.de/v1/company/attendances/projects?.*'), + status=200, json=json_dict_project_rms, adding_headers={'Authorization': 'Bearer foo'}) + +def mock_create_project(): + responses.add( + responses.POST, 'https://api.personio.de/v1/company/attendances/projects', + status=200, json=json_dict_project_create, adding_headers={'Authorization': 'Bearer bar'}) + +def mock_update_project(): + responses.add( + responses.PATCH, 'https://api.personio.de/v1/company/attendances/projects/238751', + status=200, json=json_dict_project_update, adding_headers={'Authorization': 'Bearer bar'}) + +def mock_delete_project(): + responses.add( + responses.DELETE, 'https://api.personio.de/v1/company/attendances/projects/238751', + status=204, json=json_dict_project_delete, adding_headers={'Authorization': 'Bearer bar'}) From 9c0ce2e84d1b40cd7ac51c2d5979742bbce48b39 Mon Sep 17 00:00:00 2001 From: Fateme Tardasti Date: Thu, 30 Mar 2023 09:56:53 +0200 Subject: [PATCH 06/10] Modify Changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18f48a0..0b32b08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.2.4](https://github.com/at-gmbh/personio-py/tree/feature/projects-fateme) - 2023-03-30 + +* debug projects feature ([#36](https://github.com/at-gmbh/personio-py/pull/36)) +* add tests + ## [0.2.3](https://github.com/at-gmbh/personio-py/tree/v0.2.3) - 2023-02-08 * debug attendance feature From dea2a8609ab0baa2625f5e9a68534a4cc5d145df Mon Sep 17 00:00:00 2001 From: Sebastian Straub Date: Fri, 5 May 2023 10:50:26 +0200 Subject: [PATCH 07/10] README update: available API functions and updated links to API docs --- README.md | 78 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 53 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 9091f52..105fbbb 100644 --- a/README.md +++ b/README.md @@ -84,31 +84,59 @@ This project is released on [PyPI](https://pypi.org/project/personio-py/). Most ## API Functions -Available - -* [`POST /auth`](https://developer.personio.de/reference#auth): fully transparent authentication handling -* [`GET /company/employees`](https://developer.personio.de/reference#get_company-employees): list all employees -* [`GET /company/employees/{id}`](https://developer.personio.de/reference#get_company-employees-employee-id): get the employee with the specified ID -* [`GET /company/employees/{id}/profile-picture/{width}`](https://developer.personio.de/reference#get_company-employees-employee-id-profile-picture-width): get the profile picture of the specified employee -* [`GET /company/attendances`](https://developer.personio.de/reference#get_company-attendances): fetch attendance data for the company employees -* [`POST /company/attendances`](https://developer.personio.de/reference#post_company-attendances): add attendance data for the company employees -* [`DELETE /company/attendances/{id}`](https://developer.personio.de/reference#delete_company-attendances-id): delete the attendance entry with the specified ID -* [`PATCH /company/attendances/{id}`](https://developer.personio.de/reference#patch_company-attendances-id): update the attendance entry with the specified ID -* [`GET /company/time-off-types`](https://developer.personio.de/reference#get_company-time-off-types): get a list of available absences types -* [`GET /company/time-offs`](https://developer.personio.de/reference#get_company-time-offs): fetch absence data for the company employees -* [`POST /company/time-offs`](https://developer.personio.de/reference#post_company-time-offs): add absence data for the company employees -* [`GET /company/time-offs/{id}`](https://developer.personio.de/reference#get_company-time-offs-id): get the absence entry with the specified ID -* [`DELETE /company/time-offs/{id}`](https://developer.personio.de/reference#delete_company-time-offs-id): delete the absence entry with the specified ID -* [`GET /company/attendances/projects`](https://developer.personio.de/reference/get_company-attendances-projects): fetch projects for attendances -* [`POST /company/attendances/projects`](https://developer.personio.de/reference/post_company-attendances-projects): create a project for attendance -* [`DELETE /company/attendances/projects`](https://developer.personio.de/reference/delete_company-attendances-projects-id): delete a project for attendance -* [`PATCH /company/attendances/projects/{id}`](https://developer.personio.de/reference/patch_company-attendances-projects-id): update a project for attendance - - -Work in Progress - -* [`POST /company/employees`](https://developer.personio.de/reference#post_company-employees): create a new employee -* [`PATCH /company/employees/{id}`](https://developer.personio.de/reference#patch_company-employees-employee-id): update an existing employee entry +Since the [Personio API](https://developer.personio.de/reference/introduction) gets extended over time, personio-py usually only implements a subset of all available API features. This section gives an overview, which API functions are accessible through personio-py. + +### Available + +Authentication + +* [`POST /auth`](https://developer.personio.de/reference/post_auth-1): fully transparent authentication handling + +Employees + +* [`GET /company/employees`](https://developer.personio.de/reference/get_company-employees): list all employees +* [`POST /company/employees`](https://developer.personio.de/reference/post_company-employees): create a new employee +* [`GET /company/employees/{id}`](https://developer.personio.de/reference/get_company-employees-employee-id): get the employee with the specified ID +* [`GET /company/employees/{id}/profile-picture/{width}`](https://developer.personio.de/reference/get_company-employees-employee-id-profile-picture-width): get the profile picture of the specified employee + +Attendances + +* [`GET /company/attendances`](https://developer.personio.de/reference/get_company-attendances): fetch attendance data for the company employees +* [`POST /company/attendances`](https://developer.personio.de/reference/post_company-attendances): add attendance data for the company employees +* [`DELETE /company/attendances/{id}`](https://developer.personio.de/reference/delete_company-attendances-id): delete the attendance entry with the specified ID +* [`PATCH /company/attendances/{id}`](https://developer.personio.de/reference/patch_company-attendances-id): update the attendance entry with the specified ID + +Projects + +* [`GET /company/attendances/projects`](https://developer.personio.de/reference/get_company-attendances-projects): provides a list of all company projects +* [`POST /company/attendances/projects`](https://developer.personio.de/reference/post_company-attendances-projects): creates a project into the company account +* [`DELETE /company/attendances/projects/{id}`](https://developer.personio.de/reference/delete_company-attendances-projects-id): deletes a project from the company account +* [`PATCH /company/attendances/projects/{id}`](https://developer.personio.de/reference/patch_company-attendances-projects-id): updates a project with the given data + +Absences + +* [`GET /company/time-off-types`](https://developer.personio.de/reference/get_company-time-off-types): get a list of available absences types +* [`GET /company/time-offs`](https://developer.personio.de/reference/get_company-time-offs): fetch absence data for the company employees +* [`POST /company/time-offs`](https://developer.personio.de/reference/post_company-time-offs): add absence data for the company employees +* [`GET /company/time-offs/{id}`](https://developer.personio.de/reference/get_company-time-offs-id): get the absence entry with the specified ID +* [`DELETE /company/time-offs/{id}`](https://developer.personio.de/reference/delete_company-time-offs-id): delete the absence entry with the specified ID + +### Not yet implemented + +* [`PATCH /company/employees/{id}`](https://developer.personio.de/reference/patch_company-employees-employee-id): update an existing employee entry +* [`GET /company/employees/{employee_id}/absences/balance`](https://developer.personio.de/reference/get_company-employees-employee-id-absences-balance): retrieve the absence balance for a specific employee +* [`GET /company/employees/custom-attributes`](https://developer.personio.de/reference/get_company-employees-custom-attributes): this endpoint is an alias for /company/employees/attributes +* [`GET /company/employees/attributes`](https://developer.personio.de/reference/get_company-employees-attributes): lists all the allowed attributes per API credentials including custom (dynamic) attributes. +* [`GET /company/absence-periods`](https://developer.personio.de/reference/get_company-absence-periods) +* [`POST /company/absence-periods`](https://developer.personio.de/reference/post_company-absence-periods) +* [`DELETE /company/absence-periods/{id}`](https://developer.personio.de/reference/delete_company-absence-periods-id) + +* [`GET /company/document-categories`](https://developer.personio.de/reference/get_company-document-categories): this endpoint is responsible for fetching all document categories of the company +* [`POST /company/documents`](https://developer.personio.de/reference/post_company-documents): this endpoint is responsible for uploading documents for the company employees +* [`GET /company/custom-reports/reports`](https://developer.personio.de/reference/listreports): this endpoint provides you with metadata about existing custom reports in your Personio account, such as report name, report type, report date / timeframe +* [`GET /company/custom-reports/reports/{report_id}`](https://developer.personio.de/reference/listreportitems): this endpoint provides you with the data of an existing Custom Report +* [`GET /company/custom-reports/columns`](https://developer.personio.de/reference/listcolumns): this endpoint provides human-readable labels for report table columns +* all of the [recruiting API](https://developer.personio.de/reference/introduction-1) ## Contact From bbbf5a982e215b67247c87064f1b952d1eef4862 Mon Sep 17 00:00:00 2001 From: Sebastian Straub Date: Fri, 5 May 2023 10:50:56 +0200 Subject: [PATCH 08/10] changelog update (no actual releases since 0.2.2) --- CHANGELOG.md | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b32b08..6c1bd60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,19 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.2.4](https://github.com/at-gmbh/personio-py/tree/feature/projects-fateme) - 2023-03-30 - -* debug projects feature ([#36](https://github.com/at-gmbh/personio-py/pull/36)) -* add tests - -## [0.2.3](https://github.com/at-gmbh/personio-py/tree/v0.2.3) - 2023-02-08 - -* debug attendance feature -* add support for attendance paginated API requests - ## [Unreleased](https://github.com/at-gmbh/personio-py/compare/v0.2.2...HEAD) -* add support for Projects ([#30](https://github.com/at-gmbh/personio-py/pull/30)) +* add support for Projects ([#36](https://github.com/at-gmbh/personio-py/pull/36)) +* add support for attendances, with paginated API requests ([#35](https://github.com/at-gmbh/personio-py/pull/35)) ## [0.2.2](https://github.com/at-gmbh/personio-py/tree/v0.2.2) - 2022-07-04 From 7f36c960656d564f07ec0d9b222509b9b8d4408f Mon Sep 17 00:00:00 2001 From: Sebastian Straub Date: Fri, 5 May 2023 12:09:04 +0200 Subject: [PATCH 09/10] type hint --- src/personio_py/client.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/personio_py/client.py b/src/personio_py/client.py index 3260395..b26ce2d 100644 --- a/src/personio_py/client.py +++ b/src/personio_py/client.py @@ -515,16 +515,14 @@ def invalidate_index(self): """ self.search_index.invalidate() - def get_projects(self): + def get_projects(self) -> List[Project]: """ - Get a list of all projects used to attendances. - + Get a list of all company projects. :return: list of ``Project`` records """ response = self.request_json(self.PROJECT_URL) projects = [Project.from_dict(d, self) for d in response['data']] - return projects def create_project(self, project: Project) -> Project: From 96a884f4274e5dd297b4d34f25a5000fbab07f05 Mon Sep 17 00:00:00 2001 From: Sebastian Straub Date: Fri, 5 May 2023 15:23:23 +0200 Subject: [PATCH 10/10] changelog update for today's release --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c1bd60..6c523a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased](https://github.com/at-gmbh/personio-py/compare/v0.2.2...HEAD) +## [Unreleased](https://github.com/at-gmbh/personio-py/compare/v0.2.3...HEAD) + +... + +## [0.2.3](https://github.com/at-gmbh/personio-py/tree/v0.2.3) - 2023-05-05 * add support for Projects ([#36](https://github.com/at-gmbh/personio-py/pull/36)) * add support for attendances, with paginated API requests ([#35](https://github.com/at-gmbh/personio-py/pull/35))