brynq-sdk-vplan 1.0.0__tar.gz → 1.2.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 1.0
2
2
  Name: brynq_sdk_vplan
3
- Version: 1.0.0
3
+ Version: 1.2.0
4
4
  Summary: vPlan wrapper from BrynQ
5
5
  Home-page: UNKNOWN
6
6
  Author: BrynQ
@@ -0,0 +1,44 @@
1
+ from brynq_sdk_brynq import BrynQ
2
+ from typing import List, Union
3
+ from .get_data import GetData
4
+ from .activity import Activity
5
+ from .item import Item
6
+ from .order import Order
7
+ from .project import Project
8
+ from .resource import Resource
9
+ from .time_tracking import TimeTracking
10
+ from .user import User
11
+ from .leave import Leave
12
+
13
+
14
+ class VPlan(BrynQ):
15
+ def __init__(self, label: Union[str, List], debug: bool = False):
16
+ """
17
+ A class to fetch data from the vPlan API. See https://developer.vplan.com/documentation/#tag/General/ for more information
18
+ """
19
+ super().__init__()
20
+ self.headers = self._get_credentials(label)
21
+ self.post_headers = {**self.headers, 'Content-Type': 'application/json'}
22
+ self.base_url = 'https://api.vplan.com/v1/'
23
+ self.get = GetData(self)
24
+ self.activity = Activity(self)
25
+ self.item = Item(self)
26
+ self.order = Order(self)
27
+ self.project = Project(self)
28
+ self.resource = Resource(self)
29
+ self.time_tracking = TimeTracking(self)
30
+ self.user = User(self)
31
+ self.leave = Leave(self)
32
+
33
+ def _get_credentials(self, label) -> dict:
34
+ """
35
+ Retrieve API key and env from the system credentials.
36
+ Args: label (Union[str, List]): The label or list of labels to get the credentials.
37
+ Returns: str: The authorization headers
38
+ """
39
+ credentials = self.get_system_credential(system='vplan', label=label)
40
+ headers = {
41
+ 'X-Api-Key': credentials['X-Api-Key'],
42
+ 'X-Api-Env': credentials['X-Api-Env']
43
+ }
44
+ return headers
@@ -0,0 +1,132 @@
1
+ import requests
2
+ from typing import Union, List
3
+ import warnings
4
+ import json
5
+ from .get_data import GetData
6
+
7
+
8
+ class Activity:
9
+ def __init__(self, vplan):
10
+ """
11
+ Initialize the GetData class.
12
+ Args: vplan: contains the vplan object with the headers and base_url
13
+ """
14
+ self.vplan = vplan
15
+ self.get_data = GetData(vplan)
16
+
17
+ def get_activity_list(self, filter: str = None, show: str = None, hide: str = None):
18
+ """
19
+ Get all the activitys from vPlan: https://developer.vplan.com/documentation/#tag/activity/paths/~1activity/get
20
+ Args: filter (str, optional): On the list endpoints it is possible to filter the result set given. To apply a filter use the filter query parameter with the format: field:operator:value
21
+ Example: ?filter=created_at:gt:2020-09-26,and,(id:not:starts_with:0000,or,id:contains:FFFF)
22
+
23
+ Colon : is the separator between the field, operator(s) and value.
24
+ Comma , can be used to combine filter statements by using ,and, or ,or,.
25
+ Braces (...) can be used to group filter statements.
26
+
27
+ The following operators are supported
28
+
29
+ operator description
30
+ eq equal to the given value
31
+ gt greater than the given value
32
+ gte greater than or equal to the given value
33
+ lt lesser than the given value
34
+ lte lesser than or equal to the given value
35
+ not negation of the operator that follows it
36
+ contains has occurrence of the given value
37
+ starts_with starts with the given value
38
+ end_with ends with the given value
39
+
40
+ warning: Currently the comma , and colon : are not supported within the filter value
41
+
42
+ show (str, optional): On the list endpoints it is possible to select the fields that should be returned. To apply a show use the show query parameter with the format: field1,field2,field3
43
+ hide (str, optional): On the list endpoints it is possible to hide the fields that should not be returned. To apply a hide use the hide query parameter with the format: field1,field2,field3
44
+
45
+ Returns: pd.DataFrame: The fetched data as a pandas DataFrame.
46
+ """
47
+ df = self.get_data.get_data(endpoint='activity', filter=filter, show=show, hide=hide)
48
+ return df
49
+
50
+ def post_activity(self, data: dict) -> requests.Response:
51
+ """
52
+ Create a new activity in vPlan: https://developer.vplan.com/documentation/#tag/activity/paths/~1activity/post
53
+
54
+ This method constructs a request URL based on the endpoint and sends a POST request
55
+ to the vPlan API with the provided data.
56
+
57
+ Args: endpoint (str): The name of the endpoint to create a new activity in.
58
+ data (dict): The data to create the new activity with.
59
+
60
+ Returns: requests.Response: The response from the vPlan API.
61
+ """
62
+ required_fields = ['code', 'description']
63
+ allowed_fields = ['resource_type', 'default_duration', 'external_ref', 'archive']
64
+ self.__check_fields(data=data, required_fields=required_fields, allowed_fields=allowed_fields)
65
+
66
+ url = f"{self.vplan.base_url}activity"
67
+
68
+ base_body = {
69
+ "code": data['code'],
70
+ "description": data['description']
71
+ }
72
+ # Add fields that you want to update a dict (adding to body itself is too much text)
73
+ base_body.update({"resource_type": data['resource_type']}) if 'resource_type' in data else base_body
74
+ base_body.update({"default_duration": data['default_duration']}) if 'default_duration' in data else base_body
75
+ base_body.update({"external_ref": data['external_ref']}) if 'external_ref' in data else base_body
76
+ base_body.update({"archive": data['archive']}) if 'archive' in data else base_body
77
+
78
+ response = requests.request('POST', url, headers=self.vplan.post_headers, data=base_body)
79
+ return response
80
+
81
+ def update_activity(self, activity_id: str, data: dict) -> requests.Response:
82
+ """
83
+ Update an existing activity in vPlan: https://developer.vplan.com/documentation/#tag/activity/paths/~1activity~1%7Bactivity_id%7D/put
84
+
85
+ This method constructs a request URL based on the endpoint and sends a POST request
86
+ to the vPlan API with the provided data.
87
+
88
+ Args: endpoint (str): The name of the endpoint to create a new activity in.
89
+ data (dict): The data to create the new activity with.
90
+
91
+ Returns: requests.Response: The response from the vPlan API.
92
+ """
93
+ required_fields = ['code', 'description']
94
+ allowed_fields = ['resource_type', 'default_duration', 'external_ref', 'archive']
95
+ self.__check_fields(data=data, required_fields=required_fields, allowed_fields=allowed_fields)
96
+
97
+ url = f"{self.vplan.base_url}activity/{activity_id}"
98
+
99
+ base_body = {
100
+ "code": data['code'],
101
+ "description": data['description']
102
+ }
103
+ # Add fields that you want to update a dict (adding to body itself is too much text)
104
+ base_body.update({"resource_type": data['resource_type']}) if 'resource_type' in data else base_body
105
+ base_body.update({"default_duration": data['default_duration']}) if 'default_duration' in data else base_body
106
+ base_body.update({"external_ref": data['external_ref']}) if 'external_ref' in data else base_body
107
+ base_body.update({"archive": data['archive']}) if 'archive' in data else base_body
108
+
109
+ response = requests.request('PUT', url, headers=self.vplan.post_headers, json=base_body)
110
+ return response
111
+
112
+ def delete_activity(self, activity_id):
113
+ """
114
+ Delete an existing activity in vPlan: https://developer.vplan.com/documentation/#tag/activity/paths/~1activity~1%7Bactivity_id%7D/delete
115
+ This method constructs a request URL based on the endpoint and sends a DELETE request to the vPlan API.
116
+ """
117
+ url = f"{self.vplan.base_url}activity/{activity_id}"
118
+ response = requests.request('DELETE', url, headers=self.vplan.headers)
119
+ return response
120
+
121
+ @staticmethod
122
+ def __check_fields(data: Union[dict, List], required_fields: List, allowed_fields: List):
123
+ if isinstance(data, dict):
124
+ data = data.keys()
125
+
126
+ for field in data:
127
+ if field not in allowed_fields and field not in required_fields:
128
+ warnings.warn('Field {field} is not implemented. Optional fields are: {allowed_fields}'.format(field=field, allowed_fields=tuple(allowed_fields)))
129
+
130
+ for field in required_fields:
131
+ if field not in data:
132
+ raise ValueError('Field {field} is required. Required fields are: {required_fields}'.format(field=field, required_fields=tuple(required_fields)))
@@ -0,0 +1,86 @@
1
+ import pandas as pd
2
+ from typing import List, Any
3
+ import requests
4
+
5
+
6
+ class GetData:
7
+ def __init__(self, vplan):
8
+ """
9
+ Initialize the GetData class.
10
+ Args: vplan: contains the vplan object with the headers and base_url
11
+ """
12
+ self.vplan = vplan
13
+
14
+ def get_data(self, endpoint: str, limit: int = 1000, filter: str = None, with_array: str = None, show: str = None, hide: str = None, archived: bool = False) -> pd.DataFrame:
15
+ """
16
+ Fetch data from a specified table from vPlan
17
+
18
+ This method constructs a request URL based on the table name and optional filter,
19
+ sends a request to the vPlan API, parses the JSON response, and converts it
20
+ to a pandas DataFrame. The DataFrame columns are cleaned to remove unnecessary
21
+ characters and duplicate columns are handled.
22
+
23
+ Args: endpoint (str): The name of the endpoint to fetch data from.
24
+ filter (str, optional): On the list endpoints it is possible to filter the result set given. To apply a filter use the filter query parameter with the format: field:operator:value
25
+ Example: ?filter=created_at:gt:2020-09-26,and,(id:not:starts_with:0000,or,id:contains:FFFF)
26
+
27
+ Colon : is the separator between the field, operator(s) and value.
28
+ Comma , can be used to combine filter statements by using ,and, or ,or,.
29
+ Braces (...) can be used to group filter statements.
30
+
31
+ The following operators are supported
32
+
33
+ operator description
34
+ eq equal to the given value
35
+ gt greater than the given value
36
+ gte greater than or equal to the given value
37
+ lt lesser than the given value
38
+ lte lesser than or equal to the given value
39
+ not negation of the operator that follows it
40
+ contains has occurrence of the given value
41
+ starts_with starts with the given value
42
+ end_with ends with the given value
43
+
44
+ warning: Currently the comma , and colon : are not supported within the filter value
45
+
46
+ show (str, optional): On the list endpoints it is possible to select the fields that should be returned. To apply a show use the show query parameter with the format: field1,field2,field3
47
+ hide (str, optional): On the list endpoints it is possible to hide the fields that should not be returned. To apply a hide use the hide query parameter with the format: field1,field2,field3
48
+
49
+ Returns: pd.DataFrame: The fetched data as a pandas DataFrame.
50
+ """
51
+ # Initial URL with subscription-key
52
+ url = f"{self.vplan.base_url}{endpoint}?"
53
+ if limit:
54
+ url += f'limit={limit}&'
55
+ if filter:
56
+ url += f'filter={filter}&'
57
+ if with_array:
58
+ url += f'with={with_array}&'
59
+ if show:
60
+ url += f'show={show}&'
61
+ if hide:
62
+ url += f'hide={hide}&'
63
+ if archived:
64
+ url += f'archived={archived}&'
65
+
66
+ all_data = []
67
+ offset = 0
68
+ received = 0
69
+ while True:
70
+ final_url = f'{url}offset={offset}'
71
+ response = requests.get(url=final_url, headers=self.vplan.headers)
72
+ response.raise_for_status()
73
+ result = response.json()
74
+ data = result.get('data', result)
75
+ all_data.extend(data)
76
+
77
+ # Determine if we need to continue fetching data
78
+ count = result.get('count', 0)
79
+ received += len(data)
80
+ offset += limit
81
+ if received >= count:
82
+ break
83
+
84
+
85
+ df = pd.DataFrame(all_data)
86
+ return df
@@ -0,0 +1,141 @@
1
+ import requests
2
+ from typing import Union, List
3
+ import warnings
4
+ import json
5
+ from .get_data import GetData
6
+
7
+
8
+ class Item:
9
+ def __init__(self, vplan):
10
+ """
11
+ Initialize the GetData class.
12
+ Args: vplan: contains the vplan object with the headers and base_url
13
+ """
14
+ self.vplan = vplan
15
+ self.get_data = GetData(vplan)
16
+
17
+ def get_item_list(self, filter: str = None, show: str = None, hide: str = None):
18
+ """
19
+ Get all the items from vPlan: https://developer.vplan.com/documentation/#tag/item/paths/~1item/get
20
+ Args: filter (str, optional): On the list endpoints it is possible to filter the result set given. To apply a filter use the filter query parameter with the format: field:operator:value
21
+ Example: ?filter=created_at:gt:2020-09-26,and,(id:not:starts_with:0000,or,id:contains:FFFF)
22
+
23
+ Colon : is the separator between the field, operator(s) and value.
24
+ Comma , can be used to combine filter statements by using ,and, or ,or,.
25
+ Braces (...) can be used to group filter statements.
26
+
27
+ The following operators are supported
28
+
29
+ operator description
30
+ eq equal to the given value
31
+ gt greater than the given value
32
+ gte greater than or equal to the given value
33
+ lt lesser than the given value
34
+ lte lesser than or equal to the given value
35
+ not negation of the operator that follows it
36
+ contains has occurrence of the given value
37
+ starts_with starts with the given value
38
+ end_with ends with the given value
39
+
40
+ warning: Currently the comma , and colon : are not supported within the filter value
41
+
42
+ show (str, optional): On the list endpoints it is possible to select the fields that should be returned. To apply a show use the show query parameter with the format: field1,field2,field3
43
+ hide (str, optional): On the list endpoints it is possible to hide the fields that should not be returned. To apply a hide use the hide query parameter with the format: field1,field2,field3
44
+
45
+ Returns: pd.DataFrame: The fetched data as a pandas DataFrame.
46
+ """
47
+ df = self.get_data.get_data(endpoint='item', filter=filter, show=show, hide=hide)
48
+ return df
49
+
50
+ def post_item(self, data: dict) -> requests.Response:
51
+ """
52
+ Create a new item in vPlan: https://developer.vplan.com/documentation/#tag/item/paths/~1item/post
53
+
54
+ This method constructs a request URL based on the endpoint and sends a POST request
55
+ to the vPlan API with the provided data.
56
+
57
+ Args: endpoint (str): The name of the endpoint to create a new item in.
58
+ data (dict): The data to create the new item with.
59
+
60
+ Returns: requests.Response: The response from the vPlan API.
61
+ """
62
+ required_fields = ['code', 'description']
63
+ allowed_fields = ['stock_management', 'unit', 'type', 'location', 'note', 'external_ref']
64
+ self.__check_fields(data=data, required_fields=required_fields, allowed_fields=allowed_fields)
65
+
66
+ url = f"{self.vplan.base_url}item"
67
+
68
+ base_body = {
69
+ "code": data['code'],
70
+ "description": data['description']
71
+ }
72
+ # Add fields that you want to update a dict (adding to body itself is too much text)
73
+ base_body.update({"code": data['code']}) if 'code' in data else base_body
74
+ base_body.update({"description": data['description']}) if 'description' in data else base_body
75
+ base_body.update({"stock_management": data['stock_management']}) if 'stock_management' in data else base_body
76
+ base_body.update({"unit": data['unit']}) if 'unit' in data else base_body
77
+ base_body.update({"type": data['type']}) if 'type' in data else base_body
78
+ base_body.update({"location": data['location']}) if 'location' in data else base_body
79
+ base_body.update({"note": data['note']}) if 'note' in data else base_body
80
+ base_body.update({"external_ref": data['external_ref']}) if 'external_ref' in data else base_body
81
+
82
+ response = requests.request('POST', url, headers=self.vplan.post_headers, data=base_body)
83
+ return response
84
+
85
+ def update_item(self, item_id: str, data: dict) -> requests.Response:
86
+ """
87
+ Update an existing item in vPlan: https://developer.vplan.com/documentation/#tag/item/paths/~1item~1%7Bitem_id%7D/put
88
+
89
+ This method constructs a request URL based on the endpoint and sends a POST request
90
+ to the vPlan API with the provided data.
91
+
92
+ Args: endpoint (str): The name of the endpoint to create a new item in.
93
+ data (dict): The data to create the new item with.
94
+
95
+ Returns: requests.Response: The response from the vPlan API.
96
+ """
97
+ required_fields = ['code', 'description']
98
+ allowed_fields = ['stock_management', 'unit', 'type', 'location', 'note', 'external_ref']
99
+ self.__check_fields(data=data, required_fields=required_fields, allowed_fields=allowed_fields)
100
+
101
+ url = f"{self.vplan.base_url}item/{item_id}"
102
+
103
+ base_body = {
104
+ "code": data['code'],
105
+ "description": data['description']
106
+ }
107
+ # Add fields that you want to update a dict (adding to body itself is too much text)
108
+ base_body.update({"code": data['code']}) if 'code' in data else base_body
109
+ base_body.update({"description": data['description']}) if 'description' in data else base_body
110
+ base_body.update({"stock_management": data['stock_management']}) if 'stock_management' in data else base_body
111
+ base_body.update({"unit": data['unit']}) if 'unit' in data else base_body
112
+ base_body.update({"type": data['type']}) if 'type' in data else base_body
113
+ base_body.update({"location": data['location']}) if 'location' in data else base_body
114
+ base_body.update({"note": data['note']}) if 'note' in data else base_body
115
+ base_body.update({"external_ref": data['external_ref']}) if 'external_ref' in data else base_body
116
+
117
+ response = requests.request('PUT', url, headers=self.vplan.post_headers, json=base_body)
118
+ return response
119
+
120
+ def delete_item(self, item_id):
121
+ """
122
+ Delete an existing item in vPlan: https://developer.vplan.com/documentation/#tag/Item/paths/~1item~1%7Bitem_id%7D/delete
123
+ This method constructs a request URL based on the endpoint and sends a DELETE request to the vPlan API.
124
+ """
125
+ url = f"{self.vplan.base_url}item/{item_id}"
126
+ response = requests.request('DELETE', url, headers=self.vplan.headers)
127
+ return response
128
+
129
+
130
+ @staticmethod
131
+ def __check_fields(data: Union[dict, List], required_fields: List, allowed_fields: List):
132
+ if isinstance(data, dict):
133
+ data = data.keys()
134
+
135
+ for field in data:
136
+ if field not in allowed_fields and field not in required_fields:
137
+ warnings.warn('Field {field} is not implemented. Optional fields are: {allowed_fields}'.format(field=field, allowed_fields=tuple(allowed_fields)))
138
+
139
+ for field in required_fields:
140
+ if field not in data:
141
+ raise ValueError('Field {field} is required. Required fields are: {required_fields}'.format(field=field, required_fields=tuple(required_fields)))
@@ -0,0 +1,110 @@
1
+ import requests
2
+ from typing import Union, List, Any
3
+ import warnings
4
+ import json
5
+ from .get_data import GetData
6
+
7
+
8
+ class Leave:
9
+ def __init__(self, vplan):
10
+ """
11
+ Initialize the GetData class.
12
+ Args: vplan: contains the vplan object with the headers and base_url
13
+ """
14
+ self.vplan = vplan
15
+ self.get_data = GetData(vplan)
16
+
17
+ def get_leave(self, resource_id: str) -> requests.Response:
18
+ """
19
+ There is no documentation for this method available
20
+
21
+ This method constructs a request URL based on the endpoint and sends a GET request
22
+ to the vPlan API.
23
+
24
+ Args: resource_id (str): The id of the resource to get the leave from
25
+
26
+ Returns: requests.Response: The response from the vPlan API.
27
+ """
28
+ url = f"{self.vplan.base_url}resource/{resource_id}/schedule_deviation"
29
+ response = requests.request('GET', url, headers=self.vplan.headers)
30
+ return response
31
+
32
+ def post_leave(self, resource_id: str, data: dict) -> requests.Response:
33
+ """
34
+ There is no documentation for this method available
35
+
36
+ This method constructs a request URL based on the endpoint and sends a POST request
37
+ to the vPlan API with the provided data.
38
+
39
+ Args: resource_id (str): The resource id of the employee to add the leave to
40
+ data (dict): The data to create the new order with.
41
+
42
+ Returns: requests.Response: The response from the vPlan API.
43
+ """
44
+ required_fields = ['type', 'time', 'description', 'start_date', 'end_date']
45
+ allowed_fields = []
46
+ self.__check_fields(data=data, required_fields=required_fields, allowed_fields=allowed_fields)
47
+
48
+ url = f"{self.vplan.base_url}resource/{resource_id}/schedule_deviation"
49
+ base_body = json.dumps({
50
+ "type": data['type'],
51
+ "time": data['time'],
52
+ "description": data['description'],
53
+ "start_date": data['start_date'],
54
+ "end_date": data['end_date']
55
+ })
56
+ response = requests.request('POST', url, headers=self.vplan.post_headers, data=base_body)
57
+ return response
58
+
59
+ def update_leave(self, resource_id: str, leave_id: str, data: dict) -> requests.Response:
60
+ """
61
+ There is no documentation for this method available
62
+
63
+ This method constructs a request URL based on the endpoint and sends a POST request
64
+ to the vPlan API with the provided data.
65
+
66
+ Args: resource_id (str): The resource id of the employee to add the leave to
67
+ leave_id (str): The id of the leave to update
68
+ data (dict): The data to create the new order with.
69
+
70
+ Returns: requests.Response: The response from the vPlan API.
71
+ """
72
+ required_fields = ['type', 'time', 'description', 'start_date', 'end_date']
73
+ allowed_fields = []
74
+ self.__check_fields(data=data, required_fields=required_fields, allowed_fields=allowed_fields)
75
+
76
+ url = f"{self.vplan.base_url}resource/{resource_id}/schedule_deviation/{leave_id}"
77
+ base_body = json.dumps({
78
+ "type": data['type'],
79
+ "time": data['time'],
80
+ "description": data['description'],
81
+ "start_date": data['start_date'],
82
+ "end_date": data['end_date']
83
+ })
84
+ response = requests.request('PUT', url, headers=self.vplan.post_headers, json=base_body)
85
+ return response
86
+
87
+ def delete_leave(self, resource_id: str, leave_id: str):
88
+ """
89
+ There is no documentation for this method available
90
+ This method constructs a request URL based on the endpoint and sends a DELETE request to the vPlan API.
91
+ :param resource_id: The resource id of the employee to delete the the leave to
92
+ :param leave_id: The id of the leave to delete
93
+ """
94
+ url = f"{self.vplan.base_url}resource/{resource_id}/schedule_deviation/{leave_id}"
95
+ response = requests.request('DELETE', url, headers=self.vplan.headers)
96
+ return response
97
+
98
+
99
+ @staticmethod
100
+ def __check_fields(data: Union[dict, List], required_fields: List, allowed_fields: List):
101
+ if isinstance(data, dict):
102
+ data = data.keys()
103
+
104
+ for field in data:
105
+ if field not in allowed_fields and field not in required_fields:
106
+ warnings.warn('Field {field} is not implemented. Optional fields are: {allowed_fields}'.format(field=field, allowed_fields=tuple(allowed_fields)))
107
+
108
+ for field in required_fields:
109
+ if field not in data:
110
+ raise ValueError('Field {field} is required. Required fields are: {required_fields}'.format(field=field, required_fields=tuple(required_fields)))