brynq-sdk-bob 0.0.6__tar.gz → 1.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. {brynq_sdk_bob-0.0.6 → brynq_sdk_bob-1.1.0}/PKG-INFO +1 -1
  2. brynq_sdk_bob-1.1.0/brynq_sdk_bob/__init__.py +77 -0
  3. brynq_sdk_bob-1.1.0/brynq_sdk_bob/bank.py +26 -0
  4. brynq_sdk_bob-1.1.0/brynq_sdk_bob/company.py +23 -0
  5. brynq_sdk_bob-1.1.0/brynq_sdk_bob/custom_tables.py +36 -0
  6. brynq_sdk_bob-1.1.0/brynq_sdk_bob/documents.py +46 -0
  7. brynq_sdk_bob-1.1.0/brynq_sdk_bob/employment.py +23 -0
  8. brynq_sdk_bob-1.1.0/brynq_sdk_bob/named_lists.py +37 -0
  9. brynq_sdk_bob-1.1.0/brynq_sdk_bob/payments.py +23 -0
  10. brynq_sdk_bob-1.1.0/brynq_sdk_bob/people.py +61 -0
  11. brynq_sdk_bob-1.1.0/brynq_sdk_bob/salaries.py +24 -0
  12. brynq_sdk_bob-1.1.0/brynq_sdk_bob/schemas/__init__.py +0 -0
  13. brynq_sdk_bob-1.1.0/brynq_sdk_bob/schemas/bank.py +20 -0
  14. brynq_sdk_bob-1.1.0/brynq_sdk_bob/schemas/custom_tables.py +10 -0
  15. brynq_sdk_bob-1.1.0/brynq_sdk_bob/schemas/employment.py +28 -0
  16. brynq_sdk_bob-1.1.0/brynq_sdk_bob/schemas/named_lists.py +13 -0
  17. brynq_sdk_bob-1.1.0/brynq_sdk_bob/schemas/payments.py +30 -0
  18. brynq_sdk_bob-1.1.0/brynq_sdk_bob/schemas/people.py +87 -0
  19. brynq_sdk_bob-1.1.0/brynq_sdk_bob/schemas/salary.py +21 -0
  20. brynq_sdk_bob-1.1.0/brynq_sdk_bob/schemas/timeoff.py +28 -0
  21. brynq_sdk_bob-1.1.0/brynq_sdk_bob/schemas/work.py +29 -0
  22. brynq_sdk_bob-1.1.0/brynq_sdk_bob/timeoff.py +25 -0
  23. brynq_sdk_bob-1.1.0/brynq_sdk_bob/work.py +24 -0
  24. {brynq_sdk_bob-0.0.6 → brynq_sdk_bob-1.1.0}/brynq_sdk_bob.egg-info/PKG-INFO +1 -1
  25. brynq_sdk_bob-1.1.0/brynq_sdk_bob.egg-info/SOURCES.txt +29 -0
  26. brynq_sdk_bob-1.1.0/brynq_sdk_bob.egg-info/top_level.txt +1 -0
  27. {brynq_sdk_bob-0.0.6 → brynq_sdk_bob-1.1.0}/setup.py +1 -1
  28. brynq_sdk_bob-0.0.6/brynq_sdk_bob.egg-info/SOURCES.txt +0 -7
  29. brynq_sdk_bob-0.0.6/brynq_sdk_bob.egg-info/top_level.txt +0 -1
  30. {brynq_sdk_bob-0.0.6 → brynq_sdk_bob-1.1.0}/brynq_sdk_bob.egg-info/dependency_links.txt +0 -0
  31. {brynq_sdk_bob-0.0.6 → brynq_sdk_bob-1.1.0}/brynq_sdk_bob.egg-info/not-zip-safe +0 -0
  32. {brynq_sdk_bob-0.0.6 → brynq_sdk_bob-1.1.0}/brynq_sdk_bob.egg-info/requires.txt +0 -0
  33. {brynq_sdk_bob-0.0.6 → brynq_sdk_bob-1.1.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 1.0
2
2
  Name: brynq_sdk_bob
3
- Version: 0.0.6
3
+ Version: 1.1.0
4
4
  Summary: Bob wrapper from BrynQ
5
5
  Home-page: UNKNOWN
6
6
  Author: BrynQ
@@ -0,0 +1,77 @@
1
+ import base64
2
+ import re
3
+ from typing import Union, List
4
+ import pandas as pd
5
+ import requests
6
+ from brynq_sdk_brynq import BrynQ
7
+ from .bank import Bank
8
+ from .company import Company
9
+ from .documents import CustomDocuments
10
+ from .employment import Employment
11
+ from .named_lists import NamedLists
12
+ from .payments import Payments
13
+ from .people import People
14
+ from .salaries import Salaries
15
+ from .timeoff import TimeOff
16
+ from .work import Work
17
+
18
+ class Bob(BrynQ):
19
+ def __init__(self, label: Union[str, List], test_environment: bool = True, debug: bool = False, target_system: str = None):
20
+ super().__init__()
21
+ self.headers = self._get_request_headers(label=label)
22
+ if test_environment:
23
+ self.base_url = "https://api.sandbox.hibob.com/v1/"
24
+ else:
25
+ self.base_url = "https://api.hibob.com/v1/"
26
+ self.session = requests.Session()
27
+ self.session.headers.update(self.headers)
28
+ self.people = People(self)
29
+ self.salaries = Salaries(self)
30
+ self.work = Work(self)
31
+ self.bank = Bank(self)
32
+ self.employment = Employment(self)
33
+ self.payments = Payments(self)
34
+ self.time_off = TimeOff(self)
35
+ self.documents = CustomDocuments(self)
36
+ self.companies = Company(self)
37
+ self.named_lists = NamedLists(self)
38
+
39
+ def _get_request_headers(self, label):
40
+ credentials = self.get_system_credential(system='bob', label=label)
41
+ auth_token = base64.b64encode(f"{credentials['User ID']}:{credentials['API Token']}".encode()).decode('utf-8')
42
+ headers = {
43
+ "accept": "application/json",
44
+ "Authorization": f"Basic {auth_token}",
45
+ "Partner-Token": "001Vg00000A6FY6IAN"
46
+ }
47
+
48
+ return headers
49
+
50
+ def get_paginated_result(self, request: requests.Request) -> List:
51
+ has_next_page = True
52
+ result_data = []
53
+ while has_next_page:
54
+ prepped = request.prepare()
55
+ prepped.headers.update(self.session.headers)
56
+ resp = self.session.send(prepped)
57
+ resp.raise_for_status()
58
+ response_data = resp.json()
59
+ result_data += response_data['results']
60
+ next_cursor = response_data.get('response_metadata').get('next_cursor')
61
+ # If there is no next page, set has_next_page to False, we could use the falsy value of None but this is more readable
62
+ has_next_page = next_cursor is not None
63
+ if has_next_page:
64
+ request.params.update({"cursor": next_cursor})
65
+
66
+ return result_data
67
+
68
+ def rename_camel_columns_to_snake_case(self, df: pd.DataFrame) -> pd.DataFrame:
69
+ def camel_to_snake_case(column):
70
+ # Replace periods with underscores
71
+ column = column.replace('.', '_')
72
+ # Insert underscores before capital letters and convert to lowercase
73
+ return re.sub(r'(?<!^)(?=[A-Z])', '_', column).lower()
74
+
75
+ df.columns = map(camel_to_snake_case, df.columns)
76
+
77
+ return df
@@ -0,0 +1,26 @@
1
+ import pandas as pd
2
+ from brynq_sdk_functions import Functions
3
+ from .schemas.bank import BankSchema
4
+
5
+
6
+ class Bank:
7
+ def __init__(self, bob):
8
+ self.bob = bob
9
+
10
+ def get(self, person_ids: pd.Series) -> (pd.DataFrame, pd.DataFrame):
11
+ data = []
12
+ for person_id in person_ids:
13
+ resp = self.bob.session.get(url=f"{self.bob.base_url}people/{person_id}/bank-accounts")
14
+ resp.raise_for_status()
15
+ temp_data = resp.json()['values']
16
+ # when an employee has one or more bank accounts, the response is a list of dictionaries.
17
+ for account in temp_data:
18
+ account['employee_id'] = person_id
19
+ data += temp_data
20
+
21
+ df = pd.DataFrame(data)
22
+ df = self.bob.rename_camel_columns_to_snake_case(df)
23
+
24
+ valid_banks, invalid_banks = Functions.validate_data(df=df, schema=BankSchema, debug=True)
25
+
26
+ return valid_banks, invalid_banks
@@ -0,0 +1,23 @@
1
+ import pandas as pd
2
+
3
+
4
+ class Company:
5
+ def __init__(self, bob):
6
+ self.bob = bob
7
+
8
+ def get_variable_values(self, list_name: str = None) -> dict:
9
+ values = {}
10
+
11
+ if list_name is not None:
12
+ resp = self.bob.session.get(url=f"{self.bob.base_url}company/named-lists/{list_name}")
13
+ resp.raise_for_status()
14
+ data = resp.json()
15
+ values.update({data["name"]: [value['id'] for value in data['values']]})
16
+ else:
17
+ resp = self.bob.session.get(url=f"{self.bob.base_url}company/named-lists")
18
+ resp.raise_for_status()
19
+ data = resp.json()
20
+ for list_key, list_data in data.items():
21
+ values.update({list_key: [value['id'] for value in list_data['values']]})
22
+
23
+ return values
@@ -0,0 +1,36 @@
1
+ from datetime import datetime
2
+ import pandas as pd
3
+ from brynq_sdk_functions import Functions
4
+ from .schemas.custom_tables import CustomTableSchema
5
+
6
+
7
+ class CustomTables:
8
+ def __init__(self, bob):
9
+ self.bob = bob
10
+
11
+ def get(self, employee_id: str, custom_table_id: str) -> (pd.DataFrame, pd.DataFrame):
12
+ """
13
+ Get custom table data for an employee
14
+
15
+ Args:
16
+ employee_id: The employee ID
17
+ custom_table_id: The custom table ID
18
+
19
+ Returns:
20
+ A tuple of (valid_data, invalid_data) as pandas DataFrames
21
+ """
22
+ resp = self.bob.session.get(url=f"{self.bob.base_url}people/custom-tables/{employee_id}/{custom_table_id}")
23
+ resp.raise_for_status()
24
+ data = resp.json()
25
+
26
+ # Normalize the nested JSON response
27
+ df = pd.json_normalize(
28
+ data,
29
+ record_path=['values']
30
+ )
31
+
32
+ df['employee_id'] = employee_id
33
+ df = self.bob.rename_camel_columns_to_snake_case(df)
34
+ valid_data, invalid_data = Functions.validate_data(df=df, schema=CustomTableSchema, debug=True)
35
+
36
+ return valid_data, invalid_data
@@ -0,0 +1,46 @@
1
+ from datetime import datetime
2
+ from io import BytesIO
3
+
4
+ import pandas as pd
5
+ from brynq_sdk_functions import Functions
6
+
7
+
8
+ class CustomDocuments:
9
+ def __init__(self, bob):
10
+ self.bob = bob
11
+ # self.headers_upload = self.bob.headers.copy()
12
+ # self.headers_upload['Content-Type'] = 'multipart/form-data'
13
+ # self.headers_upload['Accept'] = 'application/json'
14
+
15
+ def get(self, person_id: datetime) -> pd.DataFrame:
16
+ resp = self.bob.session.get(url=f"{self.bob.base_url}docs/people/{person_id}")
17
+ resp.raise_for_status()
18
+ data = resp.json()['documents']
19
+ df = pd.DataFrame(data)
20
+ # data = self.bob.get_paginated_result(request)
21
+ # df = pd.json_normalize(
22
+ # data,
23
+ # record_path='changes',
24
+ # meta=['employeeId']
25
+ # )
26
+ df = self.bob.rename_camel_columns_to_snake_case(df)
27
+ # valid_documents, invalid_documents = Functions.validate_data(df=df, schema=DocumentsSchema, debug=True)
28
+
29
+ return df
30
+
31
+ def get_folders(self) -> dict:
32
+ resp = self.bob.session.get(url=f"{self.bob.base_url}docs/folders/metadata")
33
+ resp.raise_for_status()
34
+ data = resp.json()
35
+
36
+ return data
37
+
38
+ def create(self,
39
+ person_id: datetime,
40
+ folder_id: str,
41
+ file_name: str,
42
+ file_object: BytesIO):
43
+ files = {"file": (file_name, file_object, "application/pdf")}
44
+ resp = self.bob.session.post(url=f"{self.bob.base_url}docs/people/{person_id}/folders/{folder_id}/upload",
45
+ files=files)
46
+ resp.raise_for_status()
@@ -0,0 +1,23 @@
1
+ import pandas as pd
2
+ import requests
3
+ from .schemas.employment import EmploymentSchema
4
+ from brynq_sdk_functions import Functions
5
+
6
+
7
+ class Employment:
8
+ def __init__(self, bob):
9
+ self.bob = bob
10
+
11
+ def get(self) -> (pd.DataFrame, pd.DataFrame):
12
+ request = requests.Request(method='GET',
13
+ url=f"{self.bob.base_url}bulk/people/employment")
14
+ data = self.bob.get_paginated_result(request)
15
+ df = pd.json_normalize(
16
+ data,
17
+ record_path='values',
18
+ meta=['employeeId']
19
+ )
20
+ df = self.bob.rename_camel_columns_to_snake_case(df)
21
+ valid_contracts, invalid_contracts = Functions.validate_data(df=df, schema=EmploymentSchema, debug=True)
22
+
23
+ return valid_contracts, invalid_contracts
@@ -0,0 +1,37 @@
1
+ from datetime import datetime
2
+ import pandas as pd
3
+ from brynq_sdk_functions import Functions
4
+ from .schemas.named_lists import NamedListSchema
5
+
6
+
7
+ class NamedLists:
8
+ def __init__(self, bob):
9
+ self.bob = bob
10
+
11
+ def get(self) -> (pd.DataFrame, pd.DataFrame):
12
+ """
13
+ Get custom table data for an employee
14
+
15
+ Args:
16
+ list_name: The list name
17
+
18
+ Returns:
19
+ A tuple of (valid_data, invalid_data) as pandas DataFrames
20
+ """
21
+ url = f"{self.bob.base_url}company/named-lists/"
22
+ resp = self.bob.session.get(url=url)
23
+ resp.raise_for_status()
24
+ data = resp.json()
25
+
26
+ df = pd.DataFrame([
27
+ {**item, "type": key}
28
+ for key, group in data.items()
29
+ for item in group["values"]
30
+ ])
31
+
32
+ # Normalize the nested JSON response
33
+ # df = pd.DataFrame(data.get('values'))
34
+ df = self.bob.rename_camel_columns_to_snake_case(df)
35
+ valid_data, invalid_data = Functions.validate_data(df=df, schema=NamedListSchema, debug=True)
36
+
37
+ return valid_data, invalid_data
@@ -0,0 +1,23 @@
1
+ from datetime import datetime
2
+ import pandas as pd
3
+ from brynq_sdk_functions import Functions
4
+ from .schemas.payments import VariablePaymentSchema
5
+
6
+
7
+ class Payments:
8
+ def __init__(self, bob):
9
+ self.bob = bob
10
+
11
+ def get(self, person_id: str) -> (pd.DataFrame, pd.DataFrame):
12
+ resp = self.bob.session.get(url=f"{self.bob.base_url}people/{person_id}/variable")
13
+ resp.raise_for_status()
14
+ data = resp.json()
15
+ df = pd.json_normalize(
16
+ data,
17
+ record_path='values'
18
+ )
19
+ df['employee_id'] = person_id
20
+ df = self.bob.rename_camel_columns_to_snake_case(df)
21
+ valid_payments, invalid_payments = Functions.validate_data(df=df, schema=VariablePaymentSchema, debug=True)
22
+
23
+ return valid_payments, invalid_payments
@@ -0,0 +1,61 @@
1
+ import pandas as pd
2
+ from brynq_sdk_functions import Functions
3
+ from .bank import Bank
4
+ from .employment import Employment
5
+ from .salaries import Salaries
6
+ from .schemas.people import PeopleSchema
7
+ from .work import Work
8
+ from .custom_tables import CustomTables
9
+
10
+
11
+ class People:
12
+ def __init__(self, bob):
13
+ self.bob = bob
14
+ self.salaries = Salaries(bob)
15
+ self.employment = Employment(bob)
16
+ self.bank = Bank(bob)
17
+ self.work = Work(bob)
18
+ self.custom_tables = CustomTables(bob)
19
+
20
+ def get(self) -> pd.DataFrame:
21
+ resp = self.bob.session.get(url=f"{self.bob.base_url}profiles")
22
+ # Bob sucks with default fields so you need to do a search call to retrieve additional fields.
23
+ additional_fields = [
24
+ "personal.birthDate",
25
+ "address.city",
26
+ "address.postCode",
27
+ "address.line1",
28
+ "address.line2",
29
+ "address.activeEffectiveDate",
30
+ "address.country",
31
+ # "home.legalGender",
32
+ "home.spouse.firstName",
33
+ "home.spouse.surname",
34
+ # "home.spouse.birthDate",
35
+ "home.spouse.gender",
36
+ "internal.terminationReason",
37
+ "internal.terminationDate",
38
+ "internal.terminationType",
39
+ "employee.lastDayOfWork",
40
+ # housenumber addition
41
+ "address.customColumns.column_1740046184782",
42
+ # contract end date (bob only fills this when you get a new contract normally)
43
+ "payroll.employment.customColumns.column_1680013460318",
44
+ # iban
45
+ "financial.iban"
46
+ # ploegentoeslag
47
+ ]
48
+ resp_additional_fields = self.bob.session.post(url=f"{self.bob.base_url}people/search",
49
+ json={
50
+ "fields": ["root.id"] + additional_fields,
51
+ "filters": []
52
+ })
53
+ df_extra_fields = pd.json_normalize(resp_additional_fields.json()['employees'])
54
+ resp.raise_for_status()
55
+ data = resp.json()
56
+ df = pd.json_normalize(data['employees'])
57
+ df = pd.merge(df, df_extra_fields[["id"] + additional_fields], left_on='id', right_on='id')
58
+ df = self.bob.rename_camel_columns_to_snake_case(df)
59
+ valid_people, invalid_people = Functions.validate_data(df=df, schema=PeopleSchema, debug=True)
60
+
61
+ return valid_people, invalid_people
@@ -0,0 +1,24 @@
1
+ import pandas as pd
2
+ import requests
3
+ from brynq_sdk_functions import Functions
4
+ from .schemas.salary import SalarySchema
5
+
6
+
7
+ class Salaries:
8
+ def __init__(self, bob):
9
+ self.bob = bob
10
+
11
+ def get(self) -> (pd.DataFrame, pd.DataFrame):
12
+ request = requests.Request(method='GET',
13
+ url=f"{self.bob.base_url}bulk/people/salaries",
14
+ params={"limit": 100})
15
+ data = self.bob.get_paginated_result(request)
16
+ df = pd.json_normalize(
17
+ data,
18
+ record_path='values',
19
+ meta=['employeeId']
20
+ )
21
+ df = self.bob.rename_camel_columns_to_snake_case(df)
22
+ valid_salaries, invalid_salaries = Functions.validate_data(df=df, schema=SalarySchema, debug=True)
23
+
24
+ return valid_salaries, invalid_salaries
File without changes
@@ -0,0 +1,20 @@
1
+ import pandera as pa
2
+ from pandera.typing import Series, String
3
+ import pandas as pd
4
+
5
+
6
+ class BankSchema(pa.DataFrameModel):
7
+ id: Series[pd.Int64Dtype] = pa.Field(coerce=True)
8
+ employee_id: Series[pd.Int64Dtype] = pa.Field(coerce=True)
9
+ amount: Series[pd.Int64Dtype] = pa.Field(coerce=True)
10
+ allocation: Series[String] = pa.Field(coerce=True, nullable=True)
11
+ branch_address: Series[String] = pa.Field(coerce=True, nullable=True)
12
+ bank_name: Series[String] = pa.Field(coerce=True, nullable=True)
13
+ account_number: Series[String] = pa.Field(coerce=True, nullable=True)
14
+ routing_number: Series[String] = pa.Field(coerce=True, nullable=True)
15
+ bank_account_type: Series[String] = pa.Field(coerce=True, nullable=True)
16
+ bic_or_swift: Series[String] = pa.Field(coerce=True, nullable=True)
17
+ changed_by: Series[String] = pa.Field(coerce=True, nullable=True)
18
+ iban: Series[String] = pa.Field(coerce=True)
19
+ account_nickname: Series[String] = pa.Field(coerce=True, nullable=True)
20
+ use_for_bonus: Series[pd.BooleanDtype] = pa.Field(coerce=True, nullable=True)
@@ -0,0 +1,10 @@
1
+ import pandera as pa
2
+ from pandera.typing import Series
3
+ import pandas as pd
4
+
5
+ class CustomTableSchema(pa.DataFrameModel):
6
+ id: Series[pd.Int64Dtype] = pa.Field(coerce=True)
7
+ employee_id: Series[pd.Int64Dtype] = pa.Field(coerce=True)
8
+
9
+ class Config:
10
+ coerce = True
@@ -0,0 +1,28 @@
1
+ import pandas as pd
2
+ import pandera as pa
3
+ from pandera import Bool
4
+ from pandera.typing import Series, String, Float, DateTime
5
+
6
+
7
+ class EmploymentSchema(pa.DataFrameModel):
8
+ id: Series[pd.Int64Dtype] = pa.Field(coerce=True)
9
+ employee_id: Series[pd.Int64Dtype] = pa.Field(coerce=True)
10
+ active_effective_date: Series[DateTime] = pa.Field(coerce=True)
11
+ contract: Series[String] = pa.Field(coerce=True, nullable=True) # has a list of possible values
12
+ creation_date: Series[DateTime] = pa.Field(coerce=True, nullable=True)
13
+ effective_date: Series[DateTime] = pa.Field(coerce=True)
14
+ end_effective_date: Series[DateTime] = pa.Field(coerce=True, nullable=True)
15
+ fte: Series[Float] = pa.Field(coerce=True)
16
+ is_current: Series[Bool] = pa.Field(coerce=True)
17
+ modification_date: Series[DateTime] = pa.Field(coerce=True, nullable=True)
18
+ salary_pay_type: Series[String] = pa.Field(coerce=True, nullable=True)
19
+ weekly_hours: Series[Float] = pa.Field(coerce=True, nullable=True)
20
+ # weekly_hours_sort_factor: Series[pd.Int64Dtype] = pa.Field(coerce=True, nullable=False)
21
+ actual_working_pattern_working_pattern_type: Series[pa.String] = pa.Field(nullable=True)
22
+ actual_working_pattern_days_sunday: Series[Float] = pa.Field(nullable=True)
23
+ actual_working_pattern_days_tuesday: Series[Float] = pa.Field(nullable=True)
24
+ actual_working_pattern_days_wednesday: Series[Float] = pa.Field(nullable=True)
25
+ actual_working_pattern_days_monday: Series[Float] = pa.Field(nullable=True)
26
+ actual_working_pattern_days_friday: Series[Float] = pa.Field(nullable=True)
27
+ actual_working_pattern_days_thursday: Series[Float] = pa.Field(nullable=True)
28
+ actual_working_pattern_days_saturday: Series[Float] = pa.Field(nullable=True)
@@ -0,0 +1,13 @@
1
+ import pandera as pa
2
+ from pandera.typing import Series
3
+
4
+ class NamedListSchema(pa.DataFrameModel):
5
+ id: Series[str] = pa.Field(coerce=True)
6
+ value: Series[str] = pa.Field(coerce=True)
7
+ name: Series[str] = pa.Field(coerce=True)
8
+ archived: Series[bool] = pa.Field(coerce=True)
9
+ # children: Series[list] = pa.Field(coerce=True)
10
+ type: Series[str] = pa.Field(coerce=True)
11
+
12
+ class Config:
13
+ coerce = True
@@ -0,0 +1,30 @@
1
+ import pandera as pa
2
+ from pandera.typing import Series, String, Float, DateTime
3
+ import pandas as pd
4
+
5
+ class VariablePaymentSchema(pa.DataFrameModel):
6
+ can_be_deleted: Series[bool] = pa.Field(nullable=True, coerce=True)
7
+ department_percent: Series[Float] = pa.Field(nullable=True, coerce=True)
8
+ payout_type: Series[String] = pa.Field(coerce=True)
9
+ num_of_salaries: Series[pd.Int64Dtype] = pa.Field(nullable=True, coerce=True)
10
+ end_date: Series[DateTime] = pa.Field(nullable=True, coerce=True)
11
+ creation_date: Series[DateTime] = pa.Field(coerce=True)
12
+ percentage_of_annual_salary: Series[Float] = pa.Field(nullable=True, coerce=True)
13
+ individual_percent: Series[Float] = pa.Field(nullable=True, coerce=True)
14
+ variable_type: Series[String] = pa.Field(nullable=True, coerce=True)
15
+ is_current: Series[bool] = pa.Field(nullable=True, coerce=True)
16
+ modification_date: Series[DateTime] = pa.Field(nullable=True, coerce=True)
17
+ company_percent: Series[Float] = pa.Field(nullable=True, coerce=True)
18
+ id: Series[pd.Int64Dtype] = pa.Field(coerce=True)
19
+ end_effective_date: Series[DateTime] = pa.Field(nullable=True, coerce=True)
20
+ payment_period: Series[String] = pa.Field(coerce=True)
21
+ effective_date: Series[DateTime] = pa.Field(coerce=True)
22
+ amount_value: Series[Float] = pa.Field(coerce=True)
23
+ amount_currency: Series[String] = pa.Field(coerce=True)
24
+ change_reason: Series[String] = pa.Field(nullable=True, coerce=True)
25
+ change_changed_by: Series[String] = pa.Field(nullable=True, coerce=True)
26
+ change_changed_by_id: Series[pd.Int64Dtype] = pa.Field(nullable=True, coerce=True)
27
+ employee_id: Series[pd.Int64Dtype] = pa.Field(coerce=True)
28
+
29
+ class Config:
30
+ coerce = True
@@ -0,0 +1,87 @@
1
+ from datetime import datetime
2
+
3
+ import pandas as pd
4
+ import pandera as pa
5
+ from pandera import Bool
6
+ from pandera.typing import Series, String, Float, DateTime
7
+ import pandera.extensions as extensions
8
+
9
+
10
+
11
+ @extensions.register_check_method()
12
+ def check_list(x):
13
+ return isinstance(x, list)
14
+
15
+
16
+ class PeopleSchema(pa.DataFrameModel):
17
+ id: Series[pd.Int64Dtype] = pa.Field(coerce=True)
18
+ display_name: Series[String] = pa.Field(coerce=True, nullable=True)
19
+ company_id: Series[String] = pa.Field(coerce=True)
20
+ email: Series[String] = pa.Field(coerce=True, nullable=True)
21
+ surname: Series[String] = pa.Field(coerce=True)
22
+ first_name: Series[String] = pa.Field(coerce=True, nullable=True)
23
+ personal_birth_date: Series[DateTime] = pa.Field(coerce=True)
24
+ personal_pronouns: Series[String] = pa.Field(coerce=True, nullable=True)
25
+ personal_honorific: Series[String] = pa.Field(coerce=True, nullable=True)
26
+ personal_nationality: Series[object] = pa.Field(coerce=True, check_name=check_list)
27
+ # employee_payroll_manager: Series[String] = pa.Field(coerce=True, nullable=True)
28
+ # employee_hrbp: Series[String] = pa.Field(coerce=True, nullable=True)
29
+ # employee_it_admin: Series[String] = pa.Field(coerce=True, nullable=True)
30
+ # employee_buddy: Series[String] = pa.Field(coerce=True, nullable=True)
31
+ employee_veteran_status: Series[object] = pa.Field(coerce=True, check_name=check_list)
32
+ employee_disability_status: Series[String] = pa.Field(coerce=True, nullable=True)
33
+ work_start_date: Series[DateTime] = pa.Field(coerce=True, nullable=True)
34
+ work_manager: Series[String] = pa.Field(coerce=True, nullable=True)
35
+ work_work_phone: Series[String] = pa.Field(coerce=True, nullable=True)
36
+ work_tenure_duration_period_i_s_o: Series[String] = pa.Field(coerce=True, nullable=True)
37
+ work_tenure_duration_sort_factor: Series[pd.Int64Dtype] = pa.Field(coerce=True, nullable=False)
38
+ work_tenure_duration_humanize: Series[String] = pa.Field(coerce=True, nullable=True)
39
+ work_duration_of_employment_period_i_s_o: Series[String] = pa.Field(coerce=True, nullable=True)
40
+ work_duration_of_employment_sort_factor: Series[String] = pa.Field(coerce=True, nullable=False)
41
+ work_duration_of_employment_humanize: Series[String] = pa.Field(coerce=True, nullable=True)
42
+ work_reports_to_id_in_company: Series[pd.Int64Dtype] = pa.Field(coerce=True, nullable=True)
43
+ work_employee_id_in_company: Series[pd.Int64Dtype] = pa.Field(coerce=True, nullable=True)
44
+ work_reports_to_display_name: Series[String] = pa.Field(coerce=True, nullable=True)
45
+ work_reports_to_email: Series[String] = pa.Field(coerce=True, nullable=True)
46
+ work_reports_to_surname: Series[String] = pa.Field(coerce=True, nullable=True)
47
+ work_reports_to_first_name: Series[String] = pa.Field(coerce=True, nullable=True)
48
+ work_reports_to_id: Series[pd.Int64Dtype] = pa.Field(coerce=True, nullable=True)
49
+ work_work_mobile: Series[String] = pa.Field(coerce=True, nullable=True)
50
+ work_indirect_reports: Series[pd.Int64Dtype] = pa.Field(coerce=True, nullable=True)
51
+ work_site_id: Series[pd.Int64Dtype] = pa.Field(coerce=True, nullable=True)
52
+ work_department: Series[String] = pa.Field(coerce=True, nullable=True)
53
+ work_tenure_duration_years: Series[Float] = pa.Field(coerce=True, nullable=True)
54
+ work_tenure_years: Series[pd.Int64Dtype] = pa.Field(coerce=True, nullable=True)
55
+ work_is_manager: Series[Bool] = pa.Field(coerce=True)
56
+ work_title: Series[String] = pa.Field(coerce=True, nullable=True)
57
+ work_site: Series[String] = pa.Field(coerce=True, nullable=True)
58
+ work_original_start_date: Series[DateTime] = pa.Field(coerce=True, nullable=True)
59
+ work_active_effective_date: Series[DateTime] = pa.Field(coerce=True, nullable=True)
60
+ work_direct_reports: Series[pd.Int64Dtype] = pa.Field(coerce=True, nullable=True)
61
+ # work_work_change_type: Series[String] = pa.Field(coerce=True, nullable=True)
62
+ work_second_level_manager: Series[pd.Int64Dtype] = pa.Field(coerce=True, nullable=True)
63
+ work_days_of_previous_service: Series[pd.Int64Dtype] = pa.Field(coerce=True, nullable=True)
64
+ work_years_of_service: Series[Float] = pa.Field(coerce=True, nullable=True)
65
+ about_food_preferences: Series[object] = pa.Field(coerce=True, check_name=check_list)
66
+ # about_social_data_linkedin: Series[String] = pa.Field(coerce=True, nullable=True)
67
+ about_social_data_twitter: Series[String] = pa.Field(coerce=True, nullable=True)
68
+ about_social_data_facebook: Series[String] = pa.Field(coerce=True, nullable=True)
69
+ about_superpowers: Series[object] = pa.Field(coerce=True, check_name=check_list)
70
+ about_hobbies: Series[object] = pa.Field(coerce=True, check_name=check_list)
71
+ about_about: Series[String] = pa.Field(coerce=True, nullable=True)
72
+ about_avatar: Series[String] = pa.Field(coerce=True, nullable=True)
73
+ address_city: Series[String] = pa.Field(coerce=True, nullable=True)
74
+ address_post_code: Series[String] = pa.Field(coerce=True, nullable=True)
75
+ address_line1: Series[String] = pa.Field(coerce=True, nullable=True)
76
+ address_line2: Series[String] = pa.Field(coerce=True, nullable=True)
77
+ # address_country: Series[String] = pa.Field(coerce=True, nullable=True)
78
+ address_active_effective_date: Series[DateTime] = pa.Field(coerce=True, nullable=True)
79
+ # home_legal_gender: Series[String] = pa.Field(coerce=True, nullable=True)
80
+ home_spouse_first_name: Series[String] = pa.Field(coerce=True, nullable=True)
81
+ home_spouse_surname: Series[String] = pa.Field(coerce=True, nullable=True)
82
+ # home_spouse_birth_date: Series[DateTime] = pa.Field(coerce=True, nullable=True)
83
+ home_spouse_gender: Series[String] = pa.Field(coerce=True, nullable=True)
84
+ internal_termination_reason: Series[String] = pa.Field(coerce=True, nullable=True)
85
+ internal_termination_date: Series[DateTime] = pa.Field(coerce=True, nullable=True)
86
+ internal_termination_type: Series[String] = pa.Field(coerce=True, nullable=True)
87
+ employee_last_day_of_work: Series[DateTime] = pa.Field(coerce=True, nullable=True)
@@ -0,0 +1,21 @@
1
+ import pandas as pd
2
+ import pandera as pa
3
+ from pandera import Bool
4
+ from pandera.typing import Series, String, Float, DateTime
5
+ import pandera.extensions as extensions
6
+
7
+
8
+ class SalarySchema(pa.DataFrameModel):
9
+ id: Series[pd.Int64Dtype] = pa.Field(coerce=True)
10
+ employee_id: Series[pd.Int64Dtype] = pa.Field(coerce=True)
11
+ pay_frequency: Series[String] = pa.Field(coerce=True, nullable=True) # has a list of possible values , isin=['Monthly']
12
+ creation_date: Series[DateTime] = pa.Field(coerce=True, nullable=True)
13
+ is_current: Series[Bool] = pa.Field(coerce=True)
14
+ modification_date: Series[DateTime] = pa.Field(coerce=True, nullable=True)
15
+ effective_date: Series[DateTime] = pa.Field(coerce=True)
16
+ end_effective_date: Series[DateTime] = pa.Field(coerce=True, nullable=True)
17
+ change_reason: Series[str] = pa.Field(coerce=True, nullable=True)
18
+ pay_period: Series[String] = pa.Field(coerce=True, nullable=True)
19
+ base_value: Series[Float] = pa.Field(coerce=True, nullable=True)
20
+ base_currency: Series[String] = pa.Field(coerce=True, isin=['EUR', 'USD'])
21
+ active_effective_date: Series[DateTime] = pa.Field(coerce=True)
@@ -0,0 +1,28 @@
1
+ import pandera as pa
2
+ from pandera.typing import Series, String, Float
3
+ import pandas as pd
4
+
5
+ class TimeOffSchema(pa.DataFrameModel):
6
+ change_type: Series[String] = pa.Field(coerce=True)
7
+ employee_id: Series[String] = pa.Field(coerce=True)
8
+ employee_display_name: Series[String] = pa.Field(coerce=True)
9
+ employee_email: Series[String] = pa.Field(coerce=True)
10
+ request_id: Series[pd.Int64Dtype] = pa.Field(coerce=True)
11
+ policy_type_display_name: Series[String] = pa.Field(coerce=True)
12
+ type: Series[String] = pa.Field(coerce=True)
13
+ start_date: Series[String] = pa.Field(coerce=True)
14
+ start_portion: Series[String] = pa.Field(coerce=True)
15
+ end_date: Series[String] = pa.Field(coerce=True)
16
+ end_portion: Series[String] = pa.Field(coerce=True)
17
+ day_portion: Series[String] = pa.Field(coerce=True)
18
+ date: Series[String] = pa.Field(coerce=True)
19
+ hours_on_date: Series[Float] = pa.Field(coerce=True)
20
+ daily_hours: Series[Float] = pa.Field(coerce=True)
21
+ duration_unit: Series[String] = pa.Field(coerce=True)
22
+ total_duration: Series[Float] = pa.Field(coerce=True)
23
+ total_cost: Series[Float] = pa.Field(coerce=True)
24
+ change_reason: Series[String] = pa.Field(nullable=True, coerce=True)
25
+ visibility: Series[String] = pa.Field(coerce=True)
26
+
27
+ class Config:
28
+ coerce = True
@@ -0,0 +1,29 @@
1
+ import pandera as pa
2
+ from pandera.typing import Series
3
+ import pandas as pd
4
+ from datetime import datetime
5
+
6
+ class WorkSchema(pa.DataFrameModel):
7
+ can_be_deleted: Series[pa.Bool] = pa.Field(coerce=True)
8
+ work_change_type: Series[str] = pa.Field(coerce=True)
9
+ creation_date: Series[datetime] = pa.Field(coerce=True, nullable=True)
10
+ title: Series[str] = pa.Field(coerce=True, nullable=True)
11
+ is_current: Series[pa.Bool] = pa.Field(coerce=True)
12
+ modification_date: Series[datetime] = pa.Field(coerce=True, nullable=True)
13
+ site: Series[str] = pa.Field(coerce=True, nullable=True)
14
+ site_id: Series[pd.Int64Dtype] = pa.Field(coerce=True)
15
+ id: Series[pd.Int64Dtype] = pa.Field(coerce=True)
16
+ end_effective_date: Series[datetime] = pa.Field(coerce=True, nullable=True)
17
+ active_effective_date: Series[datetime] = pa.Field(coerce=True, nullable=True)
18
+ department: Series[str] = pa.Field(coerce=True, nullable=True)
19
+ effective_date: Series[datetime] = pa.Field(coerce=True, nullable=True)
20
+ change_reason: Series[str] = pa.Field(coerce=True, nullable=True)
21
+ change_changed_by: Series[str] = pa.Field(coerce=True, nullable=True)
22
+ change_changed_by_id: Series[str] = pa.Field(coerce=True, nullable=True)
23
+ reports_to_id: Series[str] = pa.Field(coerce=True, nullable=True)
24
+ reports_to_first_name: Series[str] = pa.Field(coerce=True, nullable=True)
25
+ reports_to_surname: Series[str] = pa.Field(coerce=True, nullable=True)
26
+ reports_to_email: Series[str] = pa.Field(coerce=True, nullable=True)
27
+ reports_to_display_name: Series[str] = pa.Field(coerce=True, nullable=True)
28
+ reports_to: Series[pd.Int64Dtype] = pa.Field(coerce=True, nullable=True)
29
+ employee_id: Series[pd.Int64Dtype] = pa.Field(coerce=True)
@@ -0,0 +1,25 @@
1
+ from datetime import datetime, timezone
2
+ import pandas as pd
3
+ from brynq_sdk_functions import Functions
4
+ from .schemas.timeoff import TimeOffSchema
5
+
6
+
7
+ class TimeOff:
8
+ def __init__(self, bob):
9
+ self.bob = bob
10
+
11
+ def get(self, since: datetime) -> (pd.DataFrame, pd.DataFrame):
12
+ resp = self.bob.session.get(url=f"{self.bob.base_url}timeoff/requests/changes",
13
+ params={'since': since.replace(tzinfo=timezone.utc).isoformat(timespec='milliseconds')})
14
+ resp.raise_for_status()
15
+ data = resp.json()['changes']
16
+ # data = self.bob.get_paginated_result(request)
17
+ df = pd.json_normalize(
18
+ data,
19
+ record_path='changes',
20
+ meta=['employeeId']
21
+ )
22
+ df = self.bob.rename_camel_columns_to_snake_case(df)
23
+ valid_timeoff, invalid_timeoff = Functions.validate_data(df=df, schema=TimeOffSchema, debug=True)
24
+
25
+ return valid_timeoff, invalid_timeoff
@@ -0,0 +1,24 @@
1
+ import pandas as pd
2
+ import requests
3
+ from brynq_sdk_functions import Functions
4
+ from .schemas.work import WorkSchema
5
+
6
+
7
+ class Work:
8
+ def __init__(self, bob):
9
+ self.bob = bob
10
+
11
+ def get(self) ->(pd.DataFrame, pd.DataFrame):
12
+ request = requests.Request(method='GET',
13
+ url=f"{self.bob.base_url}bulk/people/work")
14
+ data = self.bob.get_paginated_result(request)
15
+ df = pd.json_normalize(
16
+ data,
17
+ record_path='values',
18
+ meta=['employeeId']
19
+ )
20
+ df = self.bob.rename_camel_columns_to_snake_case(df)
21
+
22
+ valid_work, invalid_work = Functions.validate_data(df=df, schema=WorkSchema, debug=True)
23
+
24
+ return valid_work, invalid_work
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 1.0
2
2
  Name: brynq-sdk-bob
3
- Version: 0.0.6
3
+ Version: 1.1.0
4
4
  Summary: Bob wrapper from BrynQ
5
5
  Home-page: UNKNOWN
6
6
  Author: BrynQ
@@ -0,0 +1,29 @@
1
+ setup.py
2
+ brynq_sdk_bob/__init__.py
3
+ brynq_sdk_bob/bank.py
4
+ brynq_sdk_bob/company.py
5
+ brynq_sdk_bob/custom_tables.py
6
+ brynq_sdk_bob/documents.py
7
+ brynq_sdk_bob/employment.py
8
+ brynq_sdk_bob/named_lists.py
9
+ brynq_sdk_bob/payments.py
10
+ brynq_sdk_bob/people.py
11
+ brynq_sdk_bob/salaries.py
12
+ brynq_sdk_bob/timeoff.py
13
+ brynq_sdk_bob/work.py
14
+ brynq_sdk_bob.egg-info/PKG-INFO
15
+ brynq_sdk_bob.egg-info/SOURCES.txt
16
+ brynq_sdk_bob.egg-info/dependency_links.txt
17
+ brynq_sdk_bob.egg-info/not-zip-safe
18
+ brynq_sdk_bob.egg-info/requires.txt
19
+ brynq_sdk_bob.egg-info/top_level.txt
20
+ brynq_sdk_bob/schemas/__init__.py
21
+ brynq_sdk_bob/schemas/bank.py
22
+ brynq_sdk_bob/schemas/custom_tables.py
23
+ brynq_sdk_bob/schemas/employment.py
24
+ brynq_sdk_bob/schemas/named_lists.py
25
+ brynq_sdk_bob/schemas/payments.py
26
+ brynq_sdk_bob/schemas/people.py
27
+ brynq_sdk_bob/schemas/salary.py
28
+ brynq_sdk_bob/schemas/timeoff.py
29
+ brynq_sdk_bob/schemas/work.py
@@ -0,0 +1 @@
1
+ brynq_sdk_bob
@@ -2,7 +2,7 @@ from setuptools import setup, find_namespace_packages
2
2
 
3
3
  setup(
4
4
  name='brynq_sdk_bob',
5
- version='0.0.6',
5
+ version='1.1.0',
6
6
  description='Bob wrapper from BrynQ',
7
7
  long_description='Bob wrapper from BrynQ',
8
8
  author='BrynQ',
@@ -1,7 +0,0 @@
1
- setup.py
2
- brynq_sdk_bob.egg-info/PKG-INFO
3
- brynq_sdk_bob.egg-info/SOURCES.txt
4
- brynq_sdk_bob.egg-info/dependency_links.txt
5
- brynq_sdk_bob.egg-info/not-zip-safe
6
- brynq_sdk_bob.egg-info/requires.txt
7
- brynq_sdk_bob.egg-info/top_level.txt
File without changes