brynq-sdk-vplan 0.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.
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 1.0
2
+ Name: brynq_sdk_vplan
3
+ Version: 0.1.0
4
+ Summary: vPlan wrapper from BrynQ
5
+ Home-page: UNKNOWN
6
+ Author: BrynQ
7
+ Author-email: support@brynq.com
8
+ License: BrynQ License
9
+ Description: vPlan wrapper from BrynQ
10
+ Platform: UNKNOWN
@@ -0,0 +1,39 @@
1
+ import requests
2
+ import json
3
+ from typing import List, Union
4
+ from brynq_sdk.brynq import BrynQ
5
+ from brynq_sdk.vplan.activity import Activity
6
+ from brynq_sdk.vplan.item import Item
7
+ from brynq_sdk.vplan.project import Project
8
+ from brynq_sdk.vplan.resource import Resource
9
+ from brynq_sdk.vplan.time_tracking import TimeTracking
10
+ from brynq_sdk.vplan.user import User
11
+
12
+
13
+ class VPlan(BrynQ):
14
+ def __init__(self, label: Union[str, List], debug: bool = False):
15
+ """
16
+ A class to fetch data from the vPlan API. See https://developer.vplan.com/documentation/#tag/General/ for more information
17
+ """
18
+ super().__init__()
19
+ self.headers = self._get_credentials(label)
20
+ self.base_url = 'https://api.vplan.com/v1/'
21
+ self.activity = Activity(self)
22
+ self.item = Item(self)
23
+ self.project = Project(self)
24
+ self.resource = Resource(self)
25
+ self.time_tracking = TimeTracking(self)
26
+ self.user = User(self)
27
+
28
+ def _get_credentials(self, label) -> str:
29
+ """
30
+ Retrieve API key and env from the system credentials.
31
+ Args: label (Union[str, List]): The label or list of labels to get the credentials.
32
+ Returns: str: The authorization headers
33
+ """
34
+ credentials = self.get_system_credential(system='vplan', label=label)
35
+ headers = {
36
+ 'X-Api-Key': credentials['X-Api-Key'],
37
+ 'X-Api-Env': credentials['X-Api-Env']
38
+ }
39
+ return headers
@@ -0,0 +1,132 @@
1
+ import requests
2
+ from typing import Union, List
3
+ import warnings
4
+ import json
5
+ from brynq_sdk.vplan.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.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.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,83 @@
1
+ import pandas as pd
2
+ import requests
3
+
4
+
5
+ class GetData:
6
+ def __init__(self, vplan):
7
+ """
8
+ Initialize the GetData class.
9
+ Args: vplan: contains the vplan object with the headers and base_url
10
+ """
11
+ self.vplan = vplan
12
+
13
+ def get_data(self, endpoint: str, limit: int = 1000, filter: str = None, show: str = None, hide: str = None, archived: bool = False) -> pd.DataFrame:
14
+ """
15
+ Fetch data from a specified table from vPlan
16
+
17
+ This method constructs a request URL based on the table name and optional filter,
18
+ sends a request to the vPlan API, parses the JSON response, and converts it
19
+ to a pandas DataFrame. The DataFrame columns are cleaned to remove unnecessary
20
+ characters and duplicate columns are handled.
21
+
22
+ Args: endpoint (str): The name of the endpoint to fetch data from.
23
+ 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
24
+ Example: ?filter=created_at:gt:2020-09-26,and,(id:not:starts_with:0000,or,id:contains:FFFF)
25
+
26
+ Colon : is the separator between the field, operator(s) and value.
27
+ Comma , can be used to combine filter statements by using ,and, or ,or,.
28
+ Braces (...) can be used to group filter statements.
29
+
30
+ The following operators are supported
31
+
32
+ operator description
33
+ eq equal to the given value
34
+ gt greater than the given value
35
+ gte greater than or equal to the given value
36
+ lt lesser than the given value
37
+ lte lesser than or equal to the given value
38
+ not negation of the operator that follows it
39
+ contains has occurrence of the given value
40
+ starts_with starts with the given value
41
+ end_with ends with the given value
42
+
43
+ warning: Currently the comma , and colon : are not supported within the filter value
44
+
45
+ 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
46
+ 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
47
+
48
+ Returns: pd.DataFrame: The fetched data as a pandas DataFrame.
49
+ """
50
+ # Initial URL with subscription-key
51
+ url = f"{self.vplan.base_url}{endpoint}?"
52
+ if limit:
53
+ url += f'limit={limit}&'
54
+ if filter:
55
+ url += f'filter={filter}&'
56
+ if show:
57
+ url += f'show={show}&'
58
+ if hide:
59
+ url += f'hide={hide}&'
60
+ if archived:
61
+ url += f'archived={archived}&'
62
+
63
+ all_data = []
64
+ offset = 0
65
+ received = 0
66
+ while True:
67
+ final_url = f'{url}offset={offset}'
68
+ response = requests.get(url=final_url, headers=self.vplan.headers)
69
+ response.raise_for_status()
70
+ result = response.json()
71
+ data = result.get('data', result)
72
+ all_data.extend(data)
73
+
74
+ # Determine if we need to continue fetching data
75
+ count = result.get('count', 0)
76
+ received += len(data)
77
+ offset += limit
78
+ if received >= count:
79
+ break
80
+
81
+
82
+ df = pd.DataFrame(all_data)
83
+ return df
@@ -0,0 +1,141 @@
1
+ import requests
2
+ from typing import Union, List
3
+ import warnings
4
+ import json
5
+ from brynq_sdk.vplan.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.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.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,129 @@
1
+ import requests
2
+ from typing import Union, List
3
+ import warnings
4
+ import json
5
+ from brynq_sdk.vplan.get_data import GetData
6
+
7
+
8
+ class Project:
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_project_list(self, filter: str = None, show: str = None, hide: str = None):
18
+ """
19
+ Get all the projects from vPlan: https://developer.vplan.com/documentation/#tag/Project/paths/~1project/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='project', filter=filter, show=show, hide=hide)
48
+ return df
49
+
50
+ def post_project(self, data: dict) -> requests.Response:
51
+ """
52
+ Create a new project in vPlan: https://developer.vplan.com/documentation/#tag/Project/paths/~1project/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 project in.
58
+ data (dict): The data to create the new project with.
59
+
60
+ Returns: requests.Response: The response from the vPlan API.
61
+ """
62
+ required_fields = ['name']
63
+ allowed_fields = ['code', 'description', 'external_ref']
64
+ self.__check_fields(data=data, required_fields=required_fields, allowed_fields=allowed_fields)
65
+
66
+ url = f"{self.vplan.base_url}project"
67
+
68
+ base_body = {
69
+ "name": data['name']
70
+ }
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({"external_ref": data['external_ref']}) if 'external_ref' in data else base_body
76
+
77
+ response = requests.request('POST', url, headers=self.vplan.headers, data=base_body)
78
+ return response
79
+
80
+ def update_project(self, project_id: str, data: dict) -> requests.Response:
81
+ """
82
+ Update an existing project in vPlan: https://developer.vplan.com/documentation/#tag/Project/paths/~1project~1%7Bproject_id%7D/put
83
+
84
+ This method constructs a request URL based on the endpoint and sends a POST request
85
+ to the vPlan API with the provided data.
86
+
87
+ Args: endpoint (str): The name of the endpoint to create a new project in.
88
+ data (dict): The data to create the new project with.
89
+
90
+ Returns: requests.Response: The response from the vPlan API.
91
+ """
92
+ required_fields = ['name']
93
+ allowed_fields = ['code', 'description', 'external_ref']
94
+ self.__check_fields(data=data, required_fields=required_fields, allowed_fields=allowed_fields)
95
+
96
+ url = f"{self.vplan.base_url}project/{project_id}"
97
+
98
+ base_body = {
99
+ "name": data['name']
100
+ }
101
+ # Add fields that you want to update a dict (adding to body itself is too much text)
102
+ base_body.update({"code": data['code']}) if 'code' in data else base_body
103
+ base_body.update({"description": data['description']}) if 'description' in data else base_body
104
+ base_body.update({"external_ref": data['external_ref']}) if 'external_ref' in data else base_body
105
+
106
+ response = requests.request('PUT', url, headers=self.vplan.headers, json=base_body)
107
+ return response
108
+
109
+ def delete_project(self, project_id):
110
+ """
111
+ Delete an existing project in vPlan: https://developer.vplan.com/documentation/#tag/Project/paths/~1project~1%7Bproject_id%7D/delete
112
+ This method constructs a request URL based on the endpoint and sends a DELETE request to the vPlan API.
113
+ """
114
+ url = f"{self.vplan.base_url}project/{project_id}"
115
+ response = requests.request('DELETE', url, headers=self.vplan.headers)
116
+ return response
117
+
118
+ @staticmethod
119
+ def __check_fields(data: Union[dict, List], required_fields: List, allowed_fields: List):
120
+ if isinstance(data, dict):
121
+ data = data.keys()
122
+
123
+ for field in data:
124
+ if field not in allowed_fields and field not in required_fields:
125
+ warnings.warn('Field {field} is not implemented. Optional fields are: {allowed_fields}'.format(field=field, allowed_fields=tuple(allowed_fields)))
126
+
127
+ for field in required_fields:
128
+ if field not in data:
129
+ raise ValueError('Field {field} is required. Required fields are: {required_fields}'.format(field=field, required_fields=tuple(required_fields)))
@@ -0,0 +1,142 @@
1
+ import requests
2
+ from typing import Union, List
3
+ import warnings
4
+ import json
5
+ from brynq_sdk.vplan.get_data import GetData
6
+
7
+
8
+ class Resource:
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_resource_list(self, filter: str = None, show: str = None, hide: str = None, archived: bool = False):
18
+ """
19
+ Get all the resources from vPlan: https://developer.vplan.com/documentation/#tag/Resource/paths/~1resource/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='resource', filter=filter, show=show, hide=hide, archived=archived)
48
+ return df
49
+
50
+ def post_resource(self, data: dict) -> requests.Response:
51
+ """
52
+ Create a new resource in vPlan: https://developer.vplan.com/documentation/#tag/Resource/paths/~1resource/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 resource in.
58
+ data (dict): The data to create the new resource with.
59
+
60
+ Returns: requests.Response: The response from the vPlan API.
61
+ """
62
+ required_fields = ['name']
63
+ allowed_fields = ['type', 'start_date', 'workdays', 'description', 'end_date', 'integration_schedule', 'avatar', 'color_hex', '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}resource"
67
+
68
+ base_body = {
69
+ "name": data['name']
70
+ }
71
+ # Add fields that you want to update a dict (adding to body itself is too much text)
72
+ base_body.update({"type": data['type']}) if 'type' in data else base_body
73
+ base_body.update({"start_date": data['start_date']}) if 'start_date' in data else base_body
74
+ base_body.update({"workdays": data['workdays']}) if 'workdays' in data else base_body
75
+ base_body.update({"description": data['description']}) if 'description' in data else base_body
76
+ base_body.update({"end_date": data['end_date']}) if 'end_date' in data else base_body
77
+ base_body.update({"integration_schedule": data['integration_schedule']}) if 'integration_schedule' in data else base_body
78
+ base_body.update({"avatar": data['avatar']}) if 'avatar' in data else base_body
79
+ base_body.update({"color_hex": data['color_hex']}) if 'color_hex' in data else base_body
80
+ base_body.update({"external_ref": data['external_ref']}) if 'external_ref' in data else base_body
81
+ base_body.update({"archive": data['archive']}) if 'archive' in data else base_body
82
+
83
+ response = requests.request('POST', url, headers=self.vplan.headers, data=json.dumps(base_body))
84
+ return response
85
+
86
+ def update_resource(self, resource_id: str, data: dict) -> requests.Response:
87
+ """
88
+ Update an existing resource in vPlan: https://developer.vplan.com/documentation/#tag/Resource/paths/~1resource/put
89
+
90
+ This method constructs a request URL based on the endpoint and sends a POST request
91
+ to the vPlan API with the provided data.
92
+
93
+ Args: endpoint (str): The name of the endpoint to create a new resource in.
94
+ data (dict): The data to create the new resource with.
95
+
96
+ Returns: requests.Response: The response from the vPlan API.
97
+ """
98
+ required_fields = []
99
+ allowed_fields = ['name', 'type', 'start_date', 'workdays', 'description', 'end_date', 'integration_schedule', 'avatar', 'color_hex', 'external_ref', 'archive']
100
+ self.__check_fields(data=data, required_fields=required_fields, allowed_fields=allowed_fields)
101
+
102
+ url = f"{self.vplan.base_url}resource/{resource_id}"
103
+
104
+ base_body = {}
105
+
106
+ # Add fields that you want to update a dict (adding to body itself is too much text)
107
+ base_body.update({"name": data['name']}) if 'name' in data else base_body
108
+ base_body.update({"type": data['type']}) if 'type' in data else base_body
109
+ base_body.update({"start_date": data['start_date']}) if 'start_date' in data else base_body
110
+ base_body.update({"workdays": data['workdays']}) if 'workdays' in data else base_body
111
+ base_body.update({"description": data['description']}) if 'description' in data else base_body
112
+ base_body.update({"end_date": data['end_date']}) if 'end_date' in data else base_body
113
+ base_body.update({"integration_schedule": data['integration_schedule']}) if 'integration_schedule' in data else base_body
114
+ base_body.update({"avatar": data['avatar']}) if 'avatar' in data else base_body
115
+ base_body.update({"color_hex": data['color_hex']}) if 'color_hex' in data else base_body
116
+ base_body.update({"external_ref": data['external_ref']}) if 'external_ref' in data else base_body
117
+ base_body.update({"archive": data['archive']}) if 'archive' in data else base_body
118
+
119
+ response = requests.request('PUT', url, headers=self.vplan.headers, json=json.dumps(base_body))
120
+ return response
121
+
122
+ def delete_resource(self, resource_id):
123
+ """
124
+ Delete an existing resource in vPlan: https://developer.vplan.com/documentation/#tag/Resource/paths/~1resource~1%7Bresource_id%7D/delete
125
+ This method constructs a request URL based on the endpoint and sends a DELETE request to the vPlan API.
126
+ """
127
+ url = f"{self.vplan.base_url}resource/{resource_id}"
128
+ response = requests.request('DELETE', url, headers=self.vplan.headers)
129
+ return response
130
+
131
+ @staticmethod
132
+ def __check_fields(data: Union[dict, List], required_fields: List, allowed_fields: List):
133
+ if isinstance(data, dict):
134
+ data = data.keys()
135
+
136
+ for field in data:
137
+ if field not in allowed_fields and field not in required_fields:
138
+ warnings.warn('Field {field} is not implemented. Optional fields are: {allowed_fields}'.format(field=field, allowed_fields=tuple(allowed_fields)))
139
+
140
+ for field in required_fields:
141
+ if field not in data:
142
+ raise ValueError('Field {field} is required. Required fields are: {required_fields}'.format(field=field, required_fields=tuple(required_fields)))
@@ -0,0 +1,144 @@
1
+ import requests
2
+ from typing import Union, List
3
+ import warnings
4
+ import json
5
+ from brynq_sdk.vplan.get_data import GetData
6
+
7
+
8
+ class TimeTracking:
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_time_tracking_list(self, filter: str = None, show: str = None, hide: str = None):
18
+ """
19
+ Get all the time records from vPlan: https://developer.vplan.com/documentation/#tag/Time-Tracking/paths/~1time_tracking/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='time_tracking', filter=filter, show=show, hide=hide)
48
+ return df
49
+
50
+ def post_time_tracking(self, data: dict) -> requests.Response:
51
+ """
52
+ Create a new time record in vPlan: https://developer.vplan.com/documentation/#tag/Time-Tracking/paths/~1time_tracking/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 time record in.
58
+ data (dict): The data to create the new time record with.
59
+
60
+ Returns: requests.Response: The response from the vPlan API.
61
+ """
62
+ required_fields = ['activity_id', 'start']
63
+ allowed_fields = ['card_id', 'end', 'duration', 'status', 'user_id', 'note', 'locked', 'synchronized_at', 'external_ref', 'external_note', 'external_failed']
64
+ self.__check_fields(data=data, required_fields=required_fields, allowed_fields=allowed_fields)
65
+
66
+ url = f"{self.vplan.base_url}time_tracking"
67
+
68
+ base_body = {
69
+ "activity_id": data['activity_id'],
70
+ "start": data['start']
71
+ }
72
+ # Add fields that you want to update a dict (adding to body itself is too much text)
73
+ base_body.update({"card_id": data['card_id']}) if 'card_id' in data else base_body
74
+ base_body.update({"end": data['end']}) if 'end' in data else base_body
75
+ base_body.update({"duration": data['duration']}) if 'duration' in data else base_body
76
+ base_body.update({"status": data['status']}) if 'status' in data else base_body
77
+ base_body.update({"user_id": data['user_id']}) if 'user_id' in data else base_body
78
+ base_body.update({"note": data['note']}) if 'note' in data else base_body
79
+ base_body.update({"locked": data['locked']}) if 'locked' in data else base_body
80
+ base_body.update({"synchronized_at": data['synchronized_at']}) if 'synchronized_at' in data else base_body
81
+ base_body.update({"external_ref": data['external_ref']}) if 'external_ref' in data else base_body
82
+ base_body.update({"external_note": data['external_note']}) if 'external_note' in data else base_body
83
+ base_body.update({"external_failed": data['external_failed']}) if 'external_failed' in data else base_body
84
+
85
+ response = requests.request('POST', url, headers=self.vplan.headers, data=base_body)
86
+ return response
87
+
88
+ def update_time_tracking(self, time_tracking_id: str, data: dict) -> requests.Response:
89
+ """
90
+ Update an existing time record in vPlan: https://developer.vplan.com/documentation/#tag/Time-Tracking/paths/~1time_tracking~1%7Btime_tracking_id%7D/put
91
+
92
+ This method constructs a request URL based on the endpoint and sends a POST request
93
+ to the vPlan API with the provided data.
94
+
95
+ Args: endpoint (str): The name of the endpoint to create a new time record in.
96
+ data (dict): The data to create the new time record with.
97
+
98
+ Returns: requests.Response: The response from the vPlan API.
99
+ """
100
+ required_fields = []
101
+ allowed_fields = ['activity_id', 'start', 'card_id', 'end', 'duration', 'status', 'user_id', 'note', 'locked', 'synchronized_at', 'external_ref', 'external_note', 'external_failed']
102
+ self.__check_fields(data=data, required_fields=required_fields, allowed_fields=allowed_fields)
103
+
104
+ url = f"{self.vplan.base_url}time_tracking/{time_tracking_id}"
105
+
106
+ base_body = {
107
+ }
108
+ # Add fields that you want to update a dict (adding to body itself is too much text)
109
+ base_body.update({"activity_id": data['activity_id']}) if 'activity_id' in data else base_body
110
+ base_body.update({"start": data['start']}) if 'start' in data else base_body
111
+ base_body.update({"code": data['code']}) if 'code' in data else base_body
112
+ base_body.update({"description": data['description']}) if 'description' in data else base_body
113
+ base_body.update({"stock_management": data['stock_management']}) if 'stock_management' in data else base_body
114
+ base_body.update({"unit": data['unit']}) if 'unit' in data else base_body
115
+ base_body.update({"type": data['type']}) if 'type' in data else base_body
116
+ base_body.update({"location": data['location']}) if 'location' in data else base_body
117
+ base_body.update({"note": data['note']}) if 'note' in data else base_body
118
+ base_body.update({"external_ref": data['external_ref']}) if 'external_ref' in data else base_body
119
+
120
+ response = requests.request('PUT', url, headers=self.vplan.headers, json=base_body)
121
+ return response
122
+
123
+ def delete_time_tracking(self, time_tracking_id):
124
+ """
125
+ Delete an existing time record in vPlan: https://developer.vplan.com/documentation/#tag/Time-Tracking/paths/~1time_tracking~1%7Btime_tracking_id%7D/delete
126
+ This method constructs a request URL based on the endpoint and sends a DELETE request to the vPlan API.
127
+ """
128
+ url = f"{self.vplan.base_url}time_tracking/{time_tracking_id}"
129
+ response = requests.request('DELETE', url, headers=self.vplan.headers)
130
+ return response
131
+
132
+
133
+ @staticmethod
134
+ def __check_fields(data: Union[dict, List], required_fields: List, allowed_fields: List):
135
+ if isinstance(data, dict):
136
+ data = data.keys()
137
+
138
+ for field in data:
139
+ if field not in allowed_fields and field not in required_fields:
140
+ warnings.warn('Field {field} is not implemented. Optional fields are: {allowed_fields}'.format(field=field, allowed_fields=tuple(allowed_fields)))
141
+
142
+ for field in required_fields:
143
+ if field not in data:
144
+ raise ValueError('Field {field} is required. Required fields are: {required_fields}'.format(field=field, required_fields=tuple(required_fields)))
@@ -0,0 +1,141 @@
1
+ import requests
2
+ from typing import Union, List
3
+ import warnings
4
+ import json
5
+ from brynq_sdk.vplan.get_data import GetData
6
+
7
+
8
+ class User:
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_user_list(self, filter: str = None, show: str = None, hide: str = None):
18
+ """
19
+ Get all the users from vPlan: https://developer.vplan.com/documentation/#tag/user/paths/~1user/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='user', filter=filter, show=show, hide=hide)
48
+ return df
49
+
50
+ def post_user(self, data: dict) -> requests.Response:
51
+ """
52
+ Create a new user in vPlan: https://developer.vplan.com/documentation/#tag/user/paths/~1user/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 user in.
58
+ data (dict): The data to create the new user 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}user"
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.headers, data=base_body)
83
+ return response
84
+
85
+ def update_user(self, user_id: str, data: dict) -> requests.Response:
86
+ """
87
+ Update an existing user in vPlan: https://developer.vplan.com/documentation/#tag/user/paths/~1user~1%7Buser_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 user in.
93
+ data (dict): The data to create the new user 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}user/{user_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.headers, json=base_body)
118
+ return response
119
+
120
+ def delete_user(self, user_id):
121
+ """
122
+ Delete an existing user in vPlan: https://developer.vplan.com/documentation/#tag/user/paths/~1user~1%7Buser_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}user/{user_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,10 @@
1
+ Metadata-Version: 1.0
2
+ Name: brynq-sdk-vplan
3
+ Version: 0.1.0
4
+ Summary: vPlan wrapper from BrynQ
5
+ Home-page: UNKNOWN
6
+ Author: BrynQ
7
+ Author-email: support@brynq.com
8
+ License: BrynQ License
9
+ Description: vPlan wrapper from BrynQ
10
+ Platform: UNKNOWN
@@ -0,0 +1,15 @@
1
+ setup.py
2
+ brynq_sdk/vplan/__init__.py
3
+ brynq_sdk/vplan/activity.py
4
+ brynq_sdk/vplan/get_data.py
5
+ brynq_sdk/vplan/item.py
6
+ brynq_sdk/vplan/project.py
7
+ brynq_sdk/vplan/resource.py
8
+ brynq_sdk/vplan/time_tracking.py
9
+ brynq_sdk/vplan/user.py
10
+ brynq_sdk_vplan.egg-info/PKG-INFO
11
+ brynq_sdk_vplan.egg-info/SOURCES.txt
12
+ brynq_sdk_vplan.egg-info/dependency_links.txt
13
+ brynq_sdk_vplan.egg-info/not-zip-safe
14
+ brynq_sdk_vplan.egg-info/requires.txt
15
+ brynq_sdk_vplan.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ brynq-sdk-brynq>=1
2
+ pandas<3,>=2
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,17 @@
1
+ from setuptools import setup
2
+
3
+ setup(
4
+ name='brynq_sdk_vplan',
5
+ version='0.1.0',
6
+ description='vPlan wrapper from BrynQ',
7
+ long_description='vPlan wrapper from BrynQ',
8
+ author='BrynQ',
9
+ author_email='support@brynq.com',
10
+ packages=["brynq_sdk.vplan"],
11
+ license='BrynQ License',
12
+ install_requires=[
13
+ 'brynq-sdk-brynq>=1',
14
+ 'pandas>=2,<3'
15
+ ],
16
+ zip_safe=False,
17
+ )