pysodafair 0.1.62__py3-none-any.whl

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 (77) hide show
  1. pysoda/__init__.py +0 -0
  2. pysoda/constants.py +3 -0
  3. pysoda/core/__init__.py +10 -0
  4. pysoda/core/dataset_generation/__init__.py +11 -0
  5. pysoda/core/dataset_generation/manifestSession/__init__.py +1 -0
  6. pysoda/core/dataset_generation/manifestSession/manifest_session.py +146 -0
  7. pysoda/core/dataset_generation/upload.py +3951 -0
  8. pysoda/core/dataset_importing/__init__.py +1 -0
  9. pysoda/core/dataset_importing/import_dataset.py +662 -0
  10. pysoda/core/metadata/__init__.py +20 -0
  11. pysoda/core/metadata/code_description.py +109 -0
  12. pysoda/core/metadata/constants.py +32 -0
  13. pysoda/core/metadata/dataset_description.py +188 -0
  14. pysoda/core/metadata/excel_utils.py +41 -0
  15. pysoda/core/metadata/helpers.py +250 -0
  16. pysoda/core/metadata/manifest.py +112 -0
  17. pysoda/core/metadata/manifest_package/__init__.py +2 -0
  18. pysoda/core/metadata/manifest_package/manifest.py +0 -0
  19. pysoda/core/metadata/manifest_package/manifest_import.py +29 -0
  20. pysoda/core/metadata/manifest_package/manifest_writer.py +666 -0
  21. pysoda/core/metadata/performances.py +46 -0
  22. pysoda/core/metadata/resources.py +53 -0
  23. pysoda/core/metadata/samples.py +184 -0
  24. pysoda/core/metadata/sites.py +51 -0
  25. pysoda/core/metadata/subjects.py +172 -0
  26. pysoda/core/metadata/submission.py +91 -0
  27. pysoda/core/metadata/text_metadata.py +47 -0
  28. pysoda/core/metadata_templates/CHANGES +1 -0
  29. pysoda/core/metadata_templates/LICENSE +1 -0
  30. pysoda/core/metadata_templates/README.md +4 -0
  31. pysoda/core/metadata_templates/__init__.py +0 -0
  32. pysoda/core/metadata_templates/code_description.xlsx +0 -0
  33. pysoda/core/metadata_templates/code_parameters.xlsx +0 -0
  34. pysoda/core/metadata_templates/dataset_description.xlsx +0 -0
  35. pysoda/core/metadata_templates/manifest.xlsx +0 -0
  36. pysoda/core/metadata_templates/performances.xlsx +0 -0
  37. pysoda/core/metadata_templates/resources.xlsx +0 -0
  38. pysoda/core/metadata_templates/samples.xlsx +0 -0
  39. pysoda/core/metadata_templates/sites.xlsx +0 -0
  40. pysoda/core/metadata_templates/subjects.xlsx +0 -0
  41. pysoda/core/metadata_templates/subjects_pools_samples_structure.xlsx +0 -0
  42. pysoda/core/metadata_templates/subjects_pools_samples_structure_example.xlsx +0 -0
  43. pysoda/core/metadata_templates/submission.xlsx +0 -0
  44. pysoda/core/permissions/__init__.py +1 -0
  45. pysoda/core/permissions/permissions.py +31 -0
  46. pysoda/core/pysoda/__init__.py +2 -0
  47. pysoda/core/pysoda/soda.py +34 -0
  48. pysoda/core/pysoda/soda_object.py +55 -0
  49. pysoda/core/upload_manifests/__init__.py +1 -0
  50. pysoda/core/upload_manifests/upload_manifests.py +37 -0
  51. pysoda/schema/__init__.py +0 -0
  52. pysoda/schema/code_description.json +629 -0
  53. pysoda/schema/dataset_description.json +295 -0
  54. pysoda/schema/manifest.json +60 -0
  55. pysoda/schema/performances.json +44 -0
  56. pysoda/schema/resources.json +39 -0
  57. pysoda/schema/samples.json +97 -0
  58. pysoda/schema/sites.json +38 -0
  59. pysoda/schema/soda_schema.json +664 -0
  60. pysoda/schema/subjects.json +131 -0
  61. pysoda/schema/submission_schema.json +28 -0
  62. pysoda/utils/__init__.py +9 -0
  63. pysoda/utils/authentication.py +381 -0
  64. pysoda/utils/config.py +68 -0
  65. pysoda/utils/exceptions.py +156 -0
  66. pysoda/utils/logger.py +6 -0
  67. pysoda/utils/metadata_utils.py +74 -0
  68. pysoda/utils/pennsieveAgentUtils.py +11 -0
  69. pysoda/utils/pennsieveUtils.py +118 -0
  70. pysoda/utils/profile.py +28 -0
  71. pysoda/utils/schema_validation.py +133 -0
  72. pysoda/utils/time_utils.py +5 -0
  73. pysoda/utils/upload_utils.py +108 -0
  74. pysodafair-0.1.62.dist-info/METADATA +190 -0
  75. pysodafair-0.1.62.dist-info/RECORD +77 -0
  76. pysodafair-0.1.62.dist-info/WHEEL +4 -0
  77. pysodafair-0.1.62.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,131 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "type": "array",
4
+ "minItems": 1,
5
+ "maxItems": 28,
6
+ "items": [
7
+ {
8
+ "type": "object",
9
+ "properties": {
10
+ "subject_id": {
11
+ "type": "string",
12
+ "description": "Unique identifier for the subject. This should be a unique identifier for the subject within the dataset.",
13
+ "minLength": 1
14
+ },
15
+ "pool_id": {
16
+ "type": "string",
17
+ "description": "Unique identifier for the pool associated with the subject. This should be a unique identifier for the pool within the dataset."
18
+ },
19
+ "subject_experimental_group": {
20
+ "type": "string",
21
+ "description": "Experimental group associated with the subject. This should provide information about the experimental conditions or treatment groups to which the subject belongs."
22
+ },
23
+ "age": {
24
+ "type": "string",
25
+ "description": "Age of the subject. This should be a numeric value representing the age of the subject at the time of data collection."
26
+ },
27
+ "sex": {
28
+ "type": "string",
29
+ "description": "Sex of the subject."
30
+ },
31
+ "species": {
32
+ "type": "string",
33
+ "description": "Species of the subject. This should be the scientific name of the species."
34
+ },
35
+ "strain": {
36
+ "type": "string",
37
+ "description": "Strain of the subject. This should provide information about the genetic background of the subject."
38
+ },
39
+ "rrid_for_strain": {
40
+ "type": "string",
41
+ "description": "RRID for strain. This should be a unique identifier for the strain in the Research Resource Identifier (RRID) database."
42
+ },
43
+ "age_category": {
44
+ "type": "string",
45
+ "description": "Age category of the subject. This should provide information about the age range of the subject."
46
+ },
47
+ "also_in_dataset": {
48
+ "type": "string",
49
+ "description": "Indicates if the subject is also part of another dataset."
50
+ },
51
+ "member_of": {
52
+ "type": "string",
53
+ "description": "Indicates the group or pool the subject is a member of."
54
+ },
55
+ "metadata_only": {
56
+ "type": "string",
57
+ "description": "Indicates if this entry is metadata only."
58
+ },
59
+ "laboratory_internal_id": {
60
+ "type": "string",
61
+ "description": "Internal identifier used by the laboratory for the subject."
62
+ },
63
+ "date_of_birth": {
64
+ "type": "string",
65
+ "description": "Date of birth of the subject."
66
+ },
67
+ "age_range_min": {
68
+ "type": "string",
69
+ "description": "Minimum age range of the subject."
70
+ },
71
+ "age_range_max": {
72
+ "type": "string",
73
+ "description": "Maximum age range of the subject."
74
+ },
75
+ "body_mass": {
76
+ "type": "string",
77
+ "description": "Body mass of the subject."
78
+ },
79
+ "genotype": {
80
+ "type": "string",
81
+ "description": "Genotype of the subject."
82
+ },
83
+ "phenotype": {
84
+ "type": "string",
85
+ "description": "Phenotype of the subject."
86
+ },
87
+ "handedness": {
88
+ "type": "string",
89
+ "description": "Handedness of the subject."
90
+ },
91
+ "reference_atlas": {
92
+ "type": "string",
93
+ "description": "Reference atlas used for the subject."
94
+ },
95
+ "experimental_log_file_path": {
96
+ "type": "string",
97
+ "description": "Path to the experimental log file for the subject."
98
+ },
99
+ "experiment_date": {
100
+ "type": "string",
101
+ "description": "Date of the experiment involving the subject."
102
+ },
103
+ "disease_or_disorder": {
104
+ "type": "string",
105
+ "description": "Disease or disorder associated with the subject."
106
+ },
107
+ "intervention": {
108
+ "type": "string",
109
+ "description": "Intervention applied to the subject."
110
+ },
111
+ "disease_model": {
112
+ "type": "string",
113
+ "description": "Disease model associated with the subject."
114
+ },
115
+ "protocol_title": {
116
+ "type": "string",
117
+ "description": "Title of the protocol used for the subject."
118
+ },
119
+ "protocol_url_or_doi": {
120
+ "type": "string",
121
+ "description": "URL or DOI of the protocol used for the subject."
122
+ }
123
+ },
124
+ "required": ["subject_id"],
125
+ "additionalProperties": {
126
+ "type": "string",
127
+ "description": "Additional properties for the subject. This can include any other relevant information about the subject that is not covered by the predefined fields."
128
+ }
129
+ }
130
+ ]
131
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "type": "object",
4
+ "properties": {
5
+ "consortium_data_standard": {
6
+ "type": "string",
7
+ "description": "The consortium data standard used for the submssion. Examples include SDS, BIDS, etc."
8
+ },
9
+ "funding_consortium": {
10
+ "type": "string",
11
+ "description": "The name of the funding consortium that supported the research. Examples include BRAIN Initiative, Allen Institute, etc."
12
+ },
13
+ "award_number": {
14
+ "type": "string",
15
+ "description": "The award number associated with the funding. This is typically a unique identifier assigned by the funding agency."
16
+ },
17
+ "milestone_achieved": {
18
+ "type": "array",
19
+ "items": { "type": "string" },
20
+ "description": "A list of milestones achieved in the research. This should include specific goals or objectives that were met during the project."
21
+ },
22
+ "milestone_completion_date": {
23
+ "type": "string",
24
+ "description": "The date when the milestone was completed. This should be in ISO 8601 format."
25
+ }
26
+ },
27
+ "required": []
28
+ }
@@ -0,0 +1,9 @@
1
+ from .upload_utils import generate_options_set, generating_locally, generating_on_ps, uploading_with_ps_account, uploading_to_existing_ps_dataset, can_resume_prior_upload, virtual_dataset_empty, get_dataset_id
2
+ from .exceptions import PropertyNotSetError, ConfigProfileNotSet, FailedToFetchPennsieveDatasets, FailedToFetchPennsieveDatasets, PennsieveActionNoPermission, PennsieveDatasetCannotBeFound, EmptyDatasetError, LocalDatasetMissingSpecifiedFiles, validation_error_message, GenericUploadError, PennsieveUploadException, PennsieveDatasetNameTaken, PennsieveDatasetNameInvalid, PennsieveAccountInvalid, GenerateOptionsNotSet, PennsieveDatasetFilesInvalid, PennsieveAgentError, PennsieveAccountInformationFailedAuthentication
3
+ from .pennsieveAgentUtils import connect_pennsieve_client
4
+ from .schema_validation import validate_schema, get_sds_headers, get_schema_path
5
+ from .config import format_agent_profile_name
6
+ from .pennsieveUtils import get_dataset_id, check_forbidden_characters_ps, get_users_dataset_list
7
+ from .authentication import get_access_token, create_request_headers
8
+ from .metadata_utils import column_check, returnFileURL, remove_high_level_folder_from_path, double_extensions, get_name_extension
9
+ from .time_utils import TZLOCAL
@@ -0,0 +1,381 @@
1
+ import boto3
2
+ import requests
3
+ from os.path import expanduser, join, exists
4
+ from configparser import ConfigParser
5
+ from .config import format_agent_profile_name
6
+ from configparser import ConfigParser
7
+ import time
8
+ import boto3
9
+ from os import mkdir
10
+ import time
11
+ from .exceptions import ConfigProfileNotSet, PennsieveAccountInformationFailedAuthentication
12
+
13
+ from ..constants import PENNSIEVE_URL
14
+
15
+ from .logger import logger
16
+
17
+ from .profile import create_unique_profile_name
18
+
19
+ userpath = expanduser("~")
20
+ configpath = join(userpath, ".pennsieve", "config.ini")
21
+
22
+ # Variables for token caching
23
+
24
+ cached_access_token = None
25
+ last_fetch_time = 0
26
+ TOKEN_CACHE_DURATION = 60 # Amount of time in seconds to cache the access token
27
+
28
+
29
+
30
+
31
+ def get_access_token(api_key=None, api_secret=None):
32
+ """
33
+ Creates a temporary access token for utilizing the Pennsieve API. Reads the api token and secret from the Pennsieve config.ini file.
34
+ get cognito config . If no target profile name is provided the default profile is used.
35
+ """
36
+ global cached_access_token, last_fetch_time, TOKEN_CACHE_DURATION # Variables used for token caching
37
+ global logger
38
+ current_time = time.time()
39
+
40
+ # If the cached_access_token is not None and the last fetch time is less than the cache duration, return the cached access token
41
+ if cached_access_token and current_time - last_fetch_time < TOKEN_CACHE_DURATION:
42
+ return cached_access_token
43
+
44
+ r = requests.get(f"{PENNSIEVE_URL}/authentication/cognito-config")
45
+ r.raise_for_status()
46
+
47
+ cognito_app_client_id = r.json()["tokenPool"]["appClientId"]
48
+ cognito_region_name = r.json()["region"]
49
+
50
+ cognito_idp_client = boto3.client(
51
+ "cognito-idp",
52
+ region_name=cognito_region_name,
53
+ aws_access_key_id="",
54
+ aws_secret_access_key="",
55
+ )
56
+
57
+ # use the default profile values for auth if no api_key or api_secret is provided
58
+ if api_key is None or api_secret is None:
59
+ api_key = get_profile_name_from_api_key("api_token")
60
+ api_secret = get_profile_name_from_api_key("api_secret")
61
+
62
+
63
+
64
+
65
+ login_response = cognito_idp_client.initiate_auth(
66
+ AuthFlow="USER_PASSWORD_AUTH",
67
+ AuthParameters={"USERNAME": api_key, "PASSWORD": api_secret},
68
+ ClientId=cognito_app_client_id,
69
+ )
70
+
71
+ cached_access_token = login_response["AuthenticationResult"]["AccessToken"]
72
+ last_fetch_time = current_time
73
+
74
+ return cached_access_token
75
+
76
+
77
+ def clear_cached_access_token():
78
+ global cached_access_token, last_fetch_time
79
+ cached_access_token = None
80
+
81
+
82
+ def get_cognito_userpool_access_token(email, password):
83
+ """
84
+ Creates a temporary access token for utilizing the Pennsieve API. Utilizes email and password to authenticate with the Pennsieve Cognito Userpool
85
+ which provides higher privileges than the API token and secret flow.
86
+ """
87
+
88
+
89
+ try:
90
+ response = requests.get(f"{PENNSIEVE_URL}/authentication/cognito-config")
91
+ response.raise_for_status()
92
+ cognito_app_client_id = response.json()["userPool"]["appClientId"]
93
+ cognito_region = response.json()["userPool"]["region"]
94
+ cognito_client = boto3.client(
95
+ "cognito-idp",
96
+ region_name=cognito_region,
97
+ aws_access_key_id="",
98
+ aws_secret_access_key="",
99
+ )
100
+ except Exception as e:
101
+ raise Exception(e) from e
102
+
103
+ try:
104
+ login_response = cognito_client.initiate_auth(
105
+ AuthFlow="USER_PASSWORD_AUTH",
106
+ AuthParameters={"USERNAME": email, "PASSWORD": password},
107
+ ClientId=cognito_app_client_id,
108
+ )
109
+ except Exception as e:
110
+ raise PennsieveAccountInformationFailedAuthentication("Invalid email or password") from e
111
+ try:
112
+ access_token = login_response["AuthenticationResult"]["AccessToken"]
113
+ response = requests.get(
114
+ f"{PENNSIEVE_URL}/user", headers={"Authorization": f"Bearer {access_token}"}
115
+ )
116
+ response.raise_for_status()
117
+ except Exception as e:
118
+ raise e
119
+
120
+ return access_token
121
+
122
+ def get_profile_name_from_api_key(key):
123
+ config = ConfigParser()
124
+ config.read(configpath)
125
+ if "global" not in config:
126
+ raise Exception("Profile has not been set")
127
+
128
+ keyname = config["global"]["default_profile"]
129
+
130
+ if keyname in config and key in config[keyname]:
131
+ return config[keyname][key]
132
+ return None
133
+
134
+
135
+ def bf_delete_account(keyname):
136
+ """
137
+ Args:
138
+ keyname: name of local Pennsieve account key (string)
139
+ Action:
140
+ Deletes account information from the Pennsieve config file
141
+ """
142
+ config = ConfigParser()
143
+ config.read(configpath)
144
+ config.remove_section(keyname)
145
+ with open(configpath, "w") as configfile:
146
+ config.write(configfile)
147
+
148
+
149
+
150
+ def bf_delete_default_profile():
151
+ config = ConfigParser()
152
+ config.read(configpath)
153
+
154
+ if "global" not in config:
155
+ return
156
+
157
+ config.remove_section("global")
158
+
159
+ with open(configpath, "w") as configfile:
160
+ config.write(configfile)
161
+
162
+
163
+
164
+
165
+ def bf_add_account_username(keyname, key, secret):
166
+ """
167
+ Associated with 'Add account' button in 'Login to your Pennsieve account' section of SODA
168
+
169
+ Args:
170
+ keyname: Name of the account to be associated with the given credentials (string)
171
+ key: API key (string)
172
+ secret: API Secret (string)
173
+ Action:
174
+ Adds account to the Pennsieve configuration file (local machine)
175
+ """
176
+ global logger
177
+
178
+
179
+ # format the keyname to lowercase and replace '.' with '_'
180
+ formatted_account_name = format_agent_profile_name(keyname)
181
+
182
+ bf_delete_default_profile()
183
+ try:
184
+ bfpath = join(userpath, ".pennsieve")
185
+ # Load existing or create new config file
186
+ config = ConfigParser()
187
+ if exists(configpath):
188
+ config.read(configpath)
189
+ elif not exists(bfpath):
190
+ mkdir(bfpath)
191
+
192
+ # Add agent section
193
+ agentkey = "agent"
194
+ if not config.has_section(agentkey):
195
+ config.add_section(agentkey)
196
+ config.set(agentkey, "port", "9000")
197
+ config.set(agentkey, "upload_workers", "10")
198
+ config.set(agentkey, "upload_chunk_size", "32")
199
+
200
+ # Add new account if it does not already exist
201
+ if not config.has_section(formatted_account_name):
202
+ config.add_section(formatted_account_name)
203
+
204
+ config.set(formatted_account_name, "api_token", key)
205
+ config.set(formatted_account_name, "api_secret", secret)
206
+
207
+ # set profile name in global section
208
+ if not config.has_section("global"):
209
+ config.add_section("global")
210
+ config.set("global", "default_profile", formatted_account_name)
211
+
212
+
213
+ with open(configpath, "w") as configfile:
214
+ config.write(configfile)
215
+
216
+ except Exception as e:
217
+ raise e
218
+
219
+ # Check key and secret are valid, if not delete account from config
220
+ try:
221
+ get_access_token()
222
+ except Exception as e:
223
+ logger.error(e)
224
+ bf_delete_account(formatted_account_name)
225
+ raise PennsieveAccountInformationFailedAuthentication("Pkease check that the key name, key value, and secret value are entered properly.") from e
226
+
227
+ try:
228
+ if not config.has_section("global"):
229
+ config.add_section("global")
230
+
231
+ default_acc = config["global"]
232
+ default_acc["default_profile"] = keyname
233
+
234
+ with open(configpath, "w+") as configfile:
235
+ config.write(configfile)
236
+
237
+ return {"message": f"Successfully added account {formatted_account_name}"}
238
+
239
+ except Exception as e:
240
+ bf_delete_account(keyname)
241
+ raise e
242
+
243
+
244
+ def delete_duplicate_keys(token, keyname):
245
+ try:
246
+
247
+ headers = {
248
+ "Content-Type": "application/json",
249
+ "Authorization": f"Bearer {token}",
250
+ }
251
+
252
+ r = requests.get(f"{PENNSIEVE_URL}/token", headers=headers)
253
+ r.raise_for_status()
254
+
255
+ tokens = r.json()
256
+
257
+ for token in tokens:
258
+ if token["name"] == keyname:
259
+ r = requests.delete(f"{PENNSIEVE_URL}/token/{token['key']}", headers=headers)
260
+ r.raise_for_status()
261
+ except Exception as e:
262
+ raise e
263
+
264
+
265
+ def create_pennsieve_api_key_secret(email, password, machine_username_specifier):
266
+
267
+ api_key = get_cognito_userpool_access_token(email, password)
268
+
269
+ # TODO: Send in computer and profile of computer from frontend to this endpoint and use it in this function
270
+ profile_name = create_unique_profile_name(api_key, machine_username_specifier)
271
+
272
+
273
+ delete_duplicate_keys(api_key, "SODA-Pennsieve")
274
+ delete_duplicate_keys(api_key, profile_name)
275
+
276
+
277
+ url = "https://api.pennsieve.io/token/"
278
+
279
+ payload = {"name": f"{profile_name}"}
280
+ headers = {
281
+ "Accept": "*/*",
282
+ "Content-Type": "application/json",
283
+ "Authorization": f"Bearer {api_key}",
284
+ }
285
+
286
+ response = requests.request("POST", url, json=payload, headers=headers)
287
+ response.raise_for_status()
288
+ response = response.json()
289
+
290
+
291
+ # clear access token cache
292
+ clear_cached_access_token()
293
+
294
+ return {
295
+ "success": "success",
296
+ "key": response["key"],
297
+ "secret": response["secret"],
298
+ "name": profile_name
299
+ }
300
+
301
+
302
+ def get_access_token(api_key=None, api_secret=None):
303
+ """
304
+ Creates a temporary access token for utilizing the Pennsieve API. Reads the api token and secret from the Pennsieve config.ini file.
305
+ get cognito config . If no target profile name is provided the default profile is used.
306
+ """
307
+ global cached_access_token, last_fetch_time, TOKEN_CACHE_DURATION # Variables used for token caching
308
+ # global logger
309
+ current_time = time.time()
310
+
311
+ print("Current time:", current_time)
312
+
313
+
314
+
315
+ # If the cached_access_token is not None and the last fetch time is less than the cache duration, return the cached access token
316
+ if cached_access_token and current_time - last_fetch_time < TOKEN_CACHE_DURATION:
317
+ return cached_access_token
318
+
319
+ print("Cached token not returned")
320
+ r = requests.get(f"{PENNSIEVE_URL}/authentication/cognito-config")
321
+ r.raise_for_status()
322
+ print("Cognito config response:", r.json())
323
+
324
+ cognito_app_client_id = r.json()["tokenPool"]["appClientId"]
325
+ cognito_region_name = r.json()["region"]
326
+
327
+ cognito_idp_client = boto3.client(
328
+ "cognito-idp",
329
+ region_name=cognito_region_name,
330
+ aws_access_key_id="",
331
+ aws_secret_access_key="",
332
+ )
333
+
334
+ print("boto3 client done")
335
+
336
+ # use the default profile values for auth if no api_key or api_secret is provided
337
+ if api_key is None or api_secret is None:
338
+ api_key = get_profile_name_from_api_key("api_token")
339
+ api_secret = get_profile_name_from_api_key("api_secret")
340
+
341
+
342
+ login_response = cognito_idp_client.initiate_auth(
343
+ AuthFlow="USER_PASSWORD_AUTH",
344
+ AuthParameters={"USERNAME": api_key, "PASSWORD": api_secret},
345
+ ClientId=cognito_app_client_id,
346
+ )
347
+
348
+ cached_access_token = login_response["AuthenticationResult"]["AccessToken"]
349
+ last_fetch_time = current_time
350
+
351
+ return cached_access_token
352
+
353
+ def get_profile_name_from_api_key(key):
354
+ config = ConfigParser()
355
+ config.read(configpath)
356
+ if "global" not in config:
357
+ raise ConfigProfileNotSet("global")
358
+
359
+ keyname = config["global"]["default_profile"]
360
+
361
+ if keyname in config and key in config[keyname]:
362
+ return config[keyname][key]
363
+ return None
364
+
365
+
366
+ def create_request_headers(ps_or_token):
367
+ """
368
+ Creates necessary HTTP headers for making Pennsieve API requests.
369
+ Input:
370
+ ps: Pennsieve object for a user that has been authenticated
371
+ """
372
+ if type(ps_or_token) == str:
373
+ return {
374
+ "Content-Type": "application/json",
375
+ "Authorization": f"Bearer {ps_or_token}",
376
+ }
377
+
378
+ return {
379
+ "Content-Type": "application/json",
380
+ "Authorization": f"Bearer {ps_or_token.get_user().session_token}",
381
+ }
pysoda/utils/config.py ADDED
@@ -0,0 +1,68 @@
1
+ """
2
+ Purpose: Functions for dealing with the pennsieve config file located at ~/.pennsieve/config.ini
3
+ """
4
+ from ..constants import PENNSIEVE_URL
5
+
6
+
7
+ def add_api_host_to_config(configparser, target_section_name, configpath):
8
+ """
9
+ Args:
10
+ configparser: configparser object
11
+ target_section_name: the section name to add the api_host to. Should be an account section.
12
+ configpath: the path to the config file ( ~/.pennsieve/config.ini )
13
+ Action:
14
+ Adds api_host to the section of the configparser object if it does not exist in the given section.
15
+ If given a section name that is 'agent' it will do nothing.
16
+ """
17
+
18
+ # do not add the api_host key to the agent section of the config
19
+ if target_section_name == 'agent':
20
+ return
21
+
22
+ if not configparser.has_option(target_section_name, "api_host"):
23
+ configparser.set(target_section_name, "api_host", PENNSIEVE_URL)
24
+
25
+ with open(configpath, "w+") as configfile:
26
+ configparser.write(configfile)
27
+
28
+
29
+
30
+ def format_agent_profile_name(profile_name):
31
+ """
32
+ Args:
33
+ profile_name: the profile name to format
34
+ Returns:
35
+ The formatted profile name
36
+ Notes:
37
+ We replace the '.' with '_' because the config parser on the Node side does not like '.' in the profile name.
38
+ """
39
+ return profile_name.lower().replace('.', '_').strip()
40
+
41
+
42
+ def lowercase_account_names(config, account_name, configpath):
43
+ """
44
+ Args:
45
+ config: configparser object
46
+ account_name: the account name to convert to lowercase
47
+ Action:
48
+ Converts the account name and global default_profile value to lowercase and updates the config file.
49
+ """
50
+ formatted_account_name = format_agent_profile_name(account_name)
51
+ # if the section exists lowercased do nothing
52
+ if config.has_section(formatted_account_name):
53
+ return
54
+
55
+ # add the section back with the lowercase account name
56
+ config.add_section(formatted_account_name)
57
+ config.set(formatted_account_name, "api_token", config.get(account_name, "api_token"))
58
+ config.set(formatted_account_name, "api_secret", config.get(account_name, "api_secret"))
59
+
60
+ # set the global default_profile option to lowercase
61
+ config.set("global", "default_profile", formatted_account_name)
62
+
63
+ # remove the unformatted account name
64
+ config.remove_section(account_name)
65
+
66
+ # finalize the changes
67
+ with open(configpath, "w+") as configfile:
68
+ config.write(configfile)