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

Feature/absences #12

Merged
merged 36 commits into from
Mar 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
9751da4
Place authentication in importable file for use with test cases place…
Nov 24, 2020
0921a1e
Readd existing tests
Nov 26, 2020
e74b645
Implement absence methods. Add raw api tests for absences
Nov 30, 2020
79d8c2f
Raise NotImplementedError if a function is called which is not implem…
philipflohr Nov 24, 2020
f56bc62
Merge branch 'master' into feature/absences
philipflohr Nov 30, 2020
202c820
Move absence mock tests / test data to new files to keep tests readab…
Nov 30, 2020
a2bd5fb
Changed client to be public. Add mock tests for deleting absences and…
Dec 1, 2020
638ac34
Moved mocking related data and functions to tests/mock folder. Added …
Dec 2, 2020
88a9768
Update import statement
Dec 2, 2020
380068c
Don't use shared_test_data dict, cache retrieval of valid online user
Dec 2, 2020
c92882e
Merge branch 'prepare_api_test_restructuring' into feature/absences
Dec 2, 2020
48c3777
Don't use shared_test_data dict, cache retrieval of valid online user
Dec 2, 2020
330d868
Merge branch 'prepare_api_test_restructuring' into feature/absences
Dec 2, 2020
6fcc7b4
use get_test_employee function instead of shared dict
Dec 2, 2020
a7699c6
Don't require python3.8 to run the tests
Dec 2, 2020
bd86d0e
Merge branch 'prepare_api_test_restructuring' into feature/absences
Dec 2, 2020
4760c95
Add mock tests for getting absences
Dec 2, 2020
1f7b037
Merge branch 'master' into feature/absences
klamann Dec 3, 2020
5b0bb43
Use unions for type hints
Dec 4, 2020
f7a3964
Merge remote-tracking branch 'origin/feature/absences' into feature/a…
Dec 4, 2020
d5b892c
Revert client visibility change. Client is a protected member again.
Dec 7, 2020
f7e8968
Updates according to comment on github
Jan 13, 2021
6fb32a4
Fix: duplicated entries returned by API if pagination offset > total …
Jan 13, 2021
df95c36
Don't allow remote id queries for absence delete operations
Jan 13, 2021
539fd1e
include metadata in mock responses for absences
Jan 13, 2021
1dc0bed
Always query for remote ids for absence get operations. Updated api m…
Jan 13, 2021
6d2bf98
Update online api tests for absences (remote id query is not a passab…
Jan 14, 2021
8d9fc66
restructure mocking data and functions
Jan 21, 2021
60c4ee2
duplicate import & whitespace
klamann Jan 22, 2021
d6aa78f
Update README.md
Jan 22, 2021
110d1cd
Merge remote-tracking branch 'origin/feature/absences' into feature/a…
Jan 22, 2021
5ebd8a3
Use fstrings to format url endpoint of the get_absence method
Feb 3, 2021
1771fe8
catch unchecked case in get_absence method
Feb 3, 2021
8d74e7b
Limit tests against the api t specific time ranges (try to avoid conf…
Feb 8, 2021
f4b732a
Changes requested for merge
Feb 8, 2021
cbf67ce
Create absence: return updated object instead of bool
Feb 8, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,12 @@ Available
* [`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/time-offs`](https://developer.personio.de/reference#get_company-time-offs): fetch absence data for the company employees
* [`GET /company/attendances`](https://developer.personio.de/reference#get_company-attendances): fetch attendance data for the company employees
* [`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

Work in Progress

Expand All @@ -102,10 +106,6 @@ Work in Progress
* [`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
* [`POST /company/time-offs`](https://developer.personio.de/reference#post_company-time-offs): add absence data for the company employees
* [`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/time-offs/{id}`](https://developer.personio.de/reference#get_company-time-offs-id): get the absence entry with the specified ID

## Contact

Expand Down
82 changes: 72 additions & 10 deletions src/personio_py/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,10 @@ def request_paginated(
resp_data = response['data']
if resp_data:
data_acc.extend(resp_data)
params['offset'] += len(resp_data)
if response['metadata']['current_page'] == response['metadata']['total_pages']:
break
else:
params['offset'] += len(resp_data)
philipflohr marked this conversation as resolved.
Show resolved Hide resolved
else:
break
# return the accumulated data
Expand Down Expand Up @@ -341,23 +344,58 @@ def get_absences(self, employees: Union[int, List[int], Employee, List[Employee]
return self._get_employee_metadata(
'company/time-offs', Absence, employees, start_date, end_date)

def get_absence(self, absence_id: int) -> Absence:
def get_absence(self, absence: Union[Absence, int]) -> Absence:
"""
placeholder; not ready to be used
Get an absence record from a given id.

:param absence: The absence id to fetch.
"""
raise NotImplementedError()
if isinstance(absence, int):
response = self.request_json(f'company/time-offs/{absence}')
return Absence.from_dict(response['data'], self)
else:
if absence.id_:
return self.get_absence(absence.id_)
else:
self.__add_remote_absence_id(absence)
return self.get_absence(absence.id_)

def create_absence(self, absence: Absence):
def create_absence(self, absence: Absence) -> Absence:
"""
placeholder; not ready to be used
Creates an absence record on the Personio servers

:param absence: The absence object to be created
:raises PersonioError: If the absence could not be created on the Personio servers
"""
raise NotImplementedError()
data = absence.to_body_params()
response = self.request_json('company/time-offs', method='POST', data=data)
if response['success']:
absence.id_ = response['data']['attributes']['id']
return absence
raise PersonioError("Could not create absence")

def delete_absence(self, absence_id: int):
def delete_absence(self, absence: Union[Absence, int]):
"""
placeholder; not ready to be used
Delete an existing record

An absence id is required.

:param absence: The Absence object holding
the new data or an absence record id to delete.
:raises:
ValueError: If a query is required but not allowed
or the query does not provide exactly one result.
"""
raise NotImplementedError()
if isinstance(absence, int):
response = self.request_json(path=f'company/time-offs/{absence}', method='DELETE')
return response['success']
elif isinstance(absence, Absence):
if absence.id_ is not None:
return self.delete_absence(absence.id_)
else:
raise ValueError("Only an absence with an absence id can be deleted.")
else:
raise ValueError("absence must be an Absence object or an integer")

def _get_employee_metadata(
self, path: str, resource_cls: Type[PersonioResourceType],
Expand Down Expand Up @@ -408,3 +446,27 @@ def _normalize_timeframe_params(
employees = [employees]
employee_ids = [(e.id_ if isinstance(e, Employee) else e) for e in employees]
return employee_ids, start_date, end_date

def __add_remote_absence_id(self, absence: Absence) -> Absence:
"""
Queries the API for an absence record matching
the given Absence object and adds the remote id.

:param absence: The absence object to be updated
:return: The absence object with the absence_id set
"""
if absence.employee is None:
raise ValueError("For a remote query an employee_id is required")
if absence.start_date is None:
raise ValueError("For a remote query a start date is required")
if absence.end_date is None:
raise ValueError("For a remote query an end date is required")
matching_remote_absences = self.get_absences(employees=[absence.employee.id_],
start_date=absence.start_date,
end_date=absence.end_date)
if len(matching_remote_absences) == 0:
raise PersonioError("The absence to patch was not found")
elif len(matching_remote_absences) > 1:
raise PersonioError("More than one absence found.")
absence.id_ = matching_remote_absences[0].id_
return absence
31 changes: 22 additions & 9 deletions src/personio_py/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -573,8 +573,8 @@ def __init__(self,
start_date: datetime = None,
end_date: datetime = None,
days_count: float = None,
half_day_start: int = None,
half_day_end: int = None,
half_day_start: bool = False,
half_day_end: bool = False,
time_off_type: AbsenceType = None,
employee: ShortEmployee = None,
created_by: str = None,
Expand All @@ -588,19 +588,32 @@ def __init__(self,
self.start_date = start_date
self.end_date = end_date
self.days_count = days_count
self.half_day_start = half_day_start
self.half_day_end = half_day_end
self.half_day_start = bool(half_day_start)
self.half_day_end = bool(half_day_end)
self.time_off_type = time_off_type
self.employee = employee
self.created_by = created_by
self.certificate = certificate
self.created_at = created_at

def _create(self, client: 'Personio'):
pass

def _delete(self, client: 'Personio'):
pass
def _create(self, client: 'Personio' = None):
return get_client(self, client).create_absence(self)
philipflohr marked this conversation as resolved.
Show resolved Hide resolved

def _delete(self, client: 'Personio' = None):
return get_client(self, client).delete_absence(self)

def to_body_params(self):
data = {
'employee_id': self.employee.id_,
'time_off_type_id': self.time_off_type.id_,
'start_date': self.start_date.strftime("%Y-%m-%d"),
'end_date': self.end_date.strftime("%Y-%m-%d"),
'half_day_start': self.half_day_start,
'half_day_end': self.half_day_end
}
if self.comment is not None:
data['comment'] = self.comment
return data


class Attendance(WritablePersonioResource):
Expand Down
5 changes: 5 additions & 0 deletions tests/apitest_shared.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import os
from functools import lru_cache
from datetime import date

import pytest

from personio_py import Personio, PersonioError

# Test time. if used on a personio instance, only touch entries during this time range
NOT_BEFORE = date(year=2022, month=1, day=1)
NOT_AFTER = date(year=2022, month=12, day=31)

# Personio client authentication
CLIENT_ID = os.getenv('CLIENT_ID')
CLIENT_SECRET = os.getenv('CLIENT_SECRET')
Expand Down
Loading