• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SwissDataScienceCenter / renku-data-services / 18715077147

22 Oct 2025 11:45AM UTC coverage: 86.653% (-0.1%) from 86.802%
18715077147

Pull #945

github

web-flow
Merge 93c973951 into 5cc2b39de
Pull Request #945: feat: add support for OpenBIS datasets

70 of 139 new or added lines in 12 files covered. (50.36%)

4 existing lines in 3 files now uncovered.

22781 of 26290 relevant lines covered (86.65%)

1.52 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.11
/components/renku_data_services/data_connectors/blueprints.py
1
"""Data connectors blueprint."""
2

3
from dataclasses import dataclass
2✔
4
from datetime import datetime
2✔
5
from typing import Any
2✔
6

7
from sanic import Request
2✔
8
from sanic.response import HTTPResponse, JSONResponse
2✔
9
from sanic_ext import validate
2✔
10
from ulid import ULID
2✔
11

12
from renku_data_services import base_models, errors
2✔
13
from renku_data_services.base_api.auth import (
2✔
14
    authenticate,
15
    only_authenticated,
16
)
17
from renku_data_services.base_api.blueprint import BlueprintFactoryResponse, CustomBlueprint
2✔
18
from renku_data_services.base_api.etag import extract_if_none_match, if_match_required
2✔
19
from renku_data_services.base_api.misc import validate_body_root_model, validate_query
2✔
20
from renku_data_services.base_api.pagination import PaginationRequest, paginate
2✔
21
from renku_data_services.base_models.core import (
2✔
22
    DataConnectorInProjectPath,
23
    DataConnectorPath,
24
    NamespacePath,
25
    ProjectPath,
26
    Slug,
27
)
28
from renku_data_services.base_models.metrics import MetricsService
2✔
29
from renku_data_services.base_models.validation import validate_and_dump, validated_json
2✔
30
from renku_data_services.data_connectors import apispec, models
2✔
31
from renku_data_services.data_connectors.core import (
2✔
32
    dump_storage_with_sensitive_fields,
33
    prevalidate_unsaved_global_data_connector,
34
    validate_data_connector_patch,
35
    validate_data_connector_secrets_patch,
36
    validate_unsaved_data_connector,
37
)
38
from renku_data_services.data_connectors.db import (
2✔
39
    DataConnectorRepository,
40
    DataConnectorSecretRepository,
41
)
42
from renku_data_services.storage.rclone import RCloneValidator
2✔
43
from renku_data_services.utils.core import get_openbis_pat
2✔
44

45

46
@dataclass(kw_only=True)
2✔
47
class DataConnectorsBP(CustomBlueprint):
2✔
48
    """Handlers for manipulating data connectors."""
49

50
    data_connector_repo: DataConnectorRepository
2✔
51
    data_connector_secret_repo: DataConnectorSecretRepository
2✔
52
    authenticator: base_models.Authenticator
2✔
53
    metrics: MetricsService
2✔
54

55
    def get_all(self) -> BlueprintFactoryResponse:
2✔
56
        """List data connectors."""
57

58
        @authenticate(self.authenticator)
2✔
59
        @validate_query(query=apispec.DataConnectorsGetQuery)
2✔
60
        @paginate
2✔
61
        async def _get_all(
2✔
62
            _: Request,
63
            user: base_models.APIUser,
64
            pagination: PaginationRequest,
65
            query: apispec.DataConnectorsGetQuery,
66
            validator: RCloneValidator,
67
        ) -> tuple[list[dict[str, Any]], int]:
68
            ns_segments = query.namespace.split("/")
2✔
69
            ns: None | NamespacePath | ProjectPath
70
            if len(ns_segments) == 0 or (len(ns_segments) == 1 and len(ns_segments[0]) == 0):
2✔
71
                ns = None
2✔
72
            elif len(ns_segments) == 1 and len(ns_segments[0]) > 0:
×
73
                ns = NamespacePath.from_strings(*ns_segments)
×
74
            elif len(ns_segments) == 2:
×
75
                ns = ProjectPath.from_strings(*ns_segments)
×
76
            else:
77
                raise errors.ValidationError(
×
78
                    message="Got an unexpected number of path segments for the data connector namespace"
79
                    " in the request query parameter, expected 0, 1 or 2"
80
                )
81
            data_connectors, total_num = await self.data_connector_repo.get_data_connectors(
2✔
82
                user=user, pagination=pagination, namespace=ns
83
            )
84
            return [
2✔
85
                validate_and_dump(
86
                    apispec.DataConnector,
87
                    self._dump_data_connector(dc, validator=validator),
88
                )
89
                for dc in data_connectors
90
            ], total_num
91

92
        return "/data_connectors", ["GET"], _get_all
2✔
93

94
    def post(self) -> BlueprintFactoryResponse:
2✔
95
        """Create a new data connector."""
96

97
        @authenticate(self.authenticator)
2✔
98
        @only_authenticated
2✔
99
        @validate(json=apispec.DataConnectorPost)
2✔
100
        async def _post(
2✔
101
            _: Request, user: base_models.APIUser, body: apispec.DataConnectorPost, validator: RCloneValidator
102
        ) -> JSONResponse:
103
            data_connector = validate_unsaved_data_connector(body, validator=validator)
2✔
104
            result = await self.data_connector_repo.insert_namespaced_data_connector(
1✔
105
                user=user, data_connector=data_connector
106
            )
107
            await self.metrics.data_connector_created(user)
1✔
108
            return validated_json(
1✔
109
                apispec.DataConnector,
110
                self._dump_data_connector(result, validator=validator),
111
                status=201,
112
            )
113

114
        return "/data_connectors", ["POST"], _post
2✔
115

116
    def post_global(self) -> BlueprintFactoryResponse:
2✔
117
        """Create a new global data connector."""
118

119
        @authenticate(self.authenticator)
2✔
120
        @only_authenticated
2✔
121
        @validate(json=apispec.GlobalDataConnectorPost)
2✔
122
        async def _post_global(
2✔
123
            _: Request, user: base_models.APIUser, body: apispec.GlobalDataConnectorPost, validator: RCloneValidator
124
        ) -> JSONResponse:
125
            data_connector = await prevalidate_unsaved_global_data_connector(body, validator=validator)
2✔
126
            result, inserted = await self.data_connector_repo.insert_global_data_connector(
1✔
127
                user=user, data_connector=data_connector, validator=validator
128
            )
129
            return validated_json(
1✔
130
                apispec.DataConnector,
131
                self._dump_data_connector(result, validator=validator),
132
                status=201 if inserted else 200,
133
            )
134

135
        return "/data_connectors/global", ["POST"], _post_global
2✔
136

137
    def get_one(self) -> BlueprintFactoryResponse:
2✔
138
        """Get a specific data connector."""
139

140
        @authenticate(self.authenticator)
2✔
141
        @extract_if_none_match
2✔
142
        async def _get_one(
2✔
143
            _: Request, user: base_models.APIUser, data_connector_id: ULID, etag: str | None, validator: RCloneValidator
144
        ) -> HTTPResponse:
145
            data_connector = await self.data_connector_repo.get_data_connector(
2✔
146
                user=user, data_connector_id=data_connector_id
147
            )
148

149
            if data_connector.etag == etag:
1✔
150
                return HTTPResponse(status=304)
×
151

152
            headers = {"ETag": data_connector.etag}
1✔
153
            return validated_json(
1✔
154
                apispec.DataConnector,
155
                self._dump_data_connector(data_connector, validator=validator),
156
                headers=headers,
157
            )
158

159
        return "/data_connectors/<data_connector_id:ulid>", ["GET"], _get_one
2✔
160

161
    def get_one_global_by_slug(self) -> BlueprintFactoryResponse:
2✔
162
        """Get a specific global data connector by slug."""
163

164
        @authenticate(self.authenticator)
2✔
165
        @extract_if_none_match
2✔
166
        async def _get_one_global_by_slug(
2✔
167
            _: Request,
168
            user: base_models.APIUser,
169
            slug: Slug,
170
            etag: str | None,
171
            validator: RCloneValidator,
172
        ) -> HTTPResponse:
173
            data_connector = await self.data_connector_repo.get_global_data_connector_by_slug(user=user, slug=slug)
2✔
174

175
            if data_connector.etag == etag:
1✔
176
                return HTTPResponse(status=304)
×
177

178
            headers = {"ETag": data_connector.etag}
1✔
179
            return validated_json(
1✔
180
                apispec.DataConnector,
181
                self._dump_data_connector(data_connector, validator=validator),
182
                headers=headers,
183
            )
184

185
        return "/data_connectors/global/<slug:renku_slug>", ["GET"], _get_one_global_by_slug
2✔
186

187
    def get_one_by_slug(self) -> BlueprintFactoryResponse:
2✔
188
        """Get a specific data connector by namespace/entity slug."""
189

190
        @authenticate(self.authenticator)
2✔
191
        @extract_if_none_match
2✔
192
        async def _get_one_by_slug(
2✔
193
            _: Request,
194
            user: base_models.APIUser,
195
            namespace: str,
196
            slug: Slug,
197
            etag: str | None,
198
            validator: RCloneValidator,
199
        ) -> HTTPResponse:
200
            data_connector = await self.data_connector_repo.get_data_connector_by_slug(
2✔
201
                user=user,
202
                path=DataConnectorPath.from_strings(namespace, slug.value),
203
            )
204

205
            if data_connector.etag == etag:
1✔
206
                return HTTPResponse(status=304)
×
207

208
            headers = {"ETag": data_connector.etag}
1✔
209
            return validated_json(
1✔
210
                apispec.DataConnector,
211
                self._dump_data_connector(data_connector, validator=validator),
212
                headers=headers,
213
            )
214

215
        return "/namespaces/<namespace>/data_connectors/<slug:renku_slug>", ["GET"], _get_one_by_slug
2✔
216

217
    def get_one_by_slug_from_project_namespace(self) -> BlueprintFactoryResponse:
2✔
218
        """Get a specific data connector by namespace/project_slug/dc_slug slug."""
219

220
        @authenticate(self.authenticator)
2✔
221
        @extract_if_none_match
2✔
222
        async def _get_one_from_project_namespace(
2✔
223
            _: Request,
224
            user: base_models.APIUser,
225
            ns_slug: Slug,
226
            project_slug: Slug,
227
            dc_slug: Slug,
228
            etag: str | None,
229
            validator: RCloneValidator,
230
        ) -> HTTPResponse:
231
            dc_path = DataConnectorInProjectPath.from_strings(
1✔
232
                ns_slug.value,
233
                project_slug.value,
234
                dc_slug.value,
235
            )
236
            data_connector = await self.data_connector_repo.get_data_connector_by_slug(user=user, path=dc_path)
1✔
237

238
            if data_connector.etag == etag:
×
239
                return HTTPResponse(status=304)
×
240

241
            headers = {"ETag": data_connector.etag}
×
242
            return validated_json(
×
243
                apispec.DataConnector,
244
                self._dump_data_connector(data_connector, validator=validator),
245
                headers=headers,
246
            )
247

248
        return (
2✔
249
            "/namespaces/<ns_slug:renku_slug>/projects/<project_slug:renku_slug>/data_connectors/<dc_slug:renku_slug>",
250
            ["GET"],
251
            _get_one_from_project_namespace,
252
        )
253

254
    def patch(self) -> BlueprintFactoryResponse:
2✔
255
        """Partially update a data connector."""
256

257
        @authenticate(self.authenticator)
2✔
258
        @only_authenticated
2✔
259
        @if_match_required
2✔
260
        @validate(json=apispec.DataConnectorPatch)
2✔
261
        async def _patch(
2✔
262
            _: Request,
263
            user: base_models.APIUser,
264
            data_connector_id: ULID,
265
            body: apispec.DataConnectorPatch,
266
            etag: str,
267
            validator: RCloneValidator,
268
        ) -> JSONResponse:
269
            existing_dc = await self.data_connector_repo.get_data_connector(
2✔
270
                user=user, data_connector_id=data_connector_id
271
            )
272
            dc_patch = validate_data_connector_patch(existing_dc, body, validator=validator)
1✔
273
            data_connector_update = await self.data_connector_repo.update_data_connector(
1✔
274
                user=user, data_connector_id=data_connector_id, patch=dc_patch, etag=etag
275
            )
276

277
            return validated_json(
1✔
278
                apispec.DataConnector,
279
                self._dump_data_connector(data_connector_update.new, validator=validator),
280
            )
281

282
        return "/data_connectors/<data_connector_id:ulid>", ["PATCH"], _patch
2✔
283

284
    def delete(self) -> BlueprintFactoryResponse:
2✔
285
        """Delete a data connector."""
286

287
        @authenticate(self.authenticator)
2✔
288
        @only_authenticated
2✔
289
        async def _delete(
2✔
290
            _: Request,
291
            user: base_models.APIUser,
292
            data_connector_id: ULID,
293
        ) -> HTTPResponse:
294
            await self.data_connector_repo.delete_data_connector(user=user, data_connector_id=data_connector_id)
2✔
295
            return HTTPResponse(status=204)
1✔
296

297
        return "/data_connectors/<data_connector_id:ulid>", ["DELETE"], _delete
2✔
298

299
    def get_permissions(self) -> BlueprintFactoryResponse:
2✔
300
        """Get the permissions of the current user on the data connector."""
301

302
        @authenticate(self.authenticator)
2✔
303
        async def _get_permissions(_: Request, user: base_models.APIUser, data_connector_id: ULID) -> JSONResponse:
2✔
304
            permissions = await self.data_connector_repo.get_data_connector_permissions(
2✔
305
                user=user, data_connector_id=data_connector_id
306
            )
307
            return validated_json(apispec.DataConnectorPermissions, permissions)
1✔
308

309
        return "/data_connectors/<data_connector_id:ulid>/permissions", ["GET"], _get_permissions
2✔
310

311
    def get_all_project_links(self) -> BlueprintFactoryResponse:
2✔
312
        """List all links from a given data connector to projects."""
313

314
        @authenticate(self.authenticator)
2✔
315
        async def _get_all_project_links(
2✔
316
            _: Request,
317
            user: base_models.APIUser,
318
            data_connector_id: ULID,
319
        ) -> JSONResponse:
320
            links = await self.data_connector_repo.get_links_from(user=user, data_connector_id=data_connector_id)
2✔
321
            return validated_json(
1✔
322
                apispec.DataConnectorToProjectLinksList,
323
                [self._dump_data_connector_to_project_link(link) for link in links],
324
            )
325

326
        return "/data_connectors/<data_connector_id:ulid>/project_links", ["GET"], _get_all_project_links
2✔
327

328
    def post_project_link(self) -> BlueprintFactoryResponse:
2✔
329
        """Create a new link from a data connector to a project."""
330

331
        @authenticate(self.authenticator)
2✔
332
        @only_authenticated
2✔
333
        @validate(json=apispec.DataConnectorToProjectLinkPost)
2✔
334
        async def _post_project_link(
2✔
335
            _: Request,
336
            user: base_models.APIUser,
337
            data_connector_id: ULID,
338
            body: apispec.DataConnectorToProjectLinkPost,
339
        ) -> JSONResponse:
340
            unsaved_link = models.UnsavedDataConnectorToProjectLink(
2✔
341
                data_connector_id=data_connector_id,
342
                project_id=ULID.from_str(body.project_id),
343
            )
344
            link = await self.data_connector_repo.insert_link(user=user, link=unsaved_link)
2✔
345
            await self.metrics.data_connector_linked(user)
1✔
346
            return validated_json(
1✔
347
                apispec.DataConnectorToProjectLink, self._dump_data_connector_to_project_link(link), status=201
348
            )
349

350
        return "/data_connectors/<data_connector_id:ulid>/project_links", ["POST"], _post_project_link
2✔
351

352
    def delete_project_link(self) -> BlueprintFactoryResponse:
2✔
353
        """Delete a link from a data connector to a project."""
354

355
        @authenticate(self.authenticator)
2✔
356
        @only_authenticated
2✔
357
        async def _delete_project_link(
2✔
358
            _: Request,
359
            user: base_models.APIUser,
360
            data_connector_id: ULID,
361
            link_id: ULID,
362
        ) -> HTTPResponse:
363
            await self.data_connector_repo.delete_link(user=user, data_connector_id=data_connector_id, link_id=link_id)
2✔
364
            return HTTPResponse(status=204)
2✔
365

366
        return (
2✔
367
            "/data_connectors/<data_connector_id:ulid>/project_links/<link_id:ulid>",
368
            ["DELETE"],
369
            _delete_project_link,
370
        )
371

372
    def get_all_data_connectors_links_to_project(self) -> BlueprintFactoryResponse:
2✔
373
        """List all links from data connectors to a given project."""
374

375
        @authenticate(self.authenticator)
2✔
376
        async def _get_all_data_connectors_links_to_project(
2✔
377
            _: Request,
378
            user: base_models.APIUser,
379
            project_id: ULID,
380
        ) -> JSONResponse:
381
            links = await self.data_connector_repo.get_links_to(user=user, project_id=project_id)
2✔
382
            return validated_json(
1✔
383
                apispec.DataConnectorToProjectLinksList,
384
                [self._dump_data_connector_to_project_link(link) for link in links],
385
            )
386

387
        return "/projects/<project_id:ulid>/data_connector_links", ["GET"], _get_all_data_connectors_links_to_project
2✔
388

389
    def get_inaccessible_data_connectors_links_to_project(self) -> BlueprintFactoryResponse:
2✔
390
        """The number of data connector links in a given project the user has no access to."""
391

392
        @authenticate(self.authenticator)
2✔
393
        async def _get_inaccessible_data_connectors_links_to_project(
2✔
394
            _: Request,
395
            user: base_models.APIUser,
396
            project_id: ULID,
397
        ) -> JSONResponse:
398
            link_ids = await self.data_connector_repo.get_inaccessible_links_to_project(
2✔
399
                user=user, project_id=project_id
400
            )
401
            return validated_json(apispec.InaccessibleDataConnectorLinks, {"count": len(link_ids)})
1✔
402

403
        return (
2✔
404
            "/projects/<project_id:ulid>/inaccessible_data_connector_links",
405
            ["GET"],
406
            _get_inaccessible_data_connectors_links_to_project,
407
        )
408

409
    def get_secrets(self) -> BlueprintFactoryResponse:
2✔
410
        """List all saved secrets for a data connector."""
411

412
        @authenticate(self.authenticator)
2✔
413
        @only_authenticated
2✔
414
        async def _get_secrets(
2✔
415
            _: Request,
416
            user: base_models.APIUser,
417
            data_connector_id: ULID,
418
        ) -> JSONResponse:
419
            secrets = await self.data_connector_secret_repo.get_data_connector_secrets(
2✔
420
                user=user, data_connector_id=data_connector_id
421
            )
422
            data_connector = await self.data_connector_repo.get_data_connector(
2✔
423
                user=user, data_connector_id=data_connector_id
424
            )
425
            return validated_json(
1✔
426
                apispec.DataConnectorSecretsList,
427
                [
428
                    self._dump_data_connector_secret(secret)
429
                    for secret in self._adjust_secrets(secrets, data_connector.storage)
430
                ],
431
            )
432

433
        return "/data_connectors/<data_connector_id:ulid>/secrets", ["GET"], _get_secrets
2✔
434

435
    def patch_secrets(self) -> BlueprintFactoryResponse:
2✔
436
        """Create, update or delete saved secrets for a data connector."""
437

438
        @authenticate(self.authenticator)
2✔
439
        @only_authenticated
2✔
440
        @validate_body_root_model(json=apispec.DataConnectorSecretPatchList)
2✔
441
        async def _patch_secrets(
2✔
442
            _: Request,
443
            user: base_models.APIUser,
444
            data_connector_id: ULID,
445
            body: apispec.DataConnectorSecretPatchList,
446
            validator: RCloneValidator,
447
        ) -> JSONResponse:
448
            unsaved_secrets = validate_data_connector_secrets_patch(put=body)
2✔
449
            data_connector = await self.data_connector_repo.get_data_connector(
2✔
450
                user=user, data_connector_id=data_connector_id
451
            )
452
            storage = data_connector.storage
1✔
453
            provider = validator.providers[storage.storage_type]
1✔
454
            sensitive_lookup = [o.name for o in provider.options if o.sensitive]
1✔
455
            for secret in unsaved_secrets:
1✔
456
                if secret.name in sensitive_lookup:
1✔
457
                    continue
1✔
458
                raise errors.ValidationError(
1✔
459
                    message=f"The '{secret.name}' property is not marked sensitive and can not be saved in the secret "
460
                    f"storage."
461
                )
462
            expiration_timestamp = None
1✔
463

464
            if storage.storage_type == "openbis":
1✔
465

NEW
466
                async def openbis_transform_session_token_to_pat() -> (
×
467
                    tuple[list[models.DataConnectorSecretUpdate], datetime]
468
                ):
NEW
469
                    if len(unsaved_secrets) == 1 and unsaved_secrets[0].name == "session_token":
×
NEW
470
                        if unsaved_secrets[0].value is not None:
×
NEW
471
                            try:
×
NEW
472
                                openbis_pat = await get_openbis_pat(
×
473
                                    storage.configuration["host"], unsaved_secrets[0].value
474
                                )
NEW
475
                                return (
×
476
                                    [models.DataConnectorSecretUpdate(name="pass", value=openbis_pat[0])],
477
                                    openbis_pat[1],
478
                                )
NEW
479
                            except Exception as e:
×
NEW
480
                                raise errors.ProgrammingError(message=str(e)) from e
×
NEW
481
                        raise errors.ValidationError(message="The openBIS session token must be a string value.")
×
482

NEW
483
                    raise errors.ValidationError(message="The openBIS storage has only one secret: session_token")
×
484

NEW
485
                (
×
486
                    unsaved_secrets,
487
                    expiration_timestamp,
488
                ) = await openbis_transform_session_token_to_pat()
489

490
            secrets = await self.data_connector_secret_repo.patch_data_connector_secrets(
1✔
491
                user=user,
492
                data_connector_id=data_connector_id,
493
                secrets=unsaved_secrets,
494
                expiration_timestamp=expiration_timestamp,
495
            )
496
            return validated_json(
1✔
497
                apispec.DataConnectorSecretsList,
498
                [self._dump_data_connector_secret(secret) for secret in self._adjust_secrets(secrets, storage)],
499
            )
500

501
        return "/data_connectors/<data_connector_id:ulid>/secrets", ["PATCH"], _patch_secrets
2✔
502

503
    def delete_secrets(self) -> BlueprintFactoryResponse:
2✔
504
        """Delete all saved secrets for a data connector."""
505

506
        @authenticate(self.authenticator)
2✔
507
        @only_authenticated
2✔
508
        async def _delete_secrets(
2✔
509
            _: Request,
510
            user: base_models.APIUser,
511
            data_connector_id: ULID,
512
        ) -> HTTPResponse:
513
            await self.data_connector_secret_repo.delete_data_connector_secrets(
2✔
514
                user=user, data_connector_id=data_connector_id
515
            )
516
            return HTTPResponse(status=204)
2✔
517

518
        return "/data_connectors/<data_connector_id:ulid>/secrets", ["DELETE"], _delete_secrets
2✔
519

520
    @staticmethod
2✔
521
    def _dump_data_connector(
2✔
522
        data_connector: models.DataConnector | models.GlobalDataConnector, validator: RCloneValidator
523
    ) -> dict[str, Any]:
524
        """Dumps a data connector for API responses."""
525
        storage = dump_storage_with_sensitive_fields(data_connector.storage, validator=validator)
1✔
526
        if data_connector.namespace is None:
1✔
527
            return dict(
1✔
528
                id=str(data_connector.id),
529
                name=data_connector.name,
530
                slug=data_connector.slug,
531
                storage=storage,
532
                creation_date=data_connector.creation_date,
533
                created_by=data_connector.created_by,
534
                visibility=data_connector.visibility.value,
535
                description=data_connector.description,
536
                etag=data_connector.etag,
537
                keywords=data_connector.keywords or [],
538
            )
539
        return dict(
1✔
540
            id=str(data_connector.id),
541
            name=data_connector.name,
542
            namespace=data_connector.namespace.path.serialize(),
543
            slug=data_connector.slug,
544
            storage=storage,
545
            # secrets=,
546
            creation_date=data_connector.creation_date,
547
            created_by=data_connector.created_by,
548
            visibility=data_connector.visibility.value,
549
            description=data_connector.description,
550
            etag=data_connector.etag,
551
            keywords=data_connector.keywords or [],
552
        )
553

554
    @staticmethod
2✔
555
    def _dump_data_connector_to_project_link(link: models.DataConnectorToProjectLink) -> dict[str, Any]:
2✔
556
        """Dumps a link from a data connector to a project for API responses."""
557
        return dict(
1✔
558
            id=str(link.id),
559
            data_connector_id=str(link.data_connector_id),
560
            project_id=str(link.project_id),
561
            creation_date=link.creation_date,
562
            created_by=link.created_by,
563
        )
564

565
    @staticmethod
2✔
566
    def _adjust_secrets(
2✔
567
        secrets: list[models.DataConnectorSecret], storage: models.CloudStorageCore
568
    ) -> list[models.DataConnectorSecret]:
569
        if storage.storage_type == "openbis":
1✔
NEW
570
            for i, secret in enumerate(secrets):
×
NEW
571
                if secret.name == "pass":
×
NEW
572
                    secrets[i] = models.DataConnectorSecret(
×
573
                        name="session_token",
574
                        user_id=secret.user_id,
575
                        data_connector_id=secret.data_connector_id,
576
                        secret_id=secret.secret_id,
577
                    )
NEW
578
                    break
×
579
        return secrets
1✔
580

581
    @staticmethod
2✔
582
    def _dump_data_connector_secret(secret: models.DataConnectorSecret) -> dict[str, Any]:
2✔
583
        """Dumps a data connector secret for API responses."""
584
        return dict(
1✔
585
            name=secret.name,
586
            secret_id=str(secret.secret_id),
587
        )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc