Skip to content

Commit 40792ec

Browse files
shivangj10shivangj-10GriffinBaxterSeequent
authored
Sample script for file service (SeequentEvo#159)
<!-- Thank you for taking the time to make a pull request. Please review our [contribution guide](https://github.com/SeequentEvo/evo-python-sdk/blob/main/CONTRIBUTING.md) and our [code of conduct](https://github.com/SeequentEvo/evo-python-sdk/blob/main/CONTRIBUTING.md) before opening your first pull request. --> ## Description Here are three sample scripts utilizing Evo File Service. - Evo File Input Script: Allows users to upload all files from a directory into a designated Evo workspace.  - Evo File Input-Output Script: Allows users to download files from Evo, process them to create new files, and then publish these new files to a specific Evo workspace.  - MX Deposit File to Evo File Script: Utilizes the MX Deposit Export API to download data from MX Deposit, which can then be uploaded to a designated Evo workspace. ## Checklist - [x] I have read the contributing guide and the code of conduct --------- Co-authored-by: Shivang Jariwala <97551095+shivangj-10@users.noreply.github.com> Co-authored-by: Griffin Baxter <74513598+GriffinBaxterSeequent@users.noreply.github.com>
1 parent 9e89555 commit 40792ec

5 files changed

Lines changed: 362 additions & 2 deletions

File tree

samples/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ Basic unstructured file management operations:
208208
- Download files from Evo
209209
- List and organize files
210210
- Delete files
211+
- Bonus: Sample Python scripts for file Input-Output in Evo
211212

212213
### 🌍 Geoscience objects
213214
**📁 [geoscience-objects](geoscience-objects/)**

samples/files/README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
1-
# Jupyter notebooks
1+
# Samples
22

3-
This directory contains a number of Jupyter notebook examples for using the Evo File API, in particular:
3+
This directory contains a number of Jupyter notebook and Python script examples for using the Evo File API, in particular:
44

5+
## Jupyter notebooks
56
* Uploading a file
67
* Downloading a file
78
* Listing files
89
* Deleting a file
910

11+
## Python script
12+
* [File Input Script](https://github.com/SeequentEvo/evo-python-sdk/blob/main/samples/files/scripts/file-input-script.py)
13+
* [File Input-Output Script](https://github.com/SeequentEvo/evo-python-sdk/blob/main/samples/files/scripts/file-input-output-script.py)
14+
* [MX Deposit File to Evo File Script](https://github.com/SeequentEvo/evo-python-sdk/blob/main/samples/files/scripts/MX-Deposit-file-to-evo-file-script.py)
15+
1016
## Requirements
1117

1218
* Python ^3.10
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# Copyright © 2025 Bentley Systems, Incorporated
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
# Unless required by applicable law or agreed to in writing, software
7+
# distributed under the License is distributed on an "AS IS" BASIS,
8+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9+
# See the License for the specific language governing permissions and
10+
# limitations under the License.
11+
12+
import asyncio
13+
import datetime
14+
import json
15+
import tempfile
16+
import time
17+
import uuid
18+
import zipfile
19+
from pathlib import Path
20+
21+
import requests
22+
23+
from evo.aio import AioTransport
24+
from evo.common import APIConnector, Environment
25+
from evo.files import FileAPIClient
26+
from evo.oauth import ClientCredentialsAuthorizer, EvoScopes, OAuthConnector
27+
28+
# Configuration
29+
CONFIG = {
30+
"mx": {
31+
"url": "https://app.mxdeposit.net/api/v3/collars/export/",
32+
"project_id": "<project_id>",
33+
"template_code": "<template_code>",
34+
"auth_token": "<api_key>",
35+
"client_id": "<client_id>",
36+
},
37+
"evo": {
38+
"USER_AGENT": "MXDepositToEvoScript",
39+
"CLIENT_ID": "<client_id>",
40+
"CLIENT_SECRET": "<client_secret>",
41+
"service_host": "<hub_url>",
42+
"org_id": "<org_id>",
43+
"workspace_id": "<workspace_id>",
44+
},
45+
}
46+
47+
48+
def export_collars(config):
49+
payload = json.dumps(
50+
{
51+
"project": config["project_id"],
52+
"template_code": config["template_code"],
53+
}
54+
)
55+
headers = {
56+
"Content-Type": "application/json",
57+
"Authorization": config["auth_token"],
58+
"Client-ID": config["client_id"],
59+
}
60+
print("Request made at (UTC):", datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%d %H:%M:%S"))
61+
response = requests.post(config["url"], headers=headers, data=payload)
62+
if response.status_code != 200:
63+
print(f"Error: Received status code {response.status_code}")
64+
print(f"Response content: {response.text}")
65+
print(response.json())
66+
try:
67+
data = response.json()
68+
operation_uid = data["jobs"][0]["parameters"]["body"]["operation_uid"]
69+
print("operation_uid:", operation_uid)
70+
return operation_uid
71+
except Exception as e:
72+
print("Could not extract operation_uid:", e)
73+
return None
74+
75+
76+
def poll_export_status(operation_uid, config, interval=30, timeout=8 * 60 * 60):
77+
headers = {
78+
"Content-Type": "application/json",
79+
"Authorization": config["auth_token"],
80+
"Client-ID": config["client_id"],
81+
}
82+
status_url = f"https://app.mxdeposit.net/export-status/{operation_uid}"
83+
start_time = time.time()
84+
while True:
85+
response = requests.get(status_url, headers=headers)
86+
try:
87+
data = response.json()
88+
print(f"Polling: {data}")
89+
if data.get("state") == "done":
90+
return data.get("url")
91+
except Exception as e:
92+
print("Error parsing response:", e)
93+
if time.time() - start_time > timeout:
94+
print("Polling timed out.")
95+
return None
96+
time.sleep(interval)
97+
98+
99+
def download_and_extract_zip(download_url, temp_dir):
100+
export_file = temp_dir / "export.zip"
101+
response = requests.get(download_url)
102+
export_file.write_bytes(response.content)
103+
with zipfile.ZipFile(export_file, "r") as zip_ref:
104+
zip_ref.extractall(temp_dir)
105+
print(f"Files extracted to: {temp_dir}")
106+
107+
108+
async def upload_csv_files(temp_dir, file_client, connector):
109+
success = True
110+
for file_path in temp_dir.glob("*.csv"):
111+
try:
112+
ctx = await file_client.prepare_upload_by_path(file_path.name)
113+
await ctx.upload_from_path(str(file_path), connector.transport)
114+
except Exception as e:
115+
print(f"Error uploading {file_path.name}: {e}")
116+
success = False
117+
if success:
118+
print("All CSV files uploaded successfully.")
119+
return success
120+
121+
122+
def main():
123+
mx_cfg = CONFIG["mx"]
124+
evo_cfg = CONFIG["evo"]
125+
126+
operation_uid = export_collars(mx_cfg)
127+
if not operation_uid:
128+
return
129+
130+
download_url = poll_export_status(operation_uid, mx_cfg)
131+
if not download_url:
132+
return
133+
134+
environment = Environment(
135+
hub_url=evo_cfg["service_host"],
136+
org_id=uuid.UUID(evo_cfg["org_id"]),
137+
workspace_id=uuid.UUID(evo_cfg["workspace_id"]),
138+
)
139+
transport = AioTransport(user_agent=evo_cfg["USER_AGENT"])
140+
authorizer = ClientCredentialsAuthorizer(
141+
oauth_connector=OAuthConnector(
142+
transport=transport,
143+
client_id=evo_cfg["CLIENT_ID"],
144+
client_secret=evo_cfg["CLIENT_SECRET"],
145+
),
146+
scopes=EvoScopes.all_evo,
147+
)
148+
connector = APIConnector(environment.hub_url, transport, authorizer)
149+
file_client = FileAPIClient(connector=connector, environment=environment)
150+
151+
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
152+
153+
script_dir = Path(__file__).parent
154+
with tempfile.TemporaryDirectory(dir=script_dir) as temp_dir:
155+
temp_path = Path(temp_dir)
156+
download_and_extract_zip(download_url, temp_path)
157+
asyncio.run(upload_csv_files(temp_path, file_client, connector))
158+
159+
160+
if __name__ == "__main__":
161+
main()
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# Copyright © 2025 Bentley Systems, Incorporated
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
# Unless required by applicable law or agreed to in writing, software
7+
# distributed under the License is distributed on an "AS IS" BASIS,
8+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9+
# See the License for the specific language governing permissions and
10+
# limitations under the License.
11+
12+
13+
import asyncio
14+
import tempfile
15+
import uuid
16+
from pathlib import Path
17+
18+
import requests
19+
20+
from evo.aio import AioTransport
21+
from evo.common import APIConnector, Environment
22+
from evo.files import FileAPIClient
23+
from evo.oauth import ClientCredentialsAuthorizer, EvoScopes, OAuthConnector
24+
25+
# Configuration
26+
CONFIG = {
27+
"evo": {
28+
"USER_AGENT": "Evo File I/O Script",
29+
"CLIENT_ID": "<client_id>",
30+
"CLIENT_SECRET": "<client_secret>",
31+
"service_host": "<hub_url>",
32+
"org_id": "<org_id>",
33+
"workspace_id": "<workspace_id>",
34+
"evo_input_file_path": "<input_file>.csv",
35+
"evo_output_file_path": "<output_file>.csv",
36+
}
37+
}
38+
39+
40+
async def download_csv_file(temp_dir, source_csv_filename, file_client):
41+
"""
42+
Downloads a CSV file from Evo workspace using the file_client and saves it to the temp directory.
43+
Returns True if successful, False otherwise.
44+
"""
45+
success = True
46+
try:
47+
ctx = await file_client.prepare_download_by_path(source_csv_filename)
48+
download_url = await ctx.get_download_url()
49+
50+
response = requests.get(download_url)
51+
if response.status_code == 200:
52+
output_file = Path(temp_dir) / source_csv_filename
53+
output_file.write_bytes(response.content)
54+
print(f"File saved to: {output_file}")
55+
else:
56+
print(f"Failed to download file. Status code: {response.status_code}")
57+
success = False
58+
59+
except Exception as e:
60+
print(f"Error downloading {source_csv_filename}: {e}")
61+
success = False
62+
if success:
63+
print(f"{source_csv_filename} file downloaded successfully.")
64+
return success
65+
66+
67+
async def upload_csv_files(temp_dir, processed_csv_filename, file_client, connector):
68+
"""
69+
Uploads the processed CSV file from the temp directory to Evo using the file_client.
70+
Returns True if successful, False otherwise.
71+
"""
72+
success = True
73+
try:
74+
output_file = Path(temp_dir) / processed_csv_filename
75+
ctx = await file_client.prepare_upload_by_path(processed_csv_filename)
76+
await ctx.upload_from_path(str(output_file), connector.transport)
77+
print(f"File {processed_csv_filename} uploaded successfully.")
78+
except Exception as e:
79+
print(f"Error uploading {processed_csv_filename}: {e}")
80+
success = False
81+
return success
82+
83+
84+
def main():
85+
evo_cfg = CONFIG["evo"]
86+
87+
environment = Environment(
88+
hub_url=evo_cfg["service_host"],
89+
org_id=uuid.UUID(evo_cfg["org_id"]),
90+
workspace_id=uuid.UUID(evo_cfg["workspace_id"]),
91+
)
92+
transport = AioTransport(user_agent=evo_cfg["USER_AGENT"])
93+
authorizer = ClientCredentialsAuthorizer(
94+
oauth_connector=OAuthConnector(
95+
transport=transport,
96+
client_id=evo_cfg["CLIENT_ID"],
97+
client_secret=evo_cfg["CLIENT_SECRET"],
98+
),
99+
scopes=EvoScopes.all_evo,
100+
)
101+
connector = APIConnector(environment.hub_url, transport, authorizer)
102+
file_client = FileAPIClient(connector=connector, environment=environment)
103+
source_csv_filename = evo_cfg["evo_input_file_path"]
104+
processed_csv_filename = evo_cfg["evo_output_file_path"]
105+
106+
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
107+
108+
script_dir = Path(__file__).parent
109+
with tempfile.TemporaryDirectory(dir=script_dir) as temp_dir:
110+
asyncio.run(download_csv_file(temp_dir, source_csv_filename, file_client))
111+
112+
# Process the file downloaded from Evo and generate a new output file to be uploaded to Evo.
113+
114+
asyncio.run(upload_csv_files(temp_dir, processed_csv_filename, file_client, connector))
115+
116+
117+
if __name__ == "__main__":
118+
main()
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Copyright © 2025 Bentley Systems, Incorporated
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
# Unless required by applicable law or agreed to in writing, software
7+
# distributed under the License is distributed on an "AS IS" BASIS,
8+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9+
# See the License for the specific language governing permissions and
10+
# limitations under the License.
11+
12+
import asyncio
13+
import uuid
14+
from pathlib import Path
15+
16+
from evo.aio import AioTransport
17+
from evo.common import APIConnector, Environment
18+
from evo.files import FileAPIClient
19+
from evo.oauth import ClientCredentialsAuthorizer, EvoScopes, OAuthConnector
20+
21+
# Configuration
22+
CONFIG = {
23+
"evo": {
24+
"USER_AGENT": "Evo File Input Script",
25+
"CLIENT_ID": "<client_id>",
26+
"CLIENT_SECRET": "<client_secret>",
27+
"service_host": "<hub_url>",
28+
"org_id": "<org_id>",
29+
"workspace_id": "<workspace_id>",
30+
"file_path": "C:\\Documents\\drilling_data", # Path to the directory containing CSV files
31+
}
32+
}
33+
34+
35+
async def upload_csv_files(script_dir, file_client, connector):
36+
success = True
37+
for file_path in script_dir.glob("*.csv"):
38+
print(f"Uploading file: {file_path.name}")
39+
try:
40+
ctx = await file_client.prepare_upload_by_path(file_path.name)
41+
await ctx.upload_from_path(str(file_path), connector.transport)
42+
except Exception as e:
43+
print(f"Error uploading {file_path.name}: {e}")
44+
success = False
45+
if success:
46+
print("All CSV files uploaded successfully.")
47+
return success
48+
49+
50+
def main():
51+
evo_cfg = CONFIG["evo"]
52+
script_dir = Path(evo_cfg["file_path"])
53+
54+
environment = Environment(
55+
hub_url=evo_cfg["service_host"],
56+
org_id=uuid.UUID(evo_cfg["org_id"]),
57+
workspace_id=uuid.UUID(evo_cfg["workspace_id"]),
58+
)
59+
transport = AioTransport(user_agent=evo_cfg["USER_AGENT"])
60+
authorizer = ClientCredentialsAuthorizer(
61+
oauth_connector=OAuthConnector(
62+
transport=transport, client_id=evo_cfg["CLIENT_ID"], client_secret=evo_cfg["CLIENT_SECRET"]
63+
),
64+
scopes=EvoScopes.all_evo,
65+
)
66+
connector = APIConnector(environment.hub_url, transport, authorizer)
67+
file_client = FileAPIClient(connector=connector, environment=environment)
68+
69+
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
70+
asyncio.run(upload_csv_files(script_dir, file_client, connector))
71+
72+
73+
if __name__ == "__main__":
74+
main()

0 commit comments

Comments
 (0)