evo-files 0.1.0__tar.gz → 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,24 @@
1
+ # IDE Settings
2
+ .idea/
3
+ .project
4
+ .pydevproject
5
+ .vscode/
6
+
7
+ # Distribution / Packaging
8
+ *.egg*
9
+ *.whl
10
+ __pycache__/
11
+ site/
12
+ build/
13
+
14
+ # Reporting
15
+ .coverage
16
+
17
+ # Environmnents
18
+ venv/
19
+ venv*/
20
+ .venv/
21
+ .env
22
+ .tox/
23
+
24
+ .DS_store
@@ -1,21 +1,21 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: evo-files
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Python SDK for using the Seequent Evo File API
5
5
  Author-email: Seequent <support@seequent.com>
6
- Requires-Python: >=3.10
7
- Description-Content-Type: text/markdown
8
6
  License-File: LICENSE.md
7
+ Requires-Python: >=3.10
9
8
  Requires-Dist: evo-sdk-common>=0.1.0
10
9
  Requires-Dist: pydantic<3,>=2
11
10
  Provides-Extra: aiohttp
12
- Requires-Dist: evo-sdk-common[aiohttp]>=0.1.0; extra == "aiohttp"
11
+ Requires-Dist: evo-sdk-common[aiohttp]>=0.1.0; extra == 'aiohttp'
13
12
  Provides-Extra: notebooks
14
- Requires-Dist: evo-sdk-common[notebooks]>=0.1.0; extra == "notebooks"
15
- Dynamic: license-file
13
+ Requires-Dist: evo-sdk-common[notebooks]>=0.1.0; extra == 'notebooks'
14
+ Description-Content-Type: text/markdown
16
15
 
17
16
  <p align="center"><a href="https://seequent.com" target="_blank"><picture><source media="(prefers-color-scheme: dark)" srcset="https://developer.seequent.com/img/seequent-logo-dark.svg" alt="Seequent logo" width="400" /><img src="https://developer.seequent.com/img/seequent-logo.svg" alt="Seequent logo" width="400" /></picture></a></p>
18
17
  <p align="center">
18
+ <a href="https://pypi.org/project/evo-files/"><img alt="PyPI - Version" src="https://img.shields.io/pypi/v/evo-files" /></a>
19
19
  <a href="https://github.com/SeequentEvo/evo-python-sdk/actions/workflows/on-push.yaml"><img src="https://github.com/SeequentEvo/evo-python-sdk/actions/workflows/on-push.yaml/badge.svg" alt="" /></a>
20
20
  </p>
21
21
  <p align="center">
@@ -36,12 +36,28 @@ formats and sizes are accepted.
36
36
  pip install evo-files
37
37
  ```
38
38
 
39
+ ## Usage
40
+
41
+ To get up and running quickly with the Evo File SDK, start by configuring your
42
+ [environment and API connector](https://github.com/SeequentEvo/evo-python-sdk/blob/main/packages/evo-sdk-common/docs/quickstart.md).
43
+
44
+ You can then use the `FileAPIClient` to perform operations, for example:
45
+
46
+ ```python
47
+ from evo.files import FileAPIClient
48
+
49
+ file_client = FileAPIClient(environment, connector)
50
+ files = await file_client.list_files()
51
+ ```
52
+
53
+ For some interactive Jupyter notebook examples, see the [examples folder](https://github.com/SeequentEvo/evo-python-sdk/tree/main/packages/evo-files/docs/examples).
54
+
39
55
  ## Developing the library
40
56
 
41
57
  For instructions on contributing to the development of this library, please refer to the [evo-python-sdk documentation](https://github.com/seequentevo/evo-python-sdk).
42
58
 
43
59
  ## License
44
- The Python SDK for Evo is open source and licensed under the [Apache 2.0 license.](./LICENSE.md).
60
+ The Python SDK for Evo is open source and licensed under the [Apache 2.0 license.](https://github.com/SeequentEvo/evo-python-sdk/tree/main/packages/evo-files/./LICENSE.md).
45
61
 
46
62
  Copyright © 2025 Bentley Systems, Incorporated.
47
63
 
@@ -1,14 +1,10 @@
1
- [build-system]
2
- requires = ["setuptools >= 61.0.0"]
3
- build-backend = "setuptools.build_meta"
4
-
5
1
  [project]
6
2
  name = "evo-files"
7
3
  description = "Python SDK for using the Seequent Evo File API"
8
- version = "0.1.0"
4
+ version = "0.1.1"
9
5
  requires-python = ">=3.10"
10
6
  license-files = ["LICENSE.md"]
11
- readme = "README.md"
7
+ dynamic = ["readme"]
12
8
  authors = [
13
9
  { name = "Seequent", email = "support@seequent.com" }
14
10
  ]
@@ -40,10 +36,6 @@ notebooks = [
40
36
  "jupyter",
41
37
  ]
42
38
 
43
- [tool.setuptools.packages.find]
44
- where = ["src"]
45
- namespaces = true
46
-
47
39
  [tool.ruff]
48
40
  src = ["src", "tests"]
49
41
  line-length = 120
@@ -51,15 +43,24 @@ line-length = 120
51
43
  [tool.ruff.lint]
52
44
  extend-select = ["I", "RUF022"]
53
45
 
54
- [tool.bumpver]
55
- current_version = "v0.1.0"
56
- version_pattern = "vMAJOR.MINOR.PATCH[.PYTAGNUM]"
57
- commit = true
58
- tag = true
59
- commit_message = "Bump version from {old_version} to {new_version}"
46
+ [build-system]
47
+ requires = ["hatchling", "hatch-fancy-pypi-readme"]
48
+ build-backend = "hatchling.build"
60
49
 
61
- [tool.bumpver.file_patterns]
62
- "pyproject.toml" = [
63
- 'version = "{pep440_version}"',
64
- 'current_version = "{version}"',
65
- ]
50
+ [tool.hatch.build.targets.sdist]
51
+ include = ["src"]
52
+
53
+ [tool.hatch.build.targets.wheel]
54
+ packages = ["src/evo"]
55
+
56
+ [tool.hatch.metadata.hooks.fancy-pypi-readme]
57
+ content-type = "text/markdown"
58
+
59
+ [[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]]
60
+ path = "README.md"
61
+
62
+ [[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]]
63
+ # Literal TOML strings (single quotes) need no escaping of backslashes.
64
+ # Converts relative links to absolute links in PyPI
65
+ pattern = '\[(.+?)\]\(((?!https?://)\S+?)\)'
66
+ replacement = '[\1](https://github.com/SeequentEvo/evo-python-sdk/tree/main/packages/$HFPR_PACKAGE_NAME/\g<2>)'
@@ -144,7 +144,7 @@ class FileAPIDownload(Download[FileMetadata], _FileIOMixin):
144
144
  def __init__(self, connector: APIConnector, metadata: FileMetadata, initial_url: str) -> None:
145
145
  """
146
146
  :param connector: The connector to use for the API calls.
147
- :param metadata: The metadata of the file that wil be downloaded.
147
+ :param metadata: The metadata of the file that will be downloaded.
148
148
  :param initial_url: The initial URL to use for the download.
149
149
  """
150
150
  super().__init__(connector, initial_url)
evo_files-0.1.0/README.md DELETED
@@ -1,42 +0,0 @@
1
- <p align="center"><a href="https://seequent.com" target="_blank"><picture><source media="(prefers-color-scheme: dark)" srcset="https://developer.seequent.com/img/seequent-logo-dark.svg" alt="Seequent logo" width="400" /><img src="https://developer.seequent.com/img/seequent-logo.svg" alt="Seequent logo" width="400" /></picture></a></p>
2
- <p align="center">
3
- <a href="https://github.com/SeequentEvo/evo-python-sdk/actions/workflows/on-push.yaml"><img src="https://github.com/SeequentEvo/evo-python-sdk/actions/workflows/on-push.yaml/badge.svg" alt="" /></a>
4
- </p>
5
- <p align="center">
6
- <a href="https://developer.seequent.com/" target="_blank">Seequent Developer Portal</a>
7
- &bull; <a href="https://community.seequent.com/" target="_blank">Seequent Community</a>
8
- &bull; <a href="https://seequent.com" target="_blank">Seequent website</a>
9
- </p>
10
-
11
- # Evo File API Client
12
-
13
- The File API provides the ability to manage files of any type or size, associated with your Evo workspace.
14
- Enable your product with Evo connected workflows by integrating with the Seequent Evo File API. Most file
15
- formats and sizes are accepted.
16
-
17
- ## Installation
18
-
19
- ```
20
- pip install evo-files
21
- ```
22
-
23
- ## Developing the library
24
-
25
- For instructions on contributing to the development of this library, please refer to the [evo-python-sdk documentation](https://github.com/seequentevo/evo-python-sdk).
26
-
27
- ## License
28
- The Python SDK for Evo is open source and licensed under the [Apache 2.0 license.](./LICENSE.md).
29
-
30
- Copyright © 2025 Bentley Systems, Incorporated.
31
-
32
- Licensed under the Apache License, Version 2.0 (the "License").
33
- You may obtain a copy of the License at
34
-
35
- http://www.apache.org/licenses/LICENSE-2.0
36
-
37
- Unless required by applicable law or agreed to in writing, software
38
- distributed under the License is distributed on an "AS IS" BASIS,
39
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
40
- See the License for the specific language governing permissions and
41
- limitations under the License.
42
-
evo_files-0.1.0/setup.cfg DELETED
@@ -1,4 +0,0 @@
1
- [egg_info]
2
- tag_build =
3
- tag_date = 0
4
-
@@ -1,58 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: evo-files
3
- Version: 0.1.0
4
- Summary: Python SDK for using the Seequent Evo File API
5
- Author-email: Seequent <support@seequent.com>
6
- Requires-Python: >=3.10
7
- Description-Content-Type: text/markdown
8
- License-File: LICENSE.md
9
- Requires-Dist: evo-sdk-common>=0.1.0
10
- Requires-Dist: pydantic<3,>=2
11
- Provides-Extra: aiohttp
12
- Requires-Dist: evo-sdk-common[aiohttp]>=0.1.0; extra == "aiohttp"
13
- Provides-Extra: notebooks
14
- Requires-Dist: evo-sdk-common[notebooks]>=0.1.0; extra == "notebooks"
15
- Dynamic: license-file
16
-
17
- <p align="center"><a href="https://seequent.com" target="_blank"><picture><source media="(prefers-color-scheme: dark)" srcset="https://developer.seequent.com/img/seequent-logo-dark.svg" alt="Seequent logo" width="400" /><img src="https://developer.seequent.com/img/seequent-logo.svg" alt="Seequent logo" width="400" /></picture></a></p>
18
- <p align="center">
19
- <a href="https://github.com/SeequentEvo/evo-python-sdk/actions/workflows/on-push.yaml"><img src="https://github.com/SeequentEvo/evo-python-sdk/actions/workflows/on-push.yaml/badge.svg" alt="" /></a>
20
- </p>
21
- <p align="center">
22
- <a href="https://developer.seequent.com/" target="_blank">Seequent Developer Portal</a>
23
- &bull; <a href="https://community.seequent.com/" target="_blank">Seequent Community</a>
24
- &bull; <a href="https://seequent.com" target="_blank">Seequent website</a>
25
- </p>
26
-
27
- # Evo File API Client
28
-
29
- The File API provides the ability to manage files of any type or size, associated with your Evo workspace.
30
- Enable your product with Evo connected workflows by integrating with the Seequent Evo File API. Most file
31
- formats and sizes are accepted.
32
-
33
- ## Installation
34
-
35
- ```
36
- pip install evo-files
37
- ```
38
-
39
- ## Developing the library
40
-
41
- For instructions on contributing to the development of this library, please refer to the [evo-python-sdk documentation](https://github.com/seequentevo/evo-python-sdk).
42
-
43
- ## License
44
- The Python SDK for Evo is open source and licensed under the [Apache 2.0 license.](./LICENSE.md).
45
-
46
- Copyright © 2025 Bentley Systems, Incorporated.
47
-
48
- Licensed under the Apache License, Version 2.0 (the "License").
49
- You may obtain a copy of the License at
50
-
51
- http://www.apache.org/licenses/LICENSE-2.0
52
-
53
- Unless required by applicable law or agreed to in writing, software
54
- distributed under the License is distributed on an "AS IS" BASIS,
55
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
56
- See the License for the specific language governing permissions and
57
- limitations under the License.
58
-
@@ -1,20 +0,0 @@
1
- LICENSE.md
2
- README.md
3
- pyproject.toml
4
- src/evo/files/__init__.py
5
- src/evo/files/_model_config.py
6
- src/evo/files/client.py
7
- src/evo/files/data.py
8
- src/evo/files/io.py
9
- src/evo/files/endpoints/__init__.py
10
- src/evo/files/endpoints/models.py
11
- src/evo/files/endpoints/api/__init__.py
12
- src/evo/files/endpoints/api/file_v2_api.py
13
- src/evo_files.egg-info/PKG-INFO
14
- src/evo_files.egg-info/SOURCES.txt
15
- src/evo_files.egg-info/dependency_links.txt
16
- src/evo_files.egg-info/requires.txt
17
- src/evo_files.egg-info/top_level.txt
18
- tests/test_file_api_client.py
19
- tests/test_file_io.py
20
- tests/test_make_name_safe.py
@@ -1,8 +0,0 @@
1
- evo-sdk-common>=0.1.0
2
- pydantic<3,>=2
3
-
4
- [aiohttp]
5
- evo-sdk-common[aiohttp]>=0.1.0
6
-
7
- [notebooks]
8
- evo-sdk-common[notebooks]>=0.1.0
@@ -1,534 +0,0 @@
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 json
13
- from unittest import mock
14
- from uuid import UUID
15
-
16
- from data import load_test_data
17
- from evo.common import Environment, HealthCheckType, Page, RequestMethod, ServiceUser
18
- from evo.common.test_tools import BASE_URL, ORG, WORKSPACE_ID, MockResponse, TestWithConnector, utc_datetime
19
- from evo.files import FileAPIClient, FileAPIDownload, FileAPIUpload, FileMetadata, FileVersion
20
-
21
-
22
- class TestFileApiClient(TestWithConnector):
23
- def setUp(self) -> None:
24
- TestWithConnector.setUp(self)
25
- self.environment = Environment(hub_url=BASE_URL, org_id=ORG.id, workspace_id=WORKSPACE_ID)
26
- self.file_api_client = FileAPIClient(connector=self.connector, environment=self.environment)
27
-
28
- @property
29
- def base_path(self) -> str:
30
- return f"file/v2/orgs/{self.environment.org_id}/workspaces/{self.environment.workspace_id}"
31
-
32
- async def test_check_service_health(self) -> None:
33
- """Test service health check implementation"""
34
- with mock.patch("evo.files.client.get_service_health", spec_set=True) as mock_get_service_health:
35
- await self.file_api_client.get_service_health()
36
- mock_get_service_health.assert_called_once_with(self.connector, "file", check_type=HealthCheckType.FULL)
37
-
38
- async def test_list_files_default_args(self) -> None:
39
- empty_content = load_test_data("list_files_empty.json")
40
- with self.transport.set_http_response(
41
- 200, json.dumps(empty_content), headers={"Content-Type": "application/json"}
42
- ):
43
- page = await self.file_api_client.list_files()
44
- self.assertIsInstance(page, Page)
45
- self.assertEqual([], page.items())
46
- self.assert_request_made(
47
- method=RequestMethod.GET,
48
- path=f"{self.base_path}/files?limit=5000&offset=0",
49
- headers={"Accept": "application/json"},
50
- )
51
-
52
- async def test_list_files_all_args(self) -> None:
53
- empty_content = load_test_data("list_files_empty.json")
54
- with self.transport.set_http_response(
55
- 200, json.dumps(empty_content), headers={"Content-Type": "application/json"}
56
- ):
57
- page = await self.file_api_client.list_files(limit=20, offset=10, name="x.csv")
58
- self.assertIsInstance(page, Page)
59
- self.assertEqual([], page.items())
60
- self.assert_request_made(
61
- method=RequestMethod.GET,
62
- path=f"{self.base_path}/files?limit=20&offset=10&file_name=x.csv",
63
- headers={"Accept": "application/json"},
64
- )
65
-
66
- async def test_list_files(self) -> None:
67
- content_0 = load_test_data("list_files_0.json")
68
- content_1 = load_test_data("list_files_1.json")
69
- responses = [
70
- MockResponse(status_code=200, content=json.dumps(content_0), headers={"Content-Type": "application/json"}),
71
- MockResponse(status_code=200, content=json.dumps(content_1), headers={"Content-Type": "application/json"}),
72
- ]
73
- self.transport.request.side_effect = responses
74
- page_one = await self.file_api_client.list_files(limit=2)
75
- expected_files_page_one = [
76
- FileMetadata(
77
- environment=self.environment,
78
- id=UUID(int=2),
79
- name="x.csv",
80
- created_by=ServiceUser(
81
- id=UUID(int=16),
82
- name="x y",
83
- email="test@example.com",
84
- ),
85
- created_at=utc_datetime(2020, 1, 1, 1, 30),
86
- modified_by=ServiceUser(
87
- id=UUID(int=16),
88
- name="x y",
89
- email="test@example.com",
90
- ),
91
- modified_at=utc_datetime(2020, 1, 1, 1, 30),
92
- parent="/A/",
93
- size=11,
94
- version_id="2",
95
- ),
96
- FileMetadata(
97
- environment=self.environment,
98
- id=UUID(int=3),
99
- name="y.csv",
100
- created_by=ServiceUser(
101
- id=UUID(int=17),
102
- name=None,
103
- email=None,
104
- ),
105
- created_at=utc_datetime(2020, 1, 2, 1, 30),
106
- modified_by=ServiceUser(
107
- id=UUID(int=17),
108
- name=None,
109
- email=None,
110
- ),
111
- modified_at=utc_datetime(2020, 1, 2, 1, 30),
112
- parent="/A/",
113
- size=12,
114
- version_id="1",
115
- ),
116
- ]
117
- self.assertIsInstance(page_one, Page)
118
- self.assertEqual(expected_files_page_one, page_one.items())
119
- self.assertEqual(0, page_one.offset)
120
- self.assertEqual(2, page_one.limit)
121
- self.assertFalse(page_one.is_last)
122
- self.assert_request_made(
123
- method=RequestMethod.GET,
124
- path=f"{self.base_path}/files?limit=2&offset=0",
125
- headers={"Accept": "application/json"},
126
- )
127
- self.transport.request.reset_mock()
128
-
129
- page_two = await self.file_api_client.list_files(offset=page_one.next_offset, limit=page_one.limit)
130
- expected_files_page_two = [
131
- FileMetadata(
132
- environment=self.environment,
133
- id=UUID(int=4),
134
- name="z.csv",
135
- created_by=ServiceUser(
136
- id=UUID(int=16),
137
- name="x y",
138
- email="test@example.com",
139
- ),
140
- created_at=utc_datetime(2020, 1, 1, 1, 30),
141
- modified_by=ServiceUser(
142
- id=UUID(int=16),
143
- name="x y",
144
- email="test@example.com",
145
- ),
146
- modified_at=utc_datetime(2020, 1, 1, 1, 30),
147
- parent="/B/",
148
- size=13,
149
- version_id="3",
150
- ),
151
- ]
152
- self.assertIsInstance(page_two, Page)
153
- self.assertEqual(expected_files_page_two, page_two.items())
154
- self.assertEqual(2, page_two.offset)
155
- self.assertEqual(2, page_two.limit)
156
- self.assertTrue(page_two.is_last)
157
- self.assert_request_made(
158
- method=RequestMethod.GET,
159
- path=f"{self.base_path}/files?limit=2&offset=2",
160
- headers={"Accept": "application/json"},
161
- )
162
-
163
- async def test_list_all_files(self) -> None:
164
- content_0 = load_test_data("list_files_0.json")
165
- content_1 = load_test_data("list_files_1.json")
166
- responses = [
167
- MockResponse(status_code=200, content=json.dumps(content_0), headers={"Content-Type": "application/json"}),
168
- MockResponse(status_code=200, content=json.dumps(content_1), headers={"Content-Type": "application/json"}),
169
- ]
170
- self.transport.request.side_effect = responses
171
- file_list = await self.file_api_client.list_all_files(limit_per_request=2)
172
- expected_files = [
173
- FileMetadata(
174
- environment=self.environment,
175
- id=UUID(int=2),
176
- name="x.csv",
177
- created_by=ServiceUser(
178
- id=UUID(int=16),
179
- name="x y",
180
- email="test@example.com",
181
- ),
182
- created_at=utc_datetime(2020, 1, 1, 1, 30),
183
- modified_by=ServiceUser(
184
- id=UUID(int=16),
185
- name="x y",
186
- email="test@example.com",
187
- ),
188
- modified_at=utc_datetime(2020, 1, 1, 1, 30),
189
- parent="/A/",
190
- size=11,
191
- version_id="2",
192
- ),
193
- FileMetadata(
194
- environment=self.environment,
195
- id=UUID(int=3),
196
- name="y.csv",
197
- created_by=ServiceUser(
198
- id=UUID(int=17),
199
- name=None,
200
- email=None,
201
- ),
202
- created_at=utc_datetime(2020, 1, 2, 1, 30),
203
- modified_by=ServiceUser(
204
- id=UUID(int=17),
205
- name=None,
206
- email=None,
207
- ),
208
- modified_at=utc_datetime(2020, 1, 2, 1, 30),
209
- parent="/A/",
210
- size=12,
211
- version_id="1",
212
- ),
213
- FileMetadata(
214
- environment=self.environment,
215
- id=UUID(int=4),
216
- name="z.csv",
217
- created_by=ServiceUser(
218
- id=UUID(int=16),
219
- name="x y",
220
- email="test@example.com",
221
- ),
222
- created_at=utc_datetime(2020, 1, 1, 1, 30),
223
- modified_by=ServiceUser(
224
- id=UUID(int=16),
225
- name="x y",
226
- email="test@example.com",
227
- ),
228
- modified_at=utc_datetime(2020, 1, 1, 1, 30),
229
- parent="/B/",
230
- size=13,
231
- version_id="3",
232
- ),
233
- ]
234
-
235
- self.assertEqual(expected_files, file_list)
236
- self.assert_any_request_made(
237
- method=RequestMethod.GET,
238
- path=f"{self.base_path}/files?limit=2&offset=0",
239
- headers={"Accept": "application/json"},
240
- )
241
- self.assert_any_request_made(
242
- method=RequestMethod.GET,
243
- path=f"{self.base_path}/files?limit=2&offset=2",
244
- headers={"Accept": "application/json"},
245
- )
246
-
247
- async def test_get_file_by_path(self) -> None:
248
- get_file_response = load_test_data("get_file.json")
249
- path = "points.csv"
250
- expected_metadata = FileMetadata(
251
- environment=self.environment,
252
- id=UUID(int=6),
253
- name="points.csv",
254
- created_by=ServiceUser(
255
- id=UUID(int=16),
256
- name="x y",
257
- email="test@example.com",
258
- ),
259
- created_at=utc_datetime(2020, 1, 1, 1, 30),
260
- modified_by=ServiceUser(
261
- id=UUID(int=16),
262
- name="x y",
263
- email="test@example.com",
264
- ),
265
- modified_at=utc_datetime(2020, 1, 1, 1, 30),
266
- parent="/",
267
- size=10,
268
- version_id="1",
269
- )
270
-
271
- with self.transport.set_http_response(
272
- status_code=200, content=json.dumps(get_file_response), headers={"Content-Type": "application/json"}
273
- ):
274
- actual_metadata = await self.file_api_client.get_file_by_path(path)
275
-
276
- self.assert_request_made(
277
- method=RequestMethod.GET,
278
- path=f"{self.base_path}/files/path/{path}",
279
- headers={"Accept": "application/json"},
280
- )
281
- self.assertEqual(expected_metadata, actual_metadata)
282
-
283
- async def test_prepare_download_by_path(self) -> None:
284
- get_file_response = load_test_data("get_file.json")
285
- path = "points.csv"
286
- expected_metadata = FileMetadata(
287
- environment=self.environment,
288
- id=UUID(int=6),
289
- name="points.csv",
290
- created_by=ServiceUser(
291
- id=UUID(int=16),
292
- name="x y",
293
- email="test@example.com",
294
- ),
295
- created_at=utc_datetime(2020, 1, 1, 1, 30),
296
- modified_by=ServiceUser(
297
- id=UUID(int=16),
298
- name="x y",
299
- email="test@example.com",
300
- ),
301
- modified_at=utc_datetime(2020, 1, 1, 1, 30),
302
- parent="/",
303
- size=10,
304
- version_id="1",
305
- )
306
-
307
- with self.transport.set_http_response(
308
- status_code=200, content=json.dumps(get_file_response), headers={"Content-Type": "application/json"}
309
- ):
310
- download = await self.file_api_client.prepare_download_by_path(path)
311
-
312
- self.assertIsInstance(download, FileAPIDownload)
313
- self.assertEqual(expected_metadata, download.metadata)
314
- self.assert_request_made(
315
- method=RequestMethod.GET,
316
- path=f"{self.base_path}/files/path/{path}",
317
- headers={"Accept": "application/json"},
318
- )
319
-
320
- async def test_get_file_by_id(self) -> None:
321
- get_file_response = load_test_data("get_file.json")
322
- file_id = UUID(int=6)
323
- expected_metadata = FileMetadata(
324
- environment=self.environment,
325
- id=file_id,
326
- name="points.csv",
327
- created_by=ServiceUser(
328
- id=UUID(int=16),
329
- name="x y",
330
- email="test@example.com",
331
- ),
332
- created_at=utc_datetime(2020, 1, 1, 1, 30),
333
- modified_by=ServiceUser(
334
- id=UUID(int=16),
335
- name="x y",
336
- email="test@example.com",
337
- ),
338
- modified_at=utc_datetime(2020, 1, 1, 1, 30),
339
- parent="/",
340
- size=10,
341
- version_id="1",
342
- )
343
-
344
- with self.transport.set_http_response(
345
- status_code=200, content=json.dumps(get_file_response), headers={"Content-Type": "application/json"}
346
- ):
347
- actual_metadata = await self.file_api_client.get_file_by_id(file_id)
348
-
349
- self.assert_request_made(
350
- method=RequestMethod.GET,
351
- path=f"{self.base_path}/files/{file_id}",
352
- headers={"Accept": "application/json"},
353
- )
354
- self.assertEqual(expected_metadata, actual_metadata)
355
-
356
- async def test_prepare_download_by_id(self) -> None:
357
- get_file_response = load_test_data("get_file.json")
358
- file_id = UUID(int=6)
359
- expected_metadata = FileMetadata(
360
- environment=self.environment,
361
- id=file_id,
362
- name="points.csv",
363
- created_by=ServiceUser(
364
- id=UUID(int=16),
365
- name="x y",
366
- email="test@example.com",
367
- ),
368
- created_at=utc_datetime(2020, 1, 1, 1, 30),
369
- modified_by=ServiceUser(
370
- id=UUID(int=16),
371
- name="x y",
372
- email="test@example.com",
373
- ),
374
- modified_at=utc_datetime(2020, 1, 1, 1, 30),
375
- parent="/",
376
- size=10,
377
- version_id="1",
378
- )
379
-
380
- with self.transport.set_http_response(
381
- status_code=200, content=json.dumps(get_file_response), headers={"Content-Type": "application/json"}
382
- ):
383
- download = await self.file_api_client.prepare_download_by_id(file_id)
384
-
385
- self.assertIsInstance(download, FileAPIDownload)
386
- self.assertEqual(expected_metadata, download.metadata)
387
- self.assert_request_made(
388
- method=RequestMethod.GET,
389
- path=f"{self.base_path}/files/{file_id}",
390
- headers={"Accept": "application/json"},
391
- )
392
-
393
- async def test_list_versions_by_path(self) -> None:
394
- file_path = "points.csv"
395
- list_versions_response = load_test_data("list_versions.json")
396
- with self.transport.set_http_response(
397
- 200, json.dumps(list_versions_response), headers={"Content-Type": "application/json"}
398
- ):
399
- versions = await self.file_api_client.list_versions_by_path(file_path)
400
- expected_versions = [
401
- FileVersion(
402
- version_id="3",
403
- created_at=utc_datetime(2020, 1, 3, 1, 30),
404
- created_by=ServiceUser(
405
- id=UUID(int=16),
406
- name="x y",
407
- email="test@example.com",
408
- ),
409
- ),
410
- FileVersion(
411
- version_id="2",
412
- created_at=utc_datetime(2020, 1, 2, 1, 30),
413
- created_by=ServiceUser(
414
- id=UUID(int=17),
415
- name="x z",
416
- email="test@example.com",
417
- ),
418
- ),
419
- FileVersion(
420
- version_id="1",
421
- created_at=utc_datetime(2020, 1, 1, 1, 30),
422
- created_by=ServiceUser(
423
- id=UUID("00000000-0000-0000-0000-000000000012"),
424
- name="x w",
425
- email="test@example.com",
426
- ),
427
- ),
428
- ]
429
-
430
- self.assertEqual(versions, expected_versions)
431
- self.assert_request_made(
432
- method=RequestMethod.GET,
433
- path=f"{self.base_path}/files/path/points.csv?include_versions=True",
434
- headers={"Accept": "application/json"},
435
- )
436
-
437
- async def test_list_versions_by_id(self) -> None:
438
- file_id = UUID(int=6)
439
- list_versions_response = load_test_data("list_versions.json")
440
- with self.transport.set_http_response(
441
- 200, json.dumps(list_versions_response), headers={"Content-Type": "application/json"}
442
- ):
443
- versions = await self.file_api_client.list_versions_by_id(file_id)
444
- expected_versions = [
445
- FileVersion(
446
- version_id="3",
447
- created_at=utc_datetime(2020, 1, 3, 1, 30),
448
- created_by=ServiceUser(
449
- id=UUID(int=16),
450
- name="x y",
451
- email="test@example.com",
452
- ),
453
- ),
454
- FileVersion(
455
- version_id="2",
456
- created_at=utc_datetime(2020, 1, 2, 1, 30),
457
- created_by=ServiceUser(
458
- id=UUID(int=17),
459
- name="x z",
460
- email="test@example.com",
461
- ),
462
- ),
463
- FileVersion(
464
- version_id="1",
465
- created_at=utc_datetime(2020, 1, 1, 1, 30),
466
- created_by=ServiceUser(
467
- id=UUID("00000000-0000-0000-0000-000000000012"),
468
- name="x w",
469
- email="test@example.com",
470
- ),
471
- ),
472
- ]
473
-
474
- self.assertEqual(versions, expected_versions)
475
- self.assert_request_made(
476
- method=RequestMethod.GET,
477
- path=f"{self.base_path}/files/{file_id}?include_versions=True",
478
- headers={"Accept": "application/json"},
479
- )
480
-
481
- async def test_prepare_upload_by_path(self) -> None:
482
- upsert_file_response = load_test_data("upsert_file.json")
483
- with self.transport.set_http_response(
484
- status_code=200, content=json.dumps(upsert_file_response), headers={"Content-Type": "application/json"}
485
- ):
486
- upload = await self.file_api_client.prepare_upload_by_path("points.csv")
487
-
488
- self.assertIsInstance(upload, FileAPIUpload)
489
- self.assertEqual(upsert_file_response["file_id"], str(upload._id))
490
- self.assertEqual(upsert_file_response["version_id"], upload._version_id)
491
- self.assert_request_made(
492
- method=RequestMethod.PUT,
493
- path=f"{self.base_path}/files/path/points.csv",
494
- headers={"Accept": "application/json"},
495
- )
496
-
497
- async def test_update_file_by_id(self) -> None:
498
- update_file_response = load_test_data("update_file.json")
499
- file_id = UUID(int=5)
500
- with self.transport.set_http_response(
501
- status_code=200, content=json.dumps(update_file_response), headers={"Content-Type": "application/json"}
502
- ):
503
- upload = await self.file_api_client.prepare_upload_by_id(file_id)
504
-
505
- self.assertIsInstance(upload, FileAPIUpload)
506
- self.assertEqual(update_file_response["file_id"], str(upload._id))
507
- self.assertEqual(update_file_response["version_id"], upload._version_id)
508
- self.assert_request_made(
509
- method=RequestMethod.PUT,
510
- path=f"{self.base_path}/files/{file_id}",
511
- headers={"Accept": "application/json"},
512
- )
513
-
514
- async def test_delete_file_by_path(self) -> None:
515
- path = "points.csv"
516
- with self.transport.set_http_response(
517
- status_code=204, content="", headers={"Content-Type": "application/json"}
518
- ):
519
- await self.file_api_client.delete_file_by_path(path)
520
- self.assert_request_made(
521
- method=RequestMethod.DELETE,
522
- path=f"{self.base_path}/files/path/{path}",
523
- )
524
-
525
- async def test_delete_file_by_id(self) -> None:
526
- file_id = UUID(int=6)
527
- with self.transport.set_http_response(
528
- status_code=204, content="", headers={"Content-Type": "application/json"}
529
- ):
530
- await self.file_api_client.delete_file_by_id(file_id)
531
- self.assert_request_made(
532
- method=RequestMethod.DELETE,
533
- path=f"{self.base_path}/files/{file_id}",
534
- )
@@ -1,152 +0,0 @@
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 json
13
- from uuid import UUID
14
-
15
- from data import load_test_data
16
- from evo.common import ServiceUser
17
- from evo.common.test_tools import TestWithConnector, TestWithDownloadHandler, TestWithUploadHandler, utc_datetime
18
- from evo.files import FileAPIDownload, FileAPIUpload, FileMetadata
19
-
20
- TEST_DATA = """
21
- x,y,z
22
- 10689.54607,100813.2874,442.172449
23
- 10690.87819,100812.6204,437.399555
24
- 10692.21062,100811.9502,432.627182
25
- 10693.54337,100811.277,427.85533
26
- 10694.87638,100810.6009,423.083966
27
- 10696.06353,100809.9976,418.831067
28
- 10725.47983,100777.2912,448.471682
29
- 10727.51038,100775.9784,444.022761
30
- 10729.54666,100774.6657,439.576449
31
- 10731.58199,100773.3566,435.128615
32
- """.lstrip().encode("utf-8")
33
-
34
- FILE_ID = UUID(int=5)
35
- VERSION_ID = "123456"
36
- INITIAL_URL = "https://unit.test/initial/url"
37
-
38
-
39
- class TestFileAPIDownload(TestWithConnector, TestWithDownloadHandler):
40
- def setUp(self) -> None:
41
- TestWithConnector.setUp(self)
42
- TestWithDownloadHandler.setUp(self)
43
- self.setup_download_handler(TEST_DATA)
44
- self.metadata = FileMetadata(
45
- environment=self.environment,
46
- id=FILE_ID,
47
- name="file.csv",
48
- created_by=ServiceUser(
49
- id=UUID(int=16),
50
- name="x y",
51
- email="test@example.com",
52
- ),
53
- created_at=utc_datetime(2020, 1, 1, 1, 30),
54
- modified_by=ServiceUser(
55
- id=UUID(int=16),
56
- name="x y",
57
- email="test@example.com",
58
- ),
59
- modified_at=utc_datetime(2020, 1, 1, 1, 30),
60
- parent="/some/path",
61
- version_id=VERSION_ID,
62
- size=len(TEST_DATA),
63
- )
64
- self.download = FileAPIDownload(connector=self.connector, metadata=self.metadata, initial_url=INITIAL_URL)
65
-
66
- def test_label(self) -> None:
67
- expected = f"{self.metadata.id}?version_id={self.metadata.version_id}"
68
- self.assertEqual(expected, self.download.label)
69
-
70
- def test_metadata(self) -> None:
71
- self.assertEqual(self.metadata, self.download.metadata)
72
-
73
- def test_get_cache_location(self) -> None:
74
- expected = self.cache.get_location(self.environment, "filev2") / str(FILE_ID) / VERSION_ID / self.metadata.name
75
- self.assertEqual(expected, self.download._get_cache_location(self.cache))
76
-
77
- async def test_get_download_url(self) -> None:
78
- # Test the initial URL is used first.
79
- first = await self.download.get_download_url()
80
- self.assertEqual(INITIAL_URL, first)
81
-
82
- # No requests should be made when using the initial URL.
83
- self.transport.assert_no_requests()
84
-
85
- # Test that a new URL is generated when the initial URL is used up.
86
- get_file_response = load_test_data("get_file.json")
87
- with self.transport.set_http_response(
88
- status_code=200, content=json.dumps(get_file_response), headers={"Content-Type": "application/json"}
89
- ):
90
- second = await self.download.get_download_url()
91
- self.assertEqual(get_file_response["download"], second)
92
-
93
- async def test_download_to_path(self) -> None:
94
- dest = self.cache.root / "test_download_to_path.csv"
95
- await self.download.download_to_path(dest, self.transport)
96
-
97
- # Check the file was downloaded correctly.
98
- self.assertEqual(TEST_DATA, dest.read_bytes())
99
- self.assert_download_requests(INITIAL_URL)
100
-
101
- async def test_download_to_cache(self) -> None:
102
- dest = await self.download.download_to_cache(self.cache, self.transport)
103
-
104
- # Check the download location is correct.
105
- expected = self.cache.get_location(self.environment, "filev2") / str(FILE_ID) / VERSION_ID / self.metadata.name
106
- self.assertEqual(expected, dest)
107
-
108
- # Check the file was downloaded correctly.
109
- self.assertEqual(TEST_DATA, dest.read_bytes())
110
- self.assert_download_requests(INITIAL_URL)
111
-
112
-
113
- class TestFileAPIUpload(TestWithConnector, TestWithUploadHandler):
114
- def setUp(self) -> None:
115
- TestWithConnector.setUp(self)
116
- TestWithUploadHandler.setUp(self)
117
- self.upload = FileAPIUpload(
118
- connector=self.connector,
119
- environment=self.environment,
120
- file_id=FILE_ID,
121
- version_id=VERSION_ID,
122
- initial_url=INITIAL_URL,
123
- )
124
-
125
- def test_label(self) -> None:
126
- expected = f"{FILE_ID}?version_id={VERSION_ID}"
127
- self.assertEqual(expected, self.upload.label)
128
-
129
- async def test_get_upload_url(self) -> None:
130
- # Test the initial URL is used first.
131
- first = await self.upload.get_upload_url()
132
- self.assertEqual(INITIAL_URL, first)
133
-
134
- # No requests should be made when using the initial URL.
135
- self.transport.assert_no_requests()
136
-
137
- # Test that a new URL is generated when the initial URL is used up.
138
- update_file_response = load_test_data("update_file.json")
139
- with self.transport.set_http_response(
140
- status_code=200, content=json.dumps(update_file_response), headers={"Content-Type": "application/json"}
141
- ):
142
- second = await self.upload.get_upload_url()
143
- self.assertEqual(update_file_response["upload"], second)
144
-
145
- async def test_upload_from_path(self) -> None:
146
- source = self.cache.root / "test_upload_from_path.csv"
147
- source.write_bytes(TEST_DATA)
148
- await self.upload.upload_from_path(source, self.transport)
149
-
150
- # Check the file was uploaded correctly.
151
- uploaded = await self.handler.get_committed()
152
- self.assertEqual(TEST_DATA, uploaded)
@@ -1,54 +0,0 @@
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 unittest
13
-
14
- from evo.files.io import _INVALID_NAMES, _make_name_safe
15
-
16
-
17
- class TestMakeNameSafe(unittest.TestCase):
18
- def test_invalid_characters(self):
19
- self.assertEqual(_make_name_safe("test<name>"), "test%3Cname%3E")
20
- self.assertEqual(_make_name_safe("test:name"), "test%3Aname")
21
- self.assertEqual(_make_name_safe("test/name"), "test%2Fname")
22
- self.assertEqual(_make_name_safe("test\\name"), "test%5Cname")
23
- self.assertEqual(_make_name_safe("test|name"), "test%7Cname")
24
- self.assertEqual(_make_name_safe("test?name"), "test%3Fname")
25
- self.assertEqual(_make_name_safe("test*name"), "test%2Aname")
26
-
27
- def test_invalid_trailing_characters(self):
28
- self.assertEqual(_make_name_safe("test "), "test%20")
29
- self.assertEqual(_make_name_safe("test."), "test%2E")
30
-
31
- def test_reserved_names(self):
32
- for name in _INVALID_NAMES:
33
- self.assertEqual(_make_name_safe(name), name + "_")
34
- self.assertEqual(_make_name_safe(name + ".txt"), name + "_.txt")
35
-
36
- def test_valid_names(self):
37
- self.assertEqual(_make_name_safe("test"), "test")
38
- self.assertEqual(_make_name_safe("test123"), "test123")
39
- self.assertEqual(_make_name_safe("test_name"), "test_name")
40
-
41
- def test_leading_trailing_special_characters(self):
42
- self.assertEqual(_make_name_safe("<test>"), "%3Ctest%3E")
43
-
44
- def test_leading_trailing_reserved_names(self):
45
- self.assertEqual(_make_name_safe("CONtestCON"), "CONtestCON")
46
-
47
- def test_mixed_valid_invalid_characters(self):
48
- self.assertEqual(_make_name_safe("te<st>na:me"), "te%3Cst%3Ena%3Ame")
49
-
50
- def test_empty_string(self):
51
- self.assertEqual(_make_name_safe(""), "")
52
-
53
- def test_only_special_characters(self):
54
- self.assertEqual(_make_name_safe('<>:"/\\|?*'), "%3C%3E%3A%22%2F%5C%7C%3F%2A")
File without changes