brynq-sdk-bob 2.9.5__tar.gz → 2.10.1__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 (41) hide show
  1. {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/PKG-INFO +4 -1
  2. {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/__init__.py +3 -4
  3. {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/bank.py +7 -7
  4. {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/company.py +4 -4
  5. {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/custom_tables.py +8 -8
  6. brynq_sdk_bob-2.10.1/brynq_sdk_bob/documents.py +37 -0
  7. {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/employment.py +8 -7
  8. brynq_sdk_bob-2.10.1/brynq_sdk_bob/entitlements.py +37 -0
  9. {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/named_lists.py +8 -9
  10. {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/payments.py +14 -13
  11. {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/payroll_history.py +16 -17
  12. {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/people.py +15 -16
  13. brynq_sdk_bob-2.10.1/brynq_sdk_bob/reports.py +41 -0
  14. brynq_sdk_bob-2.10.1/brynq_sdk_bob/salaries.py +38 -0
  15. {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/schemas/bank.py +2 -2
  16. {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/schemas/custom_tables.py +2 -2
  17. {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/schemas/employment.py +9 -9
  18. brynq_sdk_bob-2.10.1/brynq_sdk_bob/schemas/entitlements.py +110 -0
  19. {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/schemas/named_lists.py +1 -1
  20. {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/schemas/payments.py +3 -2
  21. {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/schemas/payroll_history.py +1 -1
  22. {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/schemas/people.py +1 -1
  23. brynq_sdk_bob-2.10.1/brynq_sdk_bob/schemas/reports.py +129 -0
  24. {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/schemas/salary.py +3 -4
  25. {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/schemas/timeoff.py +2 -2
  26. {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/schemas/work.py +1 -1
  27. {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/timeoff.py +13 -13
  28. {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/work.py +8 -7
  29. {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob.egg-info/PKG-INFO +4 -1
  30. {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob.egg-info/SOURCES.txt +3 -0
  31. brynq_sdk_bob-2.10.1/brynq_sdk_bob.egg-info/requires.txt +5 -0
  32. {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/setup.py +4 -1
  33. brynq_sdk_bob-2.9.5/brynq_sdk_bob/documents.py +0 -47
  34. brynq_sdk_bob-2.9.5/brynq_sdk_bob/reports.py +0 -38
  35. brynq_sdk_bob-2.9.5/brynq_sdk_bob/salaries.py +0 -38
  36. brynq_sdk_bob-2.9.5/brynq_sdk_bob.egg-info/requires.txt +0 -2
  37. {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/schemas/__init__.py +0 -0
  38. {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob.egg-info/dependency_links.txt +0 -0
  39. {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob.egg-info/not-zip-safe +0 -0
  40. {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob.egg-info/top_level.txt +0 -0
  41. {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/setup.cfg +0 -0
@@ -1,12 +1,15 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brynq_sdk_bob
3
- Version: 2.9.5
3
+ Version: 2.10.1
4
4
  Summary: Bob wrapper from BrynQ
5
5
  Author: BrynQ
6
6
  Author-email: support@brynq.com
7
7
  License: BrynQ License
8
8
  Requires-Dist: brynq-sdk-brynq<5,>=4
9
+ Requires-Dist: brynq-sdk-functions>=2.0.5
9
10
  Requires-Dist: pandas<3.0.0,>=2.2.0
11
+ Requires-Dist: pydantic<3.0.0,>=2.5.0
12
+ Requires-Dist: pandera<1.0.0,>=0.16.0
10
13
  Dynamic: author
11
14
  Dynamic: author-email
12
15
  Dynamic: description
@@ -1,16 +1,12 @@
1
1
  import base64
2
2
  import os
3
3
  from typing import List, Literal, Optional
4
-
5
4
  import requests
6
5
  from requests.adapters import HTTPAdapter
7
6
  from urllib3.util.retry import Retry
8
-
9
7
  from brynq_sdk_brynq import BrynQ
10
-
11
8
  from .bank import Bank
12
9
  from .company import Company
13
- from .custom_tables import CustomTables
14
10
  from .documents import CustomDocuments
15
11
  from .employment import Employment
16
12
  from .named_lists import NamedLists
@@ -21,6 +17,8 @@ from .reports import Reports
21
17
  from .salaries import Salaries
22
18
  from .timeoff import TimeOff
23
19
  from .work import Work
20
+ from .custom_tables import CustomTables
21
+ from .entitlements import Entitlements
24
22
 
25
23
 
26
24
  class Bob(BrynQ):
@@ -57,6 +55,7 @@ class Bob(BrynQ):
57
55
  self.custom_tables = CustomTables(self)
58
56
  self.payroll_history = History(self)
59
57
  self.reports = Reports(self)
58
+ self.entitlements = Entitlements(self)
60
59
  self.data_interface_id = os.getenv("DATA_INTERFACE_ID")
61
60
  self.debug = debug
62
61
 
@@ -1,17 +1,17 @@
1
1
  import pandas as pd
2
2
  from brynq_sdk_functions import Functions
3
- from .schemas.bank import BankSchema
3
+ from .schemas.bank import BankGet
4
4
 
5
5
 
6
6
  class Bank:
7
- def __init__(self, bob):
8
- self.bob = bob
9
- self.schema = BankSchema
7
+ def __init__(self, client):
8
+ self.client = client
9
+ self.schema = BankGet
10
10
 
11
- def get(self, person_ids: pd.Series, field_selection: list[str] = []) -> (pd.DataFrame, pd.DataFrame):
11
+ def get(self, person_ids: pd.Series, field_selection: list[str] = []) -> tuple[pd.DataFrame, pd.DataFrame]:
12
12
  data = []
13
13
  for person_id in person_ids:
14
- resp = self.bob.session.get(url=f"{self.bob.base_url}people/{person_id}/bank-accounts", timeout=self.bob.timeout)
14
+ resp = self.client.session.get(url=f"{self.client.base_url}people/{person_id}/bank-accounts", timeout=self.client.timeout)
15
15
  resp.raise_for_status()
16
16
  temp_data = resp.json()['values']
17
17
  # when an employee has one or more bank accounts, the response is a list of dictionaries.
@@ -21,6 +21,6 @@ class Bank:
21
21
 
22
22
  df = pd.DataFrame(data)
23
23
 
24
- valid_banks, invalid_banks = Functions.validate_data(df=df, schema=BankSchema, debug=True)
24
+ valid_banks, invalid_banks = Functions.validate_data(df=df, schema=BankGet, debug=True)
25
25
 
26
26
  return valid_banks, invalid_banks
@@ -2,19 +2,19 @@ import pandas as pd
2
2
 
3
3
 
4
4
  class Company:
5
- def __init__(self, bob):
6
- self.bob = bob
5
+ def __init__(self, client):
6
+ self.client = client
7
7
 
8
8
  def get_variable_values(self, list_name: str = None) -> dict:
9
9
  values = {}
10
10
 
11
11
  if list_name is not None:
12
- resp = self.bob.session.get(url=f"{self.bob.base_url}company/named-lists/{list_name}", timeout=self.bob.timeout)
12
+ resp = self.client.session.get(url=f"{self.client.base_url}company/named-lists/{list_name}", timeout=self.client.timeout)
13
13
  resp.raise_for_status()
14
14
  data = resp.json()
15
15
  values.update({data["name"]: [value['id'] for value in data['values']]})
16
16
  else:
17
- resp = self.bob.session.get(url=f"{self.bob.base_url}company/named-lists", timeout=self.bob.timeout)
17
+ resp = self.client.session.get(url=f"{self.client.base_url}company/named-lists", timeout=self.client.timeout)
18
18
  resp.raise_for_status()
19
19
  data = resp.json()
20
20
  for list_key, list_data in data.items():
@@ -4,13 +4,13 @@ import pandas as pd
4
4
 
5
5
  from brynq_sdk_functions import BrynQPanderaDataFrameModel, Functions
6
6
 
7
- from .schemas.custom_tables import CustomTableMetadataSchema, CustomTableSchema
7
+ from .schemas.custom_tables import CustomTableMetadataGet, CustomTableGet
8
8
 
9
9
 
10
10
  class CustomTables:
11
- def __init__(self, bob):
12
- self.bob = bob
13
- self.schema = CustomTableSchema
11
+ def __init__(self, client):
12
+ self.client = client
13
+ self.schema = CustomTableGet
14
14
 
15
15
  def get(self, employee_ids: List[str], custom_table_id: str, schema_custom_fields: Optional[BrynQPanderaDataFrameModel] = None) -> tuple[pd.DataFrame, pd.DataFrame]:
16
16
  """
@@ -26,7 +26,7 @@ class CustomTables:
26
26
  """
27
27
  df = pd.DataFrame()
28
28
  for employee_id in employee_ids:
29
- resp = self.bob.session.get(url=f"{self.bob.base_url}people/custom-tables/{employee_id}/{custom_table_id}", timeout=self.bob.timeout)
29
+ resp = self.client.session.get(url=f"{self.client.base_url}people/custom-tables/{employee_id}/{custom_table_id}", timeout=self.client.timeout)
30
30
  resp.raise_for_status()
31
31
  data = resp.json()
32
32
 
@@ -53,8 +53,8 @@ class CustomTables:
53
53
  Returns:
54
54
  A tuple of (valid_data, invalid_data) as pandas DataFrames containing table and column metadata
55
55
  """
56
- url = f"{self.bob.base_url}people/custom-tables/metadata"
57
- resp = self.bob.session.get(url=url)
56
+ url = f"{self.client.base_url}people/custom-tables/metadata"
57
+ resp = self.client.session.get(url=url)
58
58
  resp.raise_for_status()
59
59
  data = resp.json()
60
60
 
@@ -82,6 +82,6 @@ class CustomTables:
82
82
  df = pd.DataFrame(rows)
83
83
 
84
84
  # Validate against the metadata schema
85
- valid_data, invalid_data = Functions.validate_data(df=df, schema=CustomTableMetadataSchema, debug=True)
85
+ valid_data, invalid_data = Functions.validate_data(df=df, schema=CustomTableMetadataGet, debug=True)
86
86
 
87
87
  return valid_data, invalid_data
@@ -0,0 +1,37 @@
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, client):
10
+ self.client = client
11
+
12
+ def get(self, person_id: datetime) -> pd.DataFrame:
13
+ resp = self.client.session.get(url=f"{self.client.base_url}docs/people/{person_id}", timeout=self.client.timeout)
14
+ resp.raise_for_status()
15
+ data = resp.json()['documents']
16
+ df = pd.DataFrame(data)
17
+ df = self.client.rename_camel_columns_to_snake_case(df)
18
+
19
+ return df
20
+
21
+ def get_folders(self) -> dict:
22
+ resp = self.client.session.get(url=f"{self.client.base_url}docs/folders/metadata", timeout=self.client.timeout)
23
+ resp.raise_for_status()
24
+ data = resp.json()
25
+
26
+ return data
27
+
28
+ def create(self,
29
+ person_id: datetime,
30
+ folder_id: str,
31
+ file_name: str,
32
+ file_object: BytesIO):
33
+ files = {"file": (file_name, file_object, "application/pdf")}
34
+ resp = self.client.session.post(url=f"{self.client.base_url}docs/people/{person_id}/folders/{folder_id}/upload",
35
+ files=files,
36
+ timeout=self.client.timeout)
37
+ resp.raise_for_status()
@@ -1,18 +1,19 @@
1
1
  import pandas as pd
2
2
  import requests
3
- from .schemas.employment import EmploymentSchema
3
+ from .schemas.employment import EmploymentGet
4
4
  from brynq_sdk_functions import Functions
5
5
 
6
6
 
7
7
  class Employment:
8
- def __init__(self, bob):
9
- self.bob = bob
10
- self.schema = EmploymentSchema
8
+ def __init__(self, client):
9
+ self.client = client
10
+ self.schema = EmploymentGet
11
11
 
12
- def get(self) -> (pd.DataFrame, pd.DataFrame):
12
+ def get(self) -> tuple[pd.DataFrame, pd.DataFrame]:
13
13
  request = requests.Request(method='GET',
14
- url=f"{self.bob.base_url}bulk/people/employment")
15
- data = self.bob.get_paginated_result(request)
14
+ url=f"{self.client.base_url}bulk/people/employment",
15
+ params={"includeArchived": "true"})
16
+ data = self.client.get_paginated_result(request)
16
17
  df = pd.json_normalize(
17
18
  data,
18
19
  record_path='values',
@@ -0,0 +1,37 @@
1
+ from typing import Tuple
2
+ import pandas as pd
3
+ from brynq_sdk_functions import Functions
4
+ from brynq_sdk_bob.schemas.entitlements import EntitlementReportGet
5
+
6
+
7
+ class Entitlements:
8
+ def __init__(self, client):
9
+ self.client = client
10
+
11
+ def get(self, report_id: str | None = None, debug: bool = False) -> tuple[pd.DataFrame, pd.DataFrame]:
12
+ """
13
+ Get entitlement history data from the Bob reports.
14
+
15
+ Args:
16
+ report_id: Optional report ID. If not provided, will search for 'Entitlement history' report.
17
+ debug: Whether to print debug information.
18
+
19
+ Returns:
20
+ Tuple of (valid_entitlements, invalid_entitlements) DataFrames
21
+ """
22
+ reports_df, _ = self.client.reports.get()
23
+ if report_id is None:
24
+ entitlement_report = reports_df[reports_df['name'] == 'Entitlement history']
25
+ if entitlement_report.empty:
26
+ raise ValueError("Report 'Entitlement history' not found.")
27
+ report_id = entitlement_report['id'].iloc[0]
28
+
29
+ report_data = self.client.reports.download(report_id=report_id)
30
+ df_raw = pd.json_normalize(report_data, record_path='employees')
31
+ valid_entitlements, invalid_entitlements = Functions.validate_data(
32
+ df=df_raw,
33
+ schema=EntitlementReportGet,
34
+ debug=debug,
35
+ )
36
+
37
+ return valid_entitlements, invalid_entitlements
@@ -1,15 +1,15 @@
1
1
  from datetime import datetime
2
2
  import pandas as pd
3
3
  from brynq_sdk_functions import Functions
4
- from .schemas.named_lists import NamedListSchema
4
+ from .schemas.named_lists import NamedListGet
5
5
 
6
6
 
7
7
  class NamedLists:
8
- def __init__(self, bob):
9
- self.bob = bob
10
- self.schema = NamedListSchema
8
+ def __init__(self, client):
9
+ self.client = client
10
+ self.schema = NamedListGet
11
11
 
12
- def get(self) -> (pd.DataFrame, pd.DataFrame):
12
+ def get(self) -> tuple[pd.DataFrame, pd.DataFrame]:
13
13
  """
14
14
  Get custom table data for an employee
15
15
 
@@ -19,8 +19,8 @@ class NamedLists:
19
19
  Returns:
20
20
  A tuple of (valid_data, invalid_data) as pandas DataFrames
21
21
  """
22
- url = f"{self.bob.base_url}company/named-lists/"
23
- resp = self.bob.session.get(url=url)
22
+ url = f"{self.client.base_url}company/named-lists/"
23
+ resp = self.client.session.get(url=url)
24
24
  resp.raise_for_status()
25
25
  data = resp.json()
26
26
 
@@ -31,7 +31,6 @@ class NamedLists:
31
31
  ])
32
32
 
33
33
  # Normalize the nested JSON response
34
- # df = pd.DataFrame(data.get('values'))
35
- valid_data, invalid_data = Functions.validate_data(df=df, schema=NamedListSchema, debug=True)
34
+ valid_data, invalid_data = Functions.validate_data(df=df, schema=NamedListGet, debug=True)
36
35
 
37
36
  return valid_data, invalid_data
@@ -5,24 +5,25 @@ import pandas as pd
5
5
 
6
6
  from brynq_sdk_functions import Functions
7
7
 
8
- from .schemas.payments import ActualPaymentsSchema, VariablePaymentSchema
8
+ from .schemas.payments import ActualPaymentsGet, VariablePaymentGet
9
9
 
10
10
 
11
11
  class Payments:
12
- def __init__(self, bob):
13
- self.bob = bob
14
- self.schema = VariablePaymentSchema
12
+ def __init__(self, client):
13
+ self.client = client
14
+ self.schema = VariablePaymentGet
15
15
 
16
- def get(self, person_ids: List[str]) -> (pd.DataFrame, pd.DataFrame):
16
+ def get(self, person_ids: List[str]) -> tuple[pd.DataFrame, pd.DataFrame]:
17
17
  records = []
18
18
 
19
19
  for person_id in person_ids:
20
20
  # Throttle requests to respect API rate limits
21
21
  time.sleep(0.2)
22
22
 
23
- resp = self.bob.session.get(
24
- url=f"{self.bob.base_url}people/{person_id}/variable",
25
- timeout=self.bob.timeout
23
+ resp = self.client.session.get(
24
+ url=f"{self.client.base_url}people/{person_id}/variable",
25
+ timeout=self.client.timeout,
26
+ params={"includeArchived": "true"}
26
27
  )
27
28
  resp.raise_for_status()
28
29
 
@@ -46,7 +47,7 @@ class Payments:
46
47
  employee_ids: Optional[List[str]] = None,
47
48
  pay_date_from: Optional[str] = None,
48
49
  pay_date_to: Optional[str] = None
49
- ) -> (pd.DataFrame, pd.DataFrame):
50
+ ) -> tuple[pd.DataFrame, pd.DataFrame]:
50
51
  """
51
52
  Search for actual payments with optional employee and pay date filters.
52
53
  This method auto-paginates until all results are fetched.
@@ -101,10 +102,10 @@ class Payments:
101
102
  if next_cursor:
102
103
  payload["pagination"]["cursor"] = next_cursor
103
104
 
104
- resp = self.bob.session.post(
105
- url=f"{self.bob.base_url}people/actual-payments/search",
105
+ resp = self.client.session.post(
106
+ url=f"{self.client.base_url}people/actual-payments/search",
106
107
  json=payload,
107
- timeout=self.bob.timeout
108
+ timeout=self.client.timeout
108
109
  )
109
110
  resp.raise_for_status()
110
111
  data = resp.json()
@@ -125,7 +126,7 @@ class Payments:
125
126
 
126
127
  valid_payments, invalid_payments = Functions.validate_data(
127
128
  df=df,
128
- schema=ActualPaymentsSchema,
129
+ schema=ActualPaymentsGet,
129
130
  debug=True
130
131
  )
131
132
 
@@ -1,11 +1,11 @@
1
1
  import pandas as pd
2
2
  from brynq_sdk_functions import Functions
3
- from .schemas.people import PeopleSchema
3
+ from .schemas.people import PeopleGet
4
4
 
5
5
  class History:
6
- def __init__(self, bob):
7
- self.bob = bob
8
- self.schema = PeopleSchema
6
+ def __init__(self, client):
7
+ self.client = client
8
+ self.schema = PeopleGet
9
9
  self.field_name_in_body, self.field_name_in_response, self.endpoint_to_response = self._init_fields()
10
10
 
11
11
  def get(self, additional_fields: list[str] = [], field_selection: list[str] = []) -> tuple[pd.DataFrame, pd.DataFrame]:
@@ -16,7 +16,6 @@ class History:
16
16
  additional_fields (list[str]): Additional fields to get (not defined in the schema)
17
17
  field_selection (list[str]): Fields to get (defined in the schema), if not provided, all fields are returned
18
18
  """
19
- #resp = self.bob.session.get(url=f"{self.bob.base_url}profiles", timeout=self.bob.timeout)
20
19
  body_fields = list(set(self.field_name_in_body + additional_fields))
21
20
  response_fields = list(set(self.field_name_in_response + additional_fields))
22
21
 
@@ -25,25 +24,25 @@ class History:
25
24
  response_fields = [self.endpoint_to_response.get(field) for field in field_selection if field in self.endpoint_to_response]
26
25
 
27
26
  # Bob sucks with default fields so you need to do a search call to retrieve additional fields.
28
- resp_additional_fields = self.bob.session.post(url=f"{self.bob.base_url}people/search",
27
+ resp_additional_fields = self.client.session.post(url=f"{self.client.base_url}people/search",
29
28
  json={
30
29
  "fields": body_fields,
31
30
  "filters": []
32
31
  },
33
- timeout=self.bob.timeout)
32
+ timeout=self.client.timeout)
34
33
  json_response = resp_additional_fields.json()
35
34
  df = pd.json_normalize(resp_additional_fields.json()['employees'])
36
35
  df = df[[col for col in response_fields if col in df.columns]]
37
- # Get the valid column names from PeopleSchema
38
- valid_people, invalid_people = Functions.validate_data(df=df, schema=PeopleSchema, debug=True)
36
+ # Get the valid column names from PeopleGet
37
+ valid_people, invalid_people = Functions.validate_data(df=df, schema=PeopleGet, debug=True)
39
38
  return valid_people, invalid_people
40
39
 
41
40
 
42
41
  def _init_fields(self) -> tuple[list[str], list[str], dict[str, str]]:
43
- resp_fields = self.bob.session.get(
44
- url=f"{self.bob.base_url}company/people/fields",
45
- timeout=self.bob.timeout,
46
- headers=self.bob.headers
42
+ resp_fields = self.client.session.get(
43
+ url=f"{self.client.base_url}company/people/fields",
44
+ timeout=self.client.timeout,
45
+ headers=self.client.headers
47
46
  )
48
47
  fields = resp_fields.json()
49
48
  field_name_in_body = [field.get('id') for field in fields]
@@ -58,14 +57,14 @@ class History:
58
57
  body_fields = [employee_id_in_company, person_id]
59
58
  response_fields = [self.endpoint_to_response.get(field) for field in body_fields if field in self.endpoint_to_response]
60
59
 
61
- resp_additional_fields = self.bob.session.post(url=f"{self.bob.base_url}people/search",
60
+ resp_additional_fields = self.client.session.post(url=f"{self.client.base_url}people/search",
62
61
  json={
63
62
  "fields": body_fields,
64
63
  "filters": []
65
64
  },
66
- timeout=self.bob.timeout)
65
+ timeout=self.client.timeout)
67
66
  df = pd.json_normalize(resp_additional_fields.json()['employees'])
68
67
  df = df[[col for col in response_fields if col in df.columns]]
69
- # Get the valid column names from PeopleSchema
70
- valid_people, invalid_people = Functions.validate_data(df=df, schema=PeopleSchema, debug=True)
68
+ # Get the valid column names from PeopleGet
69
+ valid_people, invalid_people = Functions.validate_data(df=df, schema=PeopleGet, debug=True)
71
70
  return valid_people, invalid_people
@@ -9,19 +9,19 @@ from .bank import Bank
9
9
  from .custom_tables import CustomTables
10
10
  from .employment import Employment
11
11
  from .salaries import Salaries
12
- from .schemas.people import PeopleSchema
12
+ from .schemas.people import PeopleGet
13
13
  from .work import Work
14
14
 
15
15
 
16
16
  class People:
17
- def __init__(self, bob):
18
- self.bob = bob
19
- self.salaries = Salaries(bob)
20
- self.employment = Employment(bob)
21
- self.bank = Bank(bob)
22
- self.work = Work(bob)
23
- self.custom_tables = CustomTables(bob)
24
- self.schema = PeopleSchema
17
+ def __init__(self, client):
18
+ self.client = client
19
+ self.salaries = Salaries(client)
20
+ self.employment = Employment(client)
21
+ self.bank = Bank(client)
22
+ self.work = Work(client)
23
+ self.custom_tables = CustomTables(client)
24
+ self.schema = PeopleGet
25
25
 
26
26
 
27
27
 
@@ -33,19 +33,18 @@ class People:
33
33
  for col_name, col in schema.columns.items()
34
34
  ]
35
35
 
36
- def get(self, schema_custom_fields: Optional[BrynQPanderaDataFrameModel] = None) -> pd.DataFrame:
36
+ def get(self, schema_custom_fields: Optional[BrynQPanderaDataFrameModel] = None) -> tuple[pd.DataFrame, pd.DataFrame]:
37
37
 
38
- core_fields = self.__build_api_fields(PeopleSchema)
38
+ core_fields = self.__build_api_fields(PeopleGet)
39
39
  custom_fields = self.__build_api_fields(schema_custom_fields) if schema_custom_fields is not None else []
40
40
  fields = core_fields + custom_fields
41
41
 
42
- resp = self.bob.session.post(url=f"{self.bob.base_url}people/search",
42
+ resp = self.client.session.post(url=f"{self.client.base_url}people/search",
43
43
  json={
44
44
  "fields": fields,
45
45
  "filters": []
46
- #"humanReadable": "REPLACE"
47
46
  },
48
- timeout=self.bob.timeout)
47
+ timeout=self.client.timeout)
49
48
  resp.raise_for_status()
50
49
  df = pd.json_normalize(resp.json()['employees'])
51
50
 
@@ -72,7 +71,7 @@ class People:
72
71
 
73
72
 
74
73
  # A lot of fields from Bob are returned with only ID's. Those fields should be mapped to names. Therefore, we need to get the mapping from the named-lists endpoint.
75
- resp_named_lists = self.bob.session.get(url=f"{self.bob.base_url}company/named-lists", timeout=self.bob.timeout, headers=self.bob.headers)
74
+ resp_named_lists = self.client.session.get(url=f"{self.client.base_url}company/named-lists", timeout=self.client.timeout, headers=self.client.headers)
76
75
  named_lists = resp_named_lists.json()
77
76
 
78
77
  # Transform named_lists to create id-to-value mappings for each field
@@ -102,7 +101,7 @@ class People:
102
101
  invalid_people_custom = pd.DataFrame()
103
102
 
104
103
 
105
- valid_people, invalid_people = Functions.validate_data(df=valid_people, schema=PeopleSchema, debug=True)
104
+ valid_people, invalid_people = Functions.validate_data(df=valid_people, schema=PeopleGet, debug=True)
106
105
 
107
106
  # For columns ending with .value.value, use fillna to fill the corresponding base column since that's not done automatically
108
107
  def _normalize_key(key: str) -> str:
@@ -0,0 +1,41 @@
1
+ from typing import Literal, TYPE_CHECKING
2
+
3
+ import pandas as pd
4
+ from brynq_sdk_functions import Functions
5
+ from brynq_sdk_bob.schemas.reports import ReportGet
6
+ if TYPE_CHECKING:
7
+ from brynq_sdk_bob import Bob
8
+
9
+
10
+ class Reports:
11
+ def __init__(self, client):
12
+ self.client: Bob = client
13
+
14
+ def get(self) -> tuple[pd.DataFrame, pd.DataFrame]:
15
+ resp = self.client.session.get(url=f"{self.client.base_url}company/reports", timeout=self.client.timeout)
16
+ resp.raise_for_status()
17
+ data = resp.json()
18
+ df = pd.json_normalize(
19
+ data,
20
+ record_path='views'
21
+ )
22
+ valid_reports, invalid_reports = Functions.validate_data(df=df, schema=ReportGet, debug=True)
23
+
24
+ return valid_reports, invalid_reports
25
+
26
+ def download(self, report_id: int | str = None, report_format: Literal["json", "csv"] = "json") -> dict | bytes:
27
+ if report_id:
28
+ url = f"{self.client.base_url}company/reports/{report_id}/download"
29
+ else:
30
+ raise ValueError("Either report_id or report_name must be provided")
31
+
32
+ resp = self.client.session.get(url=url, timeout=self.client.timeout, params={"format": report_format})
33
+ resp.raise_for_status()
34
+ if report_format == "json":
35
+ data = resp.json()
36
+ elif report_format == "csv":
37
+ data = resp.content
38
+ else:
39
+ raise ValueError(f"Invalid report format: {report_format}")
40
+
41
+ return data
@@ -0,0 +1,38 @@
1
+ import pandas as pd
2
+ import requests
3
+ from brynq_sdk_functions import Functions
4
+ from .schemas.salary import SalaryGet, SalaryCreate
5
+
6
+
7
+ class Salaries:
8
+ def __init__(self, client):
9
+ self.client = client
10
+ self.schema = SalaryGet
11
+
12
+ def get(self) -> tuple[pd.DataFrame, pd.DataFrame]:
13
+ request = requests.Request(method='GET',
14
+ url=f"{self.client.base_url}bulk/people/salaries",
15
+ params={"limit": 100, "includeArchived": "true"})
16
+ data = self.client.get_paginated_result(request)
17
+ df = pd.json_normalize(
18
+ data,
19
+ record_path='values',
20
+ meta=['employeeId']
21
+ )
22
+ valid_salaries, invalid_salaries = Functions.validate_data(df=df, schema=SalaryGet, debug=True)
23
+
24
+ return valid_salaries, invalid_salaries
25
+
26
+ def create(self, salary_data: dict) -> requests.Response:
27
+ nested_data = self.client.flat_dict_to_nested_dict(salary_data, SalaryCreate)
28
+ salary_data = SalaryCreate(**nested_data)
29
+ payload = salary_data.model_dump(exclude_none=True, by_alias=True)
30
+
31
+ resp = self.client.session.post(url=f"{self.client.base_url}people/{salary_data.employee_id}/salaries", json=payload)
32
+ resp.raise_for_status()
33
+ return resp
34
+
35
+ def delete(self, employee_id: str, salary_id: str) -> requests.Response:
36
+ resp = self.client.session.delete(url=f"{self.client.base_url}people/{employee_id}/salaries/{salary_id}")
37
+ resp.raise_for_status()
38
+ return resp
@@ -4,9 +4,9 @@ import pandas as pd
4
4
  from brynq_sdk_functions import BrynQPanderaDataFrameModel
5
5
 
6
6
 
7
- class BankSchema(BrynQPanderaDataFrameModel):
7
+ class BankGet(BrynQPanderaDataFrameModel):
8
8
  id: Series[pd.Int64Dtype] = pa.Field(coerce=True, description="Bank ID", alias="id")
9
- employee_id: Series[String] = pa.Field(coerce=True, description="Employee ID", alias="employeeId")
9
+ employee_id: Series[String] = pa.Field(coerce=True, description="Employee ID", alias="employee_id")
10
10
  amount: Series[pd.Int64Dtype] = pa.Field(coerce=True, description="Amount", alias="amount")
11
11
  allocation: Series[String] = pa.Field(coerce=True, nullable=True, description="Allocation", alias="allocation")
12
12
  branch_address: Series[String] = pa.Field(coerce=True, nullable=True, description="Branch Address", alias="branchAddress")
@@ -3,13 +3,13 @@ from pandera.typing import Series
3
3
  import pandas as pd
4
4
  from brynq_sdk_functions import BrynQPanderaDataFrameModel
5
5
 
6
- class CustomTableSchema(BrynQPanderaDataFrameModel):
6
+ class CustomTableGet(BrynQPanderaDataFrameModel):
7
7
  id: Series[pd.Int64Dtype] = pa.Field(coerce=True, description="Custom Table ID", alias="id")
8
8
  employee_id: Series[str] = pa.Field(coerce=True, description="Employee ID", alias="employee_id")
9
9
 
10
10
  class Config:
11
11
  coerce = True
12
- class CustomTableMetadataSchema(BrynQPanderaDataFrameModel):
12
+ class CustomTableMetadataGet(BrynQPanderaDataFrameModel):
13
13
  # Table information
14
14
  table_id: Series[str] = pa.Field(coerce=True, description="Table ID", alias="table_id")
15
15
  table_name: Series[str] = pa.Field(coerce=True, description="Table Name", alias="table_name")