Skip to content

Commit f122bc6

Browse files
committed
refactored
1 parent 954528d commit f122bc6

File tree

9 files changed

+371
-38
lines changed

9 files changed

+371
-38
lines changed
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import logging
22

33
import typer
4-
from settings_library.utils_cli import create_settings_command
4+
from settings_library.utils_cli import create_settings_command, create_version_callback
55

6-
from ._meta import PROJECT_NAME
6+
from ._meta import PROJECT_NAME, __version__
77
from .core.settings import ApplicationSettings
88

99
log = logging.getLogger(__name__)
@@ -12,13 +12,14 @@
1212
main = typer.Typer(name=PROJECT_NAME)
1313

1414
main.command()(create_settings_command(settings_cls=ApplicationSettings, logger=log))
15+
main.callback()(create_version_callback(__version__))
1516

1617

1718
@main.command()
18-
def run():
19+
def run() -> None:
1920
"""Runs application"""
2021
typer.secho("Sorry, this entrypoint is intentionally disabled. Use instead")
2122
typer.secho(
22-
"$ uvicorn simcore_service_datcore_adapter.main:the_app",
23+
f"$ uvicorn {PROJECT_NAME}.main:the_app",
2324
fg=typer.colors.BLUE,
2425
)
Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,22 @@
1-
"""Main application to be deployed in for example uvicorn
2-
"""
1+
"""Main application to be deployed in for example uvicorn"""
2+
3+
import logging
4+
35
from fastapi import FastAPI
6+
from servicelib.logging_utils import config_all_loggers
47
from simcore_service_datcore_adapter.core.application import create_app
8+
from simcore_service_datcore_adapter.core.settings import ApplicationSettings
9+
10+
_the_settings = ApplicationSettings.create_from_envs()
11+
12+
# SEE https://github.com/ITISFoundation/osparc-simcore/issues/3148
13+
logging.basicConfig(level=_the_settings.log_level) # NOSONAR
14+
logging.root.setLevel(_the_settings.log_level)
15+
config_all_loggers(
16+
log_format_local_dev_enabled=_the_settings.DATCORE_ADAPTER_LOG_FORMAT_LOCAL_DEV_ENABLED,
17+
logger_filter_mapping=_the_settings.DATCORE_ADAPTER_LOG_FILTER_MAPPING,
18+
tracing_settings=_the_settings.DATCORE_ADAPTER_TRACING,
19+
)
520

621
# SINGLETON FastAPI app
7-
the_app: FastAPI = create_app()
22+
the_app: FastAPI = create_app(_the_settings)

services/datcore-adapter/src/simcore_service_datcore_adapter/modules/pennsieve.py

Lines changed: 101 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
import boto3
1010
from aiocache import SimpleMemoryCache # type: ignore[import-untyped]
1111
from fastapi.applications import FastAPI
12+
from models_library.api_schemas_datcore_adapter.datasets import (
13+
DatasetMetaData,
14+
DataType,
15+
FileMetaData,
16+
)
1217
from servicelib.logging_utils import log_context
1318
from servicelib.utils import logged_gather
1419
from starlette import status
@@ -19,8 +24,8 @@
1924
from tenacity.stop import stop_after_attempt
2025

2126
from ..core.settings import PennsieveSettings
22-
from ..models.domains.user import Profile
23-
from ..models.schemas.datasets import DatasetMetaData, FileMetaData
27+
from ..models.files import DatCorePackageMetaData
28+
from ..models.user import Profile
2429
from ..utils.client_base import BaseServiceClientApi, setup_client_instance
2530

2631
logger = logging.getLogger(__name__)
@@ -29,6 +34,36 @@
2934
_GATHER_MAX_CONCURRENCY = 10
3035

3136

37+
def _to_file_meta_data(
38+
package: dict[str, Any], files: list[DatCorePackageMetaData], base_path: Path
39+
) -> FileMetaData:
40+
"""creates a FileMetaData from a pennsieve data structure."""
41+
pck_name: str = package["content"]["name"]
42+
if "extension" in package and not pck_name.endswith(package["extension"]):
43+
pck_name += ".".join((pck_name, package["extension"]))
44+
45+
file_size = 0
46+
if package["content"]["packageType"] != "Collection" and files:
47+
file_size = files[0].size
48+
49+
return FileMetaData(
50+
dataset_id=package["content"]["datasetNodeId"],
51+
package_id=package["content"]["nodeId"],
52+
id=f"{package['content']['id']}",
53+
name=pck_name,
54+
path=base_path / pck_name,
55+
type=package["content"]["packageType"],
56+
size=file_size,
57+
created_at=package["content"]["createdAt"],
58+
last_modified_at=package["content"]["updatedAt"],
59+
data_type=(
60+
DataType.FOLDER
61+
if package["content"]["packageType"] == "Collection"
62+
else DataType.FILE
63+
),
64+
)
65+
66+
3267
def _compute_file_path(
3368
all_packages: dict[str, dict[str, Any]], pck: dict[str, Any]
3469
) -> Path:
@@ -215,27 +250,66 @@ async def _get_package(
215250
)
216251

217252
async def get_package_files(
218-
self, api_key: str, api_secret: str, package_id: str, limit: int, offset: int
219-
) -> list[dict[str, Any]]:
220-
return cast(
221-
list[dict[str, Any]],
222-
await self._request(
223-
api_key,
224-
api_secret,
225-
"GET",
226-
f"/packages/{package_id}/files",
227-
params={"limit": limit, "offset": offset},
228-
),
253+
self,
254+
*,
255+
api_key: str,
256+
api_secret: str,
257+
package_id: str,
258+
limit: int,
259+
offset: int,
260+
fill_path: bool,
261+
) -> list[DatCorePackageMetaData]:
262+
raw_data = await self._request(
263+
api_key,
264+
api_secret,
265+
"GET",
266+
f"/packages/{package_id}/files",
267+
params={"limit": limit, "offset": offset},
229268
)
269+
path = display_path = Path()
270+
if fill_path:
271+
package_info = await self._get_package(api_key, api_secret, package_id)
272+
dataset_id = package_info["content"]["datasetId"]
273+
dataset = await self._get_dataset(api_key, api_secret, dataset_id)
274+
275+
path = (
276+
Path(dataset_id)
277+
/ Path(
278+
"/".join(
279+
ancestor["content"]["id"]
280+
for ancestor in package_info.get("ancestors", [])
281+
)
282+
)
283+
/ Path(package_info["content"]["name"])
284+
)
285+
display_path = (
286+
Path(dataset["content"]["name"])
287+
/ Path(
288+
"/".join(
289+
ancestor["content"]["name"]
290+
for ancestor in package_info.get("ancestors", [])
291+
)
292+
)
293+
/ Path(package_info["content"]["name"])
294+
)
295+
296+
return [
297+
DatCorePackageMetaData(**_["content"], path=path, display_path=display_path)
298+
for _ in raw_data
299+
]
230300

231301
async def _get_pck_id_files(
232302
self, api_key: str, api_secret: str, pck_id: str, pck: dict[str, Any]
233-
) -> tuple[str, list[dict[str, Any]]]:
234-
303+
) -> tuple[str, list[DatCorePackageMetaData]]:
235304
return (
236305
pck_id,
237306
await self.get_package_files(
238-
api_key, api_secret, pck["content"]["nodeId"], limit=1, offset=0
307+
api_key=api_key,
308+
api_secret=api_secret,
309+
package_id=pck["content"]["nodeId"],
310+
limit=1,
311+
offset=0,
312+
fill_path=False,
239313
),
240314
)
241315

@@ -293,7 +367,7 @@ async def list_packages_in_dataset(
293367
for pck in islice(dataset_pck["children"], offset, offset + limit)
294368
if pck["content"]["packageType"] != "Collection"
295369
]
296-
package_files = dict(
370+
package_files: dict[str, list[DatCorePackageMetaData]] = dict(
297371
await logged_gather(
298372
*package_files_tasks,
299373
log=logger,
@@ -302,7 +376,7 @@ async def list_packages_in_dataset(
302376
)
303377
return (
304378
[
305-
FileMetaData.from_pennsieve_package(
379+
_to_file_meta_data(
306380
pck,
307381
(
308382
package_files[pck["content"]["id"]]
@@ -353,7 +427,7 @@ async def list_packages_in_collection(
353427

354428
return (
355429
[
356-
FileMetaData.from_pennsieve_package(
430+
_to_file_meta_data(
357431
pck,
358432
(
359433
package_files[pck["content"]["id"]]
@@ -433,7 +507,7 @@ async def list_all_dataset_files(
433507
file_path = base_path / _compute_file_path(all_packages, package)
434508

435509
file_meta_data.append(
436-
FileMetaData.from_pennsieve_package(
510+
_to_file_meta_data(
437511
package, package_files[package_id], file_path.parent
438512
)
439513
)
@@ -445,11 +519,16 @@ async def get_presigned_download_link(
445519
) -> URL:
446520
"""returns the presigned download link of the first file in the package"""
447521
files = await self.get_package_files(
448-
api_key, api_secret, package_id, limit=1, offset=0
522+
api_key=api_key,
523+
api_secret=api_secret,
524+
package_id=package_id,
525+
limit=1,
526+
offset=0,
527+
fill_path=False,
449528
)
450529
# NOTE: this was done like this in the original dsm. we might encounter a problem when there are more than one files
451530
assert len(files) == 1 # nosec
452-
file_id = files[0]["content"]["id"]
531+
file_id = files[0].id
453532
file_link = cast(
454533
dict[str, Any],
455534
await self._request(

services/datcore-adapter/tests/unit/conftest.py

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import simcore_service_datcore_adapter
1616
from asgi_lifespan import LifespanManager
1717
from fastapi.applications import FastAPI
18+
from models_library.utils.fastapi_encoders import jsonable_encoder
1819
from pytest_mock import MockFixture
1920
from pytest_simcore.helpers.monkeypatch_envs import EnvVarsDict, setenvs_from_dict
2021
from simcore_service_datcore_adapter.modules.pennsieve import (
@@ -24,6 +25,7 @@
2425
from starlette.testclient import TestClient
2526

2627
pytest_plugins = [
28+
"pytest_simcore.cli_runner",
2729
"pytest_simcore.environment_configs",
2830
"pytest_simcore.repository_paths",
2931
"pytest_simcore.pytest_global_environs",
@@ -317,15 +319,40 @@ async def pennsieve_subsystem_mock(
317319

318320
# get collection packages
319321
mock.get(
320-
f"https://api.pennsieve.io/packages/{pennsieve_collection_id}"
322+
rf"https://api.pennsieve.io/packages/{pennsieve_collection_id}"
321323
).respond(
322324
status.HTTP_200_OK,
323325
json={
324326
"content": {"name": "this package name is also awesome"},
325327
"children": pennsieve_mock_dataset_packages["packages"],
326328
"ancestors": [
327-
{"content": {"name": "Bigger guy"}},
328-
{"content": {"name": "Big guy"}},
329+
{
330+
"content": {
331+
"name": "Bigger guy",
332+
}
333+
},
334+
{
335+
"content": {
336+
"name": "Big guy",
337+
}
338+
},
339+
],
340+
},
341+
)
342+
# get package ancestry
343+
mock.get(
344+
url__regex=rf"https://api.pennsieve.io/packages/{pennsieve_file_id}\?includeAncestors=(?P<include>.+)$"
345+
).respond(
346+
status.HTTP_200_OK,
347+
json={
348+
"content": {
349+
"datasetId": pennsieve_dataset_id,
350+
"name": pennsieve_file_id,
351+
},
352+
"ancestors": [
353+
{"content": {"id": faker.pystr(), "name": faker.name()}},
354+
{"content": {"id": faker.pystr(), "name": faker.name()}},
355+
{"content": {"id": faker.pystr(), "name": faker.name()}},
329356
],
330357
},
331358
)
@@ -334,7 +361,22 @@ async def pennsieve_subsystem_mock(
334361
url__regex=r"https://api.pennsieve.io/packages/.+/files\?limit=1&offset=0$"
335362
).respond(
336363
status.HTTP_200_OK,
337-
json=[{"content": {"size": 12345, "id": "fake_file_id"}}],
364+
json=[
365+
jsonable_encoder(
366+
{
367+
"content": {
368+
"size": 12345,
369+
"id": faker.pyint(),
370+
"packageId": "N:package:475beff2-03c8-4dca-a221-d1d02e17f064",
371+
"name": faker.file_name(),
372+
"filename": faker.file_name(),
373+
"s3bucket": faker.pystr(),
374+
"createdAt": faker.date_time(),
375+
"updatedAt": faker.date_time(),
376+
}
377+
}
378+
)
379+
],
338380
)
339381

340382
# download file
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# pylint:disable=unused-variable
2+
# pylint:disable=unused-argument
3+
# pylint:disable=redefined-outer-name
4+
5+
import os
6+
7+
from pytest_simcore.helpers.typing_env import EnvVarsDict
8+
from simcore_service_datcore_adapter._meta import API_VERSION
9+
from simcore_service_datcore_adapter.cli import main
10+
from simcore_service_datcore_adapter.core.settings import ApplicationSettings
11+
from typer.testing import CliRunner
12+
13+
14+
def test_cli_help_and_version(cli_runner: CliRunner):
15+
result = cli_runner.invoke(main, "--help")
16+
assert result.exit_code == os.EX_OK, result.output
17+
18+
result = cli_runner.invoke(main, "--version")
19+
assert result.exit_code == os.EX_OK, result.output
20+
assert result.stdout.strip() == API_VERSION
21+
22+
23+
def test_settings(cli_runner: CliRunner, app_environment: EnvVarsDict):
24+
result = cli_runner.invoke(main, ["settings", "--show-secrets", "--as-json"])
25+
assert result.exit_code == os.EX_OK
26+
27+
print(result.output)
28+
settings = ApplicationSettings(result.output)
29+
assert settings.model_dump() == ApplicationSettings.create_from_envs().model_dump()
30+
31+
32+
def test_run(cli_runner: CliRunner):
33+
result = cli_runner.invoke(main, ["run"])
34+
assert result.exit_code == 0
35+
assert "disabled" in result.stdout

0 commit comments

Comments
 (0)