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

geo-engine / geoengine-python / 16367912334

18 Jul 2025 10:06AM UTC coverage: 76.934% (+0.1%) from 76.806%
16367912334

push

github

web-flow
ci: use Ruff as new formatter and linter (#233)

* wip

* pycodestyle

* update dependencies

* skl2onnx

* use ruff

* apply formatter

* apply lint auto fixes

* manually apply lints

* change check

* ruff ci from branch

2805 of 3646 relevant lines covered (76.93%)

0.77 hits per line

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

74.87
geoengine/layers.py
1
"""
2
A wrapper around the layer and layerDb API
3
"""
4

5
from __future__ import annotations
1✔
6

7
import json
1✔
8
import os
1✔
9
from dataclasses import dataclass
1✔
10
from enum import auto
1✔
11
from io import StringIO
1✔
12
from typing import Any, Generic, Literal, TypeVar, cast
1✔
13
from uuid import UUID
1✔
14

15
import geoengine_openapi_client
1✔
16
from strenum import LowercaseStrEnum
1✔
17

18
from geoengine.auth import get_session
1✔
19
from geoengine.error import InputException, ModificationNotOnLayerDbException
1✔
20
from geoengine.permissions import Permission, RoleId, add_permission
1✔
21
from geoengine.resource_identifier import LAYER_DB_PROVIDER_ID, LayerCollectionId, LayerId, LayerProviderId, Resource
1✔
22
from geoengine.tasks import Task, TaskId
1✔
23
from geoengine.types import Symbology
1✔
24
from geoengine.workflow import Workflow, WorkflowId
1✔
25
from geoengine.workflow_builder.operators import Operator as WorkflowBuilderOperator
1✔
26

27

28
class LayerCollectionListingType(LowercaseStrEnum):
1✔
29
    LAYER = auto()
1✔
30
    COLLECTION = auto()
1✔
31

32

33
LISTINGID = TypeVar("LISTINGID")
1✔
34

35

36
@dataclass(repr=False)
1✔
37
class Listing(Generic[LISTINGID]):
1✔
38
    """A listing item of a collection"""
39

40
    listing_id: LISTINGID
1✔
41
    provider_id: LayerProviderId
1✔
42
    name: str
1✔
43
    description: str
1✔
44

45
    def __repr__(self) -> str:
1✔
46
        """String representation of a `Listing`"""
47

48
        buf = StringIO()
1✔
49

50
        buf.write(f"{self._type_str()}{os.linesep}")
1✔
51
        buf.write(f"name: {self.name}{os.linesep}")
1✔
52
        buf.write(f"description: {self.description}{os.linesep}")
1✔
53
        buf.write(f"id: {self.listing_id}{os.linesep}")
1✔
54
        buf.write(f"provider id: {self.provider_id}{os.linesep}")
1✔
55

56
        return buf.getvalue()
1✔
57

58
    def html_str(self) -> str:
1✔
59
        """HTML representation for Jupyter notebooks"""
60

61
        buf = StringIO()
×
62

63
        buf.write("<table>")
×
64
        buf.write(f'<thead><tr><th colspan="2">{self._type_str()}</th></tr></thead>')
×
65
        buf.write("<tbody>")
×
66
        buf.write(f"<tr><th>name</th><td>{self.name}</td></tr>")
×
67
        buf.write(f"<tr><th>description</th><td>{self.description}</td></tr>")
×
68
        buf.write(f"<tr><th>id</th><td>{self.listing_id}</td></tr>")
×
69
        buf.write(f"<tr><th>provider id</th><td>{self.provider_id}</td></tr>")
×
70
        buf.write("</tbody>")
×
71
        buf.write("</table>")
×
72

73
        return buf.getvalue()
×
74

75
    def _repr_html_(self) -> str:
1✔
76
        """HTML representation for Jupyter notebooks"""
77

78
        return self.html_str()
×
79

80
    def _type_str(self) -> str:
1✔
81
        """String representation of the listing type"""
82
        raise NotImplementedError("Please Implement this method")
×
83

84
    def load(self, timeout: int = 60) -> LayerCollection | Layer:
1✔
85
        """Load the listing item"""
86
        raise NotImplementedError("Please Implement this method")
×
87

88
    def _remove(self, collection_id: LayerCollectionId, provider_id: LayerProviderId, timeout: int = 60) -> None:
1✔
89
        """Remove the item behind the listing from the parent collection"""
90
        raise NotImplementedError("Please Implement this method")
×
91

92

93
@dataclass(repr=False)
1✔
94
class LayerListing(Listing[LayerId]):
1✔
95
    """A layer listing as item of a collection"""
96

97
    def _type_str(self) -> str:
1✔
98
        """String representation of the listing type"""
99

100
        return "Layer"
1✔
101

102
    def load(self, timeout: int = 60) -> LayerCollection | Layer:
1✔
103
        """Load the listing item"""
104

105
        return layer(self.listing_id, self.provider_id, timeout)
×
106

107
    def _remove(self, collection_id: LayerCollectionId, provider_id: LayerProviderId, timeout: int = 60) -> None:
1✔
108
        if provider_id != LAYER_DB_PROVIDER_ID:
1✔
109
            raise ModificationNotOnLayerDbException("Layer collection is not stored in the layer database")
×
110

111
        _delete_layer_from_collection(
1✔
112
            collection_id,
113
            self.listing_id,
114
            timeout,
115
        )
116

117

118
@dataclass(repr=False)
1✔
119
class LayerCollectionListing(Listing[LayerCollectionId]):
1✔
120
    """A layer listing as item of a collection"""
121

122
    def _type_str(self) -> str:
1✔
123
        """String representation of the listing type"""
124

125
        return "Layer Collection"
1✔
126

127
    def load(self, timeout: int = 60) -> LayerCollection | Layer:
1✔
128
        """Load the listing item"""
129

130
        return layer_collection(self.listing_id, self.provider_id, timeout)
1✔
131

132
    def _remove(self, collection_id: LayerCollectionId, provider_id: LayerProviderId, timeout: int = 60) -> None:
1✔
133
        if provider_id != LAYER_DB_PROVIDER_ID:
1✔
134
            raise ModificationNotOnLayerDbException("Layer collection is not stored in the layer database")
×
135

136
        _delete_layer_collection_from_collection(
1✔
137
            collection_id,
138
            self.listing_id,
139
            timeout,
140
        )
141

142

143
class LayerCollection:
1✔
144
    """A layer collection"""
145

146
    name: str
1✔
147
    description: str
1✔
148
    collection_id: LayerCollectionId
1✔
149
    provider_id: LayerProviderId
1✔
150
    items: list[Listing]
1✔
151

152
    # pylint: disable=too-many-arguments,too-many-positional-arguments
153
    def __init__(
1✔
154
        self,
155
        name: str,
156
        description: str,
157
        collection_id: LayerCollectionId,
158
        provider_id: LayerProviderId,
159
        items: list[Listing],
160
    ) -> None:
161
        """Create a new `LayerCollection`"""
162

163
        self.name = name
1✔
164
        self.description = description
1✔
165
        self.collection_id = collection_id
1✔
166
        self.provider_id = provider_id
1✔
167
        self.items = items
1✔
168

169
    @classmethod
1✔
170
    def from_response(cls, response_pages: list[geoengine_openapi_client.LayerCollection]) -> LayerCollection:
1✔
171
        """Parse an HTTP JSON response to an `LayerCollection`"""
172

173
        assert len(response_pages) > 0, "No response pages"
1✔
174

175
        def parse_listing(response: geoengine_openapi_client.CollectionItem) -> Listing:
1✔
176
            inner = response.actual_instance
1✔
177
            if inner is None:
1✔
178
                raise AssertionError("Invalid listing type")
×
179

180
            item_type = LayerCollectionListingType(inner.type)
1✔
181

182
            if item_type is LayerCollectionListingType.LAYER:
1✔
183
                layer_id_response = cast(geoengine_openapi_client.ProviderLayerId, inner.id)
1✔
184
                return LayerListing(
1✔
185
                    listing_id=LayerId(layer_id_response.layer_id),
186
                    provider_id=LayerProviderId(UUID(layer_id_response.provider_id)),
187
                    name=inner.name,
188
                    description=inner.description,
189
                )
190

191
            if item_type is LayerCollectionListingType.COLLECTION:
1✔
192
                collection_id_response = cast(geoengine_openapi_client.ProviderLayerCollectionId, inner.id)
1✔
193
                return LayerCollectionListing(
1✔
194
                    listing_id=LayerCollectionId(collection_id_response.collection_id),
195
                    provider_id=LayerProviderId(UUID(collection_id_response.provider_id)),
196
                    name=inner.name,
197
                    description=inner.description,
198
                )
199

200
            raise AssertionError("Invalid listing type")
×
201

202
        items = []
1✔
203
        for response in response_pages:
1✔
204
            for item_response in response.items:
1✔
205
                items.append(parse_listing(item_response))
1✔
206

207
        response = response_pages[0]
1✔
208

209
        return LayerCollection(
1✔
210
            name=response.name,
211
            description=response.description,
212
            collection_id=LayerCollectionId(response.id.collection_id),
213
            provider_id=LayerProviderId(UUID(response.id.provider_id)),
214
            items=items,
215
        )
216

217
    def reload(self) -> LayerCollection:
1✔
218
        """Reload the layer collection"""
219

220
        return layer_collection(self.collection_id, self.provider_id)
1✔
221

222
    def __repr__(self) -> str:
1✔
223
        """String representation of a `LayerCollection`"""
224

225
        buf = StringIO()
1✔
226

227
        buf.write(f"Layer Collection{os.linesep}")
1✔
228
        buf.write(f"name: {self.name}{os.linesep}")
1✔
229
        buf.write(f"description: {self.description}{os.linesep}")
1✔
230
        buf.write(f"id: {self.collection_id}{os.linesep}")
1✔
231
        buf.write(f"provider id: {self.provider_id}{os.linesep}")
1✔
232

233
        for i, item in enumerate(self.items):
1✔
234
            items_str = "items: "
1✔
235
            buf.write(items_str if i == 0 else " " * len(items_str))
1✔
236
            buf.write(f"{item}{os.linesep}")
1✔
237

238
        return buf.getvalue()
1✔
239

240
    def _repr_html_(self) -> str | None:
1✔
241
        """HTML representation for Jupyter notebooks"""
242

243
        buf = StringIO()
×
244

245
        buf.write("<table>")
×
246
        buf.write('<thead><tr><th colspan="2">Layer Collection</th></tr></thead>')
×
247
        buf.write("<tbody>")
×
248
        buf.write(f"<tr><th>name</th><td>{self.name}</td></tr>")
×
249
        buf.write(f"<tr><th>description</th><td>{self.description}</td></tr>")
×
250
        buf.write(f"<tr><th>id</th><td>{self.collection_id}</td></tr>")
×
251
        buf.write(f"<tr><th>provider id</th><td>{self.provider_id}</td></tr>")
×
252

253
        num_items = len(self.items)
×
254
        for i, item in enumerate(self.items):
×
255
            buf.write("<tr>")
×
256
            if i == 0:
×
257
                buf.write(f'<th rowspan="{num_items}">items</th>')
×
258
            buf.write(f"<td>{item.html_str()}</td>")
×
259
            buf.write("</tr>")
×
260

261
        buf.write("</tbody>")
×
262
        buf.write("</table>")
×
263

264
        return buf.getvalue()
×
265

266
    def remove(self, timeout: int = 60) -> None:
1✔
267
        """Remove the layer collection itself"""
268

269
        if self.provider_id != LAYER_DB_PROVIDER_ID:
1✔
270
            raise ModificationNotOnLayerDbException("Layer collection is not stored in the layer database")
×
271

272
        _delete_layer_collection(self.collection_id, timeout)
1✔
273

274
    def remove_item(self, index: int, timeout: int = 60):
1✔
275
        """Remove a layer or collection from this collection"""
276

277
        if index < 0 or index >= len(self.items):
1✔
278
            raise IndexError(f"index {index} out of range")
×
279

280
        item = self.items[index]
1✔
281

282
        if self.provider_id != LAYER_DB_PROVIDER_ID:
1✔
283
            raise ModificationNotOnLayerDbException("Layer collection is not stored in the layer database")
×
284

285
        # pylint: disable=protected-access
286
        item._remove(self.collection_id, self.provider_id, timeout)
1✔
287

288
        self.items.pop(index)
1✔
289

290
    def add_layer(
1✔
291
        self,
292
        name: str,
293
        description: str,
294
        workflow: dict[str, Any] | WorkflowBuilderOperator,  # TODO: improve type
295
        symbology: Symbology | None,
296
        timeout: int = 60,
297
    ) -> LayerId:
298
        """Add a layer to this collection"""
299
        # pylint: disable=too-many-arguments,too-many-positional-arguments
300

301
        if self.provider_id != LAYER_DB_PROVIDER_ID:
1✔
302
            raise ModificationNotOnLayerDbException("Layer collection is not stored in the layer database")
×
303

304
        layer_id = _add_layer_to_collection(name, description, workflow, symbology, self.collection_id, timeout)
1✔
305

306
        self.items.append(
1✔
307
            LayerListing(
308
                listing_id=layer_id,
309
                provider_id=self.provider_id,
310
                name=name,
311
                description=description,
312
            )
313
        )
314

315
        return layer_id
1✔
316

317
    def add_layer_with_permissions(
1✔
318
        self,
319
        name: str,
320
        description: str,
321
        workflow: dict[str, Any] | WorkflowBuilderOperator,  # TODO: improve type
322
        symbology: Symbology | None,
323
        permission_tuples: list[tuple[RoleId, Permission]] | None = None,
324
        timeout: int = 60,
325
    ) -> LayerId:
326
        """
327
        Add a layer to this collection and set permissions.
328
        """
329

330
        layer_id = self.add_layer(name, description, workflow, symbology, timeout)
1✔
331

332
        if permission_tuples is not None:
1✔
333
            res = Resource.from_layer_id(layer_id)
1✔
334
            for role, perm in permission_tuples:
1✔
335
                add_permission(role, res, perm)
1✔
336

337
        return layer_id
1✔
338

339
    def add_existing_layer(self, existing_layer: LayerListing | Layer | LayerId, timeout: int = 60):
1✔
340
        """Add an existing layer to this collection"""
341

342
        if self.provider_id != LAYER_DB_PROVIDER_ID:
1✔
343
            raise ModificationNotOnLayerDbException("Layer collection is not stored in the layer database")
×
344

345
        if isinstance(existing_layer, LayerListing):
1✔
346
            layer_id = existing_layer.listing_id
1✔
347
        elif isinstance(existing_layer, Layer):
×
348
            layer_id = existing_layer.layer_id
×
349
        elif isinstance(existing_layer, str):  # TODO: check for LayerId in Python 3.11+
×
350
            layer_id = existing_layer
×
351
        else:
352
            raise InputException("Invalid layer type")
×
353

354
        _add_existing_layer_to_collection(layer_id, self.collection_id, timeout)
1✔
355

356
        child_layer = layer(layer_id, self.provider_id)
1✔
357

358
        self.items.append(
1✔
359
            LayerListing(
360
                listing_id=layer_id,
361
                provider_id=self.provider_id,
362
                name=child_layer.name,
363
                description=child_layer.description,
364
            )
365
        )
366

367
        return layer_id
1✔
368

369
    def add_collection(self, name: str, description: str, timeout: int = 60) -> LayerCollectionId:
1✔
370
        """Add a collection to this collection"""
371

372
        if self.provider_id != LAYER_DB_PROVIDER_ID:
1✔
373
            raise ModificationNotOnLayerDbException("Layer collection is not stored in the layer database")
×
374

375
        collection_id = _add_layer_collection_to_collection(name, description, self.collection_id, timeout)
1✔
376

377
        self.items.append(
1✔
378
            LayerCollectionListing(
379
                listing_id=collection_id,
380
                provider_id=self.provider_id,
381
                name=name,
382
                description=description,
383
            )
384
        )
385

386
        return collection_id
1✔
387

388
    def add_existing_collection(
1✔
389
        self, existing_collection: LayerCollectionListing | LayerCollection | LayerCollectionId, timeout: int = 60
390
    ) -> LayerCollectionId:
391
        """Add an existing collection to this collection"""
392

393
        if self.provider_id != LAYER_DB_PROVIDER_ID:
1✔
394
            raise ModificationNotOnLayerDbException("Layer collection is not stored in the layer database")
×
395

396
        if isinstance(existing_collection, LayerCollectionListing):
1✔
397
            collection_id = existing_collection.listing_id
×
398
        elif isinstance(existing_collection, LayerCollection):
1✔
399
            collection_id = existing_collection.collection_id
×
400
        elif isinstance(existing_collection, str):  # TODO: check for LayerId in Python 3.11+
1✔
401
            collection_id = existing_collection
1✔
402
        else:
403
            raise InputException("Invalid collection type")
×
404

405
        _add_existing_layer_collection_to_collection(
1✔
406
            collection_id=collection_id, parent_collection_id=self.collection_id, timeout=timeout
407
        )
408

409
        child_collection = layer_collection(collection_id, self.provider_id)
1✔
410

411
        self.items.append(
1✔
412
            LayerCollectionListing(
413
                listing_id=collection_id,
414
                provider_id=self.provider_id,
415
                name=child_collection.name,
416
                description=child_collection.description,
417
            )
418
        )
419

420
        return collection_id
1✔
421

422
    def get_items_by_name(self, name: str) -> list[Listing]:
1✔
423
        """Get all children with the given name"""
424

425
        return [item for item in self.items if item.name == name]
1✔
426

427
    def search(
1✔
428
        self,
429
        search_string: str,
430
        *,
431
        search_type: Literal["fulltext", "prefix"] = "fulltext",
432
        offset: int = 0,
433
        limit: int = 20,
434
        timeout: int = 60,
435
    ) -> list[Listing]:
436
        """Search for a string in the layer collection"""
437

438
        session = get_session()
×
439

440
        if search_type not in [
×
441
            geoengine_openapi_client.SearchType.FULLTEXT,
442
            geoengine_openapi_client.SearchType.PREFIX,
443
        ]:
444
            raise ValueError(f"Invalid search type {search_type}")
×
445

446
        with geoengine_openapi_client.ApiClient(session.configuration) as api_client:
×
447
            layers_api = geoengine_openapi_client.LayersApi(api_client)
×
448
            layer_collection_response = layers_api.search_handler(
×
449
                provider=str(self.provider_id),
450
                collection=str(self.collection_id),
451
                search_string=search_string,
452
                search_type=geoengine_openapi_client.SearchType(search_type),
453
                offset=offset,
454
                limit=limit,
455
                _request_timeout=timeout,
456
            )
457

458
        listings: list[Listing] = []
×
459
        for item in layer_collection_response.items:
×
460
            inner = item.actual_instance
×
461
            if inner is None:
×
462
                continue
×
463
            if isinstance(inner, geoengine_openapi_client.LayerCollectionListing):
×
464
                listings.append(
×
465
                    LayerCollectionListing(
466
                        listing_id=LayerCollectionId(inner.id.collection_id),
467
                        provider_id=LayerProviderId(UUID(inner.id.provider_id)),
468
                        name=inner.name,
469
                        description=inner.description,
470
                    )
471
                )
472
            elif isinstance(inner, geoengine_openapi_client.LayerListing):
×
473
                listings.append(
×
474
                    LayerListing(
475
                        listing_id=LayerId(inner.id.layer_id),
476
                        provider_id=LayerProviderId(UUID(inner.id.provider_id)),
477
                        name=inner.name,
478
                        description=inner.description,
479
                    )
480
                )
481
            else:
482
                pass  # ignore, should not happen
×
483

484
        return listings
×
485

486
    def get_or_create_unique_collection(
1✔
487
        self,
488
        collection_name: str,
489
        create_collection_description: str | None = None,
490
        delete_existing_with_same_name: bool = False,
491
        create_permissions_tuples: list[tuple[RoleId, Permission]] | None = None,
492
    ) -> LayerCollection:
493
        """
494
        Get a unique child by name OR if it does not exist create it.
495
        Removes existing collections with same name if forced!
496
        Sets permissions if the collection is created from a list of tuples
497
        """
498
        parent_collection = self.reload()  # reload just to be safe since self's state change on the server
1✔
499
        existing_collections = parent_collection.get_items_by_name(collection_name)
1✔
500

501
        if delete_existing_with_same_name and len(existing_collections) > 0:
1✔
502
            for c in existing_collections:
1✔
503
                actual = c.load()
1✔
504
                if isinstance(actual, LayerCollection):
1✔
505
                    actual.remove()
1✔
506
            parent_collection = parent_collection.reload()
1✔
507
            existing_collections = parent_collection.get_items_by_name(collection_name)
1✔
508

509
        if len(existing_collections) == 0:
1✔
510
            new_desc = create_collection_description if create_collection_description is not None else collection_name
1✔
511
            new_collection = parent_collection.add_collection(collection_name, new_desc)
1✔
512
            new_ressource = Resource.from_layer_collection_id(new_collection)
1✔
513

514
            if create_permissions_tuples is not None:
1✔
515
                for role, perm in create_permissions_tuples:
1✔
516
                    add_permission(role, new_ressource, perm)
1✔
517
            parent_collection = parent_collection.reload()
1✔
518
            existing_collections = parent_collection.get_items_by_name(collection_name)
1✔
519

520
        if len(existing_collections) == 0:
1✔
521
            raise KeyError(
×
522
                f"No collection with name {collection_name} exists in {parent_collection.name} and none was created!"
523
            )
524

525
        if len(existing_collections) > 1:
1✔
526
            raise KeyError(f"Multiple collections with name {collection_name} exist in {parent_collection.name}")
×
527

528
        res = existing_collections[0].load()
1✔
529
        if isinstance(res, Layer):
1✔
530
            raise TypeError(f"Found a Layer not a Layer collection for {collection_name}")
×
531

532
        return cast(LayerCollection, existing_collections[0].load())  # we know that it is a collection since check that
1✔
533

534
    def __eq__(self, other):
1✔
535
        """Tests if two layer listings are identical"""
536
        if not isinstance(other, self.__class__):
1✔
537
            return False
×
538

539
        return (
1✔
540
            self.name == other.name
541
            and self.description == other.description
542
            and self.provider_id == other.provider_id
543
            and self.collection_id == other.collection_id
544
            and self.items == other.items
545
        )
546

547

548
@dataclass(repr=False)
1✔
549
class Layer:
1✔
550
    """A layer"""
551

552
    # pylint: disable=too-many-instance-attributes
553

554
    name: str
1✔
555
    description: str
1✔
556
    layer_id: LayerId
1✔
557
    provider_id: LayerProviderId
1✔
558
    workflow: dict[str, Any]  # TODO: specify in more detail
1✔
559
    symbology: Symbology | None
1✔
560
    properties: list[Any]  # TODO: specify in more detail
1✔
561
    metadata: dict[str, Any]  # TODO: specify in more detail
1✔
562

563
    def __init__(
1✔
564
        self,
565
        name: str,
566
        description: str,
567
        layer_id: LayerId,
568
        provider_id: LayerProviderId,
569
        workflow: dict[str, Any],
570
        symbology: Symbology | None,
571
        properties: list[Any],
572
        metadata: dict[Any, Any],
573
    ) -> None:
574
        """Create a new `Layer`"""
575
        # pylint: disable=too-many-arguments,too-many-positional-arguments
576

577
        self.name = name
1✔
578
        self.description = description
1✔
579
        self.layer_id = layer_id
1✔
580
        self.provider_id = provider_id
1✔
581
        self.workflow = workflow
1✔
582
        self.symbology = symbology
1✔
583
        self.properties = properties
1✔
584
        self.metadata = metadata
1✔
585

586
    @classmethod
1✔
587
    def from_response(cls, response: geoengine_openapi_client.Layer) -> Layer:
1✔
588
        """Parse an HTTP JSON response to an `Layer`"""
589
        symbology = None
1✔
590
        if response.symbology is not None:
1✔
591
            symbology = Symbology.from_response(response.symbology)
1✔
592

593
        return Layer(
1✔
594
            name=response.name,
595
            description=response.description,
596
            layer_id=LayerId(response.id.layer_id),
597
            provider_id=LayerProviderId(UUID(response.id.provider_id)),
598
            workflow=response.workflow.to_dict(),
599
            symbology=symbology,
600
            properties=cast(list[Any], response.properties),
601
            metadata=cast(dict[Any, Any], response.metadata),
602
        )
603

604
    def __repr__(self) -> str:
1✔
605
        """String representation of a `Layer`"""
606

607
        buf = StringIO()
×
608

609
        buf.write(f"Layer{os.linesep}")
×
610
        buf.write(f"name: {self.name}{os.linesep}")
×
611
        buf.write(f"description: {self.description}{os.linesep}")
×
612
        buf.write(f"id: {self.layer_id}{os.linesep}")
×
613
        buf.write(f"provider id: {self.provider_id}{os.linesep}")
×
614
        # TODO: better representation of workflow, symbology, properties, metadata
615
        buf.write(f"workflow: {self.workflow}{os.linesep}")
×
616
        buf.write(f"symbology: {self.symbology}{os.linesep}")
×
617
        buf.write(f"properties: {self.properties}{os.linesep}")
×
618
        buf.write(f"metadata: {self.metadata}{os.linesep}")
×
619

620
        return buf.getvalue()
×
621

622
    def _repr_html_(self) -> str | None:
1✔
623
        """HTML representation for Jupyter notebooks"""
624

625
        buf = StringIO()
1✔
626

627
        buf.write("<table>")
1✔
628
        buf.write('<thead><tr><th colspan="2">Layer</th></tr></thead>')
1✔
629
        buf.write("<tbody>")
1✔
630
        buf.write(f"<tr><th>name</th><td>{self.name}</td></tr>")
1✔
631
        buf.write(f"<tr><th>description</th><td>{self.description}</td></tr>")
1✔
632
        buf.write(f"<tr><th>id</th><td>{self.layer_id}</td></tr>")
1✔
633
        buf.write(f"<tr><th>provider id</th><td>{self.provider_id}</td></tr>")
1✔
634

635
        # TODO: better representation of workflow, symbology, properties, metadata
636
        buf.write('<tr><th>workflow</th><td align="left">')
1✔
637
        buf.write(f"<pre>{json.dumps(self.workflow, indent=4)}{os.linesep}</pre></td></tr>")
1✔
638
        buf.write("<tr><th>symbology</th>")
1✔
639
        if self.symbology is None:
1✔
640
            buf.write('<td align="left">None</td></tr>')
×
641
        else:
642
            buf.write(f'<td align="left"><pre>{self.symbology.to_api_dict().to_json()}{os.linesep}</pre></td></tr>')
1✔
643
        buf.write(f"<tr><th>properties</th><td>{self.properties}{os.linesep}</td></tr>")
1✔
644
        buf.write(f"<tr><th>metadata</th><td>{self.metadata}{os.linesep}</td></tr>")
1✔
645

646
        buf.write("</tbody>")
1✔
647
        buf.write("</table>")
1✔
648

649
        return buf.getvalue()
1✔
650

651
    def save_as_dataset(self, timeout: int = 60) -> Task:
1✔
652
        """
653
        Save a layer as a new dataset.
654
        """
655
        session = get_session()
1✔
656

657
        with geoengine_openapi_client.ApiClient(session.configuration) as api_client:
1✔
658
            layers_api = geoengine_openapi_client.LayersApi(api_client)
1✔
659
            response = layers_api.layer_to_dataset(str(self.provider_id), str(self.layer_id), _request_timeout=timeout)
1✔
660

661
        return Task(TaskId.from_response(response))
1✔
662

663
    def to_api_dict(self) -> geoengine_openapi_client.Layer:
1✔
664
        """Convert to a dictionary that can be serialized to JSON"""
665
        return geoengine_openapi_client.Layer(
×
666
            name=self.name,
667
            description=self.description,
668
            id=geoengine_openapi_client.ProviderLayerId(
669
                layer_id=str(self.layer_id),
670
                provider_id=str(self.provider_id),
671
            ),
672
            workflow=self.workflow,
673
            symbology=self.symbology.to_api_dict() if self.symbology is not None else None,
674
            properties=self.properties,
675
            metadata=self.metadata,
676
        )
677

678
    def as_workflow_id(self, timeout: int = 60) -> WorkflowId:
1✔
679
        """
680
        Register a layer as a workflow and returns its workflowId
681
        """
682
        session = get_session()
×
683

684
        with geoengine_openapi_client.ApiClient(session.configuration) as api_client:
×
685
            layers_api = geoengine_openapi_client.LayersApi(api_client)
×
686
            response = layers_api.layer_to_workflow_id_handler(
×
687
                str(self.provider_id), self.layer_id, _request_timeout=timeout
688
            )
689

690
        return WorkflowId.from_response(response)
×
691

692
    def as_workflow(self, timeout: int = 60) -> Workflow:
1✔
693
        """
694
        Register a layer as a workflow and returns the workflow
695
        """
696
        workflow_id = self.as_workflow_id(timeout=timeout)
×
697

698
        return Workflow(workflow_id)
×
699

700

701
def layer_collection(
1✔
702
    layer_collection_id: LayerCollectionId | None = None,
703
    layer_provider_id: LayerProviderId = LAYER_DB_PROVIDER_ID,
704
    timeout: int = 60,
705
) -> LayerCollection:
706
    """
707
    Retrieve a layer collection that contains layers and layer collections.
708
    """
709

710
    session = get_session()
1✔
711

712
    page_limit = 20
1✔
713
    pages: list[geoengine_openapi_client.LayerCollection] = []
1✔
714

715
    offset = 0
1✔
716
    while True:
1✔
717
        with geoengine_openapi_client.ApiClient(session.configuration) as api_client:
1✔
718
            layers_api = geoengine_openapi_client.LayersApi(api_client)
1✔
719

720
            if layer_collection_id is None:
1✔
721
                page = layers_api.list_root_collections_handler(offset, page_limit, _request_timeout=timeout)
×
722
            else:
723
                page = layers_api.list_collection_handler(
1✔
724
                    str(layer_provider_id), layer_collection_id, offset, page_limit, _request_timeout=timeout
725
                )
726

727
        if len(page.items) < page_limit:
1✔
728
            if len(pages) == 0 or len(page.items) > 0:  # we need at least one page before breaking
1✔
729
                pages.append(page)
1✔
730
            break
1✔
731

732
        pages.append(page)
×
733
        offset += page_limit
×
734

735
    return LayerCollection.from_response(pages)
1✔
736

737

738
def layer(layer_id: LayerId, layer_provider_id: LayerProviderId = LAYER_DB_PROVIDER_ID, timeout: int = 60) -> Layer:
1✔
739
    """
740
    Retrieve a layer from the server.
741
    """
742

743
    session = get_session()
1✔
744

745
    with geoengine_openapi_client.ApiClient(session.configuration) as api_client:
1✔
746
        layers_api = geoengine_openapi_client.LayersApi(api_client)
1✔
747
        response = layers_api.layer_handler(str(layer_provider_id), str(layer_id), _request_timeout=timeout)
1✔
748

749
    return Layer.from_response(response)
1✔
750

751

752
def _delete_layer_from_collection(collection_id: LayerCollectionId, layer_id: LayerId, timeout: int = 60) -> None:
1✔
753
    """Delete a layer from a collection"""
754

755
    session = get_session()
1✔
756

757
    with geoengine_openapi_client.ApiClient(session.configuration) as api_client:
1✔
758
        layers_api = geoengine_openapi_client.LayersApi(api_client)
1✔
759
        layers_api.remove_layer_from_collection(collection_id, layer_id, _request_timeout=timeout)
1✔
760

761

762
def _delete_layer_collection_from_collection(
1✔
763
    parent_id: LayerCollectionId, collection_id: LayerCollectionId, timeout: int = 60
764
) -> None:
765
    """Delete a layer collection from a collection"""
766

767
    session = get_session()
1✔
768

769
    with geoengine_openapi_client.ApiClient(session.configuration) as api_client:
1✔
770
        layers_api = geoengine_openapi_client.LayersApi(api_client)
1✔
771
        layers_api.remove_collection_from_collection(parent_id, collection_id, _request_timeout=timeout)
1✔
772

773

774
def _delete_layer_collection(collection_id: LayerCollectionId, timeout: int = 60) -> None:
1✔
775
    """Delete a layer collection"""
776

777
    session = get_session()
1✔
778

779
    with geoengine_openapi_client.ApiClient(session.configuration) as api_client:
1✔
780
        layers_api = geoengine_openapi_client.LayersApi(api_client)
1✔
781
        layers_api.remove_collection(collection_id, _request_timeout=timeout)
1✔
782

783

784
def _add_layer_collection_to_collection(
1✔
785
    name: str, description: str, parent_collection_id: LayerCollectionId, timeout: int = 60
786
) -> LayerCollectionId:
787
    """Add a new layer collection"""
788

789
    session = get_session()
1✔
790

791
    with geoengine_openapi_client.ApiClient(session.configuration) as api_client:
1✔
792
        layers_api = geoengine_openapi_client.LayersApi(api_client)
1✔
793
        response = layers_api.add_collection(
1✔
794
            parent_collection_id,
795
            geoengine_openapi_client.AddLayerCollection(
796
                name=name,
797
                description=description,
798
            ),
799
            _request_timeout=timeout,
800
        )
801

802
    return LayerCollectionId(response.id)
1✔
803

804

805
def _add_existing_layer_collection_to_collection(
1✔
806
    collection_id: LayerCollectionId, parent_collection_id: LayerCollectionId, timeout: int = 60
807
) -> None:
808
    """Add an existing layer collection to a collection"""
809

810
    session = get_session()
1✔
811

812
    with geoengine_openapi_client.ApiClient(session.configuration) as api_client:
1✔
813
        layers_api = geoengine_openapi_client.LayersApi(api_client)
1✔
814
        layers_api.add_existing_collection_to_collection(parent_collection_id, collection_id, _request_timeout=timeout)
1✔
815

816

817
def _add_layer_to_collection(
1✔
818
    name: str,
819
    description: str,
820
    workflow: dict[str, Any] | WorkflowBuilderOperator,  # TODO: improve type
821
    symbology: Symbology | None,
822
    collection_id: LayerCollectionId,
823
    timeout: int = 60,
824
) -> LayerId:
825
    """Add a new layer"""
826
    # pylint: disable=too-many-arguments,too-many-positional-arguments
827

828
    # convert workflow to dict if necessary
829
    if isinstance(workflow, WorkflowBuilderOperator):
1✔
830
        workflow = workflow.to_workflow_dict()
×
831

832
    symbology_dict = symbology.to_api_dict() if symbology is not None and isinstance(symbology, Symbology) else None
1✔
833

834
    session = get_session()
1✔
835

836
    with geoengine_openapi_client.ApiClient(session.configuration) as api_client:
1✔
837
        layers_api = geoengine_openapi_client.LayersApi(api_client)
1✔
838
        response = layers_api.add_layer(
1✔
839
            collection_id,
840
            geoengine_openapi_client.AddLayer(
841
                name=name, description=description, workflow=workflow, symbology=symbology_dict
842
            ),
843
            _request_timeout=timeout,
844
        )
845

846
    return LayerId(response.id)
1✔
847

848

849
def _add_existing_layer_to_collection(layer_id: LayerId, collection_id: LayerCollectionId, timeout: int = 60) -> None:
1✔
850
    """Add an existing layer to a collection"""
851

852
    session = get_session()
1✔
853

854
    with geoengine_openapi_client.ApiClient(session.configuration) as api_client:
1✔
855
        layers_api = geoengine_openapi_client.LayersApi(api_client)
1✔
856
        layers_api.add_existing_layer_to_collection(collection_id, layer_id, _request_timeout=timeout)
1✔
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