Skip to content

interface

interface

common

This module contains common logic, shared beyond all specialized parts of the QGIS-Server-Light interface.

BBox dataclass

Bases: BaseInterface

Source code in src/qgis_server_light/interface/common.py
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
@dataclass(repr=False)
class BBox(BaseInterface):
    x_min: float = field(metadata={"type": "Element"})
    x_max: float = field(metadata={"type": "Element"})
    y_min: float = field(metadata={"type": "Element"})
    y_max: float = field(metadata={"type": "Element"})
    z_min: float = field(default=0.0, metadata={"type": "Element"})
    z_max: float = field(default=0.0, metadata={"type": "Element"})

    def to_list(self) -> list:
        return [self.x_min, self.y_min, self.z_min, self.x_max, self.y_max, self.z_max]

    def to_string(self) -> str:
        return ",".join([str(item) for item in self.to_list()])

    def to_2d_list(self) -> list:
        return [self.x_min, self.y_min, self.x_max, self.y_max]

    def to_2d_string(self) -> str:
        return ",".join([str(item) for item in self.to_2d_list()])

    @staticmethod
    def from_string(bbox_string: str) -> "BBox":
        """
        Takes a CSV string representation of a BBox in the form:
            '<x_min>,<y_min>,<x_max>,<y_max>' or
            '<x_min>,<y_min>,<z_min>,<x_max>,<y_max>,<z_max>'
        """
        coordinates = bbox_string.split(",")
        if len(coordinates) == 4:
            return BBox(
                x_min=float(coordinates[0]),
                y_min=float(coordinates[1]),
                x_max=float(coordinates[2]),
                y_max=float(coordinates[3]),
            )
        elif len(coordinates) == 6:
            return BBox(
                x_min=float(coordinates[0]),
                y_min=float(coordinates[1]),
                z_min=float(coordinates[2]),
                x_max=float(coordinates[3]),
                y_max=float(coordinates[4]),
                z_max=float(coordinates[5]),
            )
        else:
            raise ValueError(f"Invalid bbox string: {bbox_string}")

    @staticmethod
    def from_list(bbox_list: List[float]) -> "BBox":
        """
        Takes a list representation of a BBox in the form:
            [<x_min>,<y_min>,<x_max>,<y_max>] or
            [<x_min>,<y_min>,<z_min>,<x_max>,<y_max>,<z_max>]
        """
        if len(bbox_list) == 4:
            return BBox(
                x_min=bbox_list[0],
                y_min=bbox_list[1],
                x_max=bbox_list[2],
                y_max=bbox_list[3],
            )
        elif len(bbox_list) == 6:
            return BBox(
                x_min=bbox_list[0],
                y_min=bbox_list[1],
                z_min=bbox_list[2],
                x_max=bbox_list[3],
                y_max=bbox_list[4],
                z_max=bbox_list[5],
            )
        else:
            raise ValueError(f"Invalid bbox list: {bbox_list}")
x_max: float = field(metadata={'type': 'Element'}) class-attribute instance-attribute
x_min: float = field(metadata={'type': 'Element'}) class-attribute instance-attribute
y_max: float = field(metadata={'type': 'Element'}) class-attribute instance-attribute
y_min: float = field(metadata={'type': 'Element'}) class-attribute instance-attribute
z_max: float = field(default=0.0, metadata={'type': 'Element'}) class-attribute instance-attribute
z_min: float = field(default=0.0, metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(x_min: float, x_max: float, y_min: float, y_max: float, z_min: float = 0.0, z_max: float = 0.0) -> None
from_list(bbox_list: List[float]) -> BBox staticmethod
Takes a list representation of a BBox in the form

[,,,] or [,,,,,]

Source code in src/qgis_server_light/interface/common.py
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
@staticmethod
def from_list(bbox_list: List[float]) -> "BBox":
    """
    Takes a list representation of a BBox in the form:
        [<x_min>,<y_min>,<x_max>,<y_max>] or
        [<x_min>,<y_min>,<z_min>,<x_max>,<y_max>,<z_max>]
    """
    if len(bbox_list) == 4:
        return BBox(
            x_min=bbox_list[0],
            y_min=bbox_list[1],
            x_max=bbox_list[2],
            y_max=bbox_list[3],
        )
    elif len(bbox_list) == 6:
        return BBox(
            x_min=bbox_list[0],
            y_min=bbox_list[1],
            z_min=bbox_list[2],
            x_max=bbox_list[3],
            y_max=bbox_list[4],
            z_max=bbox_list[5],
        )
    else:
        raise ValueError(f"Invalid bbox list: {bbox_list}")
from_string(bbox_string: str) -> BBox staticmethod
Takes a CSV string representation of a BBox in the form

',,,' or ',,,,,'

Source code in src/qgis_server_light/interface/common.py
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
@staticmethod
def from_string(bbox_string: str) -> "BBox":
    """
    Takes a CSV string representation of a BBox in the form:
        '<x_min>,<y_min>,<x_max>,<y_max>' or
        '<x_min>,<y_min>,<z_min>,<x_max>,<y_max>,<z_max>'
    """
    coordinates = bbox_string.split(",")
    if len(coordinates) == 4:
        return BBox(
            x_min=float(coordinates[0]),
            y_min=float(coordinates[1]),
            x_max=float(coordinates[2]),
            y_max=float(coordinates[3]),
        )
    elif len(coordinates) == 6:
        return BBox(
            x_min=float(coordinates[0]),
            y_min=float(coordinates[1]),
            z_min=float(coordinates[2]),
            x_max=float(coordinates[3]),
            y_max=float(coordinates[4]),
            z_max=float(coordinates[5]),
        )
    else:
        raise ValueError(f"Invalid bbox string: {bbox_string}")
to_2d_list() -> list
Source code in src/qgis_server_light/interface/common.py
186
187
def to_2d_list(self) -> list:
    return [self.x_min, self.y_min, self.x_max, self.y_max]
to_2d_string() -> str
Source code in src/qgis_server_light/interface/common.py
189
190
def to_2d_string(self) -> str:
    return ",".join([str(item) for item in self.to_2d_list()])
to_list() -> list
Source code in src/qgis_server_light/interface/common.py
180
181
def to_list(self) -> list:
    return [self.x_min, self.y_min, self.z_min, self.x_max, self.y_max, self.z_max]
to_string() -> str
Source code in src/qgis_server_light/interface/common.py
183
184
def to_string(self) -> str:
    return ",".join([str(item) for item in self.to_list()])

BaseInterface dataclass

This class should be used as base class for all dataclasses in the interface. It offers useful methods to handle exposed content in a centralized way. Mainly for logging redaction.

Since dataclasses gets a repr method installed automatically when they are created, a dataclass inheriting from this base class has to be defined as follows:

@dataclass(repr=False)
class Config(BaseInterface):
    id: int = field(metadata={"type": "Element"})
    secure: str = field(metadata={"type": "Element"})
    long_content: str = field(metadata={"type": "Element"})

    @property
    def shortened_fields(self) -> set:
        return {"long_content"}

    @property
    def redacted_fields(self) -> set:
        return {"secure"}

This way, when an instance of this example class gets logged somewhere it the output will be redacted, meaning the logging output might look like this:

Config(id=1, secure=**REDACTED**, long_content=abc12...io345)
Source code in src/qgis_server_light/interface/common.py
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
@dataclass
class BaseInterface:
    """
    This class should be used as base class for all dataclasses in the interface. It offers useful methods to
    handle exposed content in a centralized way. Mainly for logging redaction.

    Since dataclasses gets a __repr__ method installed automatically when they are created, a dataclass
    inheriting from this base class has to be defined as follows:

        @dataclass(repr=False)
        class Config(BaseInterface):
            id: int = field(metadata={"type": "Element"})
            secure: str = field(metadata={"type": "Element"})
            long_content: str = field(metadata={"type": "Element"})

            @property
            def shortened_fields(self) -> set:
                return {"long_content"}

            @property
            def redacted_fields(self) -> set:
                return {"secure"}

    This way, when an instance of this example class gets logged somewhere it the output will be redacted,
    meaning the logging output might look like this:

        Config(id=1, secure=**REDACTED**, long_content=abc12...io345)

    """

    @property
    def shorten_limit(self) -> int:
        """
        The limit to which the content of a field should be shortened.

        Returns:
            The limit.
        """
        return 5

    @property
    def redacted_fields(self) -> set:
        """
        Field which contents should get redacted before printing them on the log. This is mainly used to
        prevent passwords in logs.

        Returns:
            The set of field names which should be redacted
        """
        return set()

    @property
    def shortened_fields(self) -> set:
        """
        Fields which should be shortened to a length, this is manly useful for large content fields with
        BLOB etc.

        Returns:
            The set field names which should be shortened.
        """
        return set()

    def _value_string(self, repr_value: str | bytes):
        return f"{repr_value[: self.shorten_limit]}...{repr_value[((1 + self.shorten_limit) * -1) :]}"

    def _type_aware_value_string(self, value, repr_value):
        value_string = self._value_string(repr_value)
        if type(value) in [str]:
            return f"'{value_string}'"
        else:
            return f"{value_string}"

    def __repr__(self):
        members = []
        cls = self.__class__.__name__
        for obj_field in fields(self):
            # this is the original switch dataclasses allow on fields
            if obj_field.repr:
                value = getattr(self, obj_field.name)
                repr_value = str(value)
                if obj_field.name in self.redacted_fields:
                    members.append(f"{obj_field.name}=**REDACTED**")
                elif (
                    obj_field.name in self.shortened_fields
                    and value is not None
                    and len(repr_value) > self.shorten_limit * 2
                ):
                    members.append(
                        f"{obj_field.name}={self._type_aware_value_string(value, repr_value)}"
                    )
                else:
                    members.append(f"{obj_field.name}={value!r}")
        return f"{cls}({', '.join(members)})"
redacted_fields: set property

Field which contents should get redacted before printing them on the log. This is mainly used to prevent passwords in logs.

Returns:

  • set

    The set of field names which should be redacted

shorten_limit: int property

The limit to which the content of a field should be shortened.

Returns:

  • int

    The limit.

shortened_fields: set property

Fields which should be shortened to a length, this is manly useful for large content fields with BLOB etc.

Returns:

  • set

    The set field names which should be shortened.

__init__() -> None
__repr__()
Source code in src/qgis_server_light/interface/common.py
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
def __repr__(self):
    members = []
    cls = self.__class__.__name__
    for obj_field in fields(self):
        # this is the original switch dataclasses allow on fields
        if obj_field.repr:
            value = getattr(self, obj_field.name)
            repr_value = str(value)
            if obj_field.name in self.redacted_fields:
                members.append(f"{obj_field.name}=**REDACTED**")
            elif (
                obj_field.name in self.shortened_fields
                and value is not None
                and len(repr_value) > self.shorten_limit * 2
            ):
                members.append(
                    f"{obj_field.name}={self._type_aware_value_string(value, repr_value)}"
                )
            else:
                members.append(f"{obj_field.name}={value!r}")
    return f"{cls}({', '.join(members)})"

PgServiceConf dataclass

Bases: BaseInterface

A typed definition of the pg_service.conf definition which might be used.

Source code in src/qgis_server_light/interface/common.py
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
@dataclass(repr=False)
class PgServiceConf(BaseInterface):
    """
    A typed definition of the pg_service.conf definition which might be used.
    """

    name: str = field(metadata={"type": "Element"})
    host: str | None = field(
        default=None,
        metadata={"type": "Element"},
    )
    port: int | None = field(default=None, metadata={"type": "Element"})
    user: str | None = field(default=None, metadata={"type": "Element"})
    dbname: str | None = field(default=None, metadata={"type": "Element"})
    password: str | None = field(default=None, metadata={"type": "Element"})
    sslmode: SslMode = field(default=SslMode.PREFER, metadata={"type": "Element"})
    application_name: str | None = field(default=None, metadata={"type": "Element"})
    client_encoding: str = field(default="UTF8", metadata={"type": "Element"})
    # possibilitiy to link to another service (nested definitions!)
    service: str | None = field(default=None, metadata={"type": "Element"})

    @property
    def redacted_fields(self) -> set:
        return {"password"}
application_name: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
client_encoding: str = field(default='UTF8', metadata={'type': 'Element'}) class-attribute instance-attribute
dbname: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
host: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
name: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
password: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
port: int | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
redacted_fields: set property
service: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
sslmode: SslMode = field(default=(SslMode.PREFER), metadata={'type': 'Element'}) class-attribute instance-attribute
user: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(name: str, host: str | None = None, port: int | None = None, user: str | None = None, dbname: str | None = None, password: str | None = None, sslmode: SslMode = SslMode.PREFER, application_name: str | None = None, client_encoding: str = 'UTF8', service: str | None = None) -> None

RedactedString

This special string class can be used to handle secret strings in the application. It works like a normal string but in case it's used to print or log its value is not reveled to the output.

Source code in src/qgis_server_light/interface/common.py
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
class RedactedString:
    """
    This special string class can be used to handle secret strings in the application. It works like a normal
    string but in case it's used to print or log its value is not reveled to the output.
    """

    def __init__(self, value, redacted_text="**REDACTED**"):
        self._value = value
        self._redacted_text = redacted_text

    def __str__(self):
        return self._redacted_text

    def __repr__(self):
        return f"<RedactedString {self._redacted_text}>"

    def __format__(self, format_spec):
        return self._redacted_text

    def __json__(self):
        return self._redacted_text

    def reveal(self):
        """
        Allows access to the original value when necessary.

        Returns:
            The secret string.
        """

        return self._value
__format__(format_spec)
Source code in src/qgis_server_light/interface/common.py
119
120
def __format__(self, format_spec):
    return self._redacted_text
__init__(value, redacted_text='**REDACTED**')
Source code in src/qgis_server_light/interface/common.py
109
110
111
def __init__(self, value, redacted_text="**REDACTED**"):
    self._value = value
    self._redacted_text = redacted_text
__json__()
Source code in src/qgis_server_light/interface/common.py
122
123
def __json__(self):
    return self._redacted_text
__repr__()
Source code in src/qgis_server_light/interface/common.py
116
117
def __repr__(self):
    return f"<RedactedString {self._redacted_text}>"
__str__()
Source code in src/qgis_server_light/interface/common.py
113
114
def __str__(self):
    return self._redacted_text
reveal()

Allows access to the original value when necessary.

Returns:

  • The secret string.

Source code in src/qgis_server_light/interface/common.py
125
126
127
128
129
130
131
132
133
def reveal(self):
    """
    Allows access to the original value when necessary.

    Returns:
        The secret string.
    """

    return self._value

SslMode

Bases: str, Enum

Source code in src/qgis_server_light/interface/common.py
136
137
138
139
140
141
142
class SslMode(str, Enum):
    DISABLE = "disable"
    ALLOW = "allow"
    PREFER = "prefer"
    REQUIRE = "require"
    VERIFY_CA = "verify-ca"
    VERIFY_FULL = "verify-full"
ALLOW = 'allow' class-attribute instance-attribute
DISABLE = 'disable' class-attribute instance-attribute
PREFER = 'prefer' class-attribute instance-attribute
REQUIRE = 'require' class-attribute instance-attribute
VERIFY_CA = 'verify-ca' class-attribute instance-attribute
VERIFY_FULL = 'verify-full' class-attribute instance-attribute

Style dataclass

Bases: BaseInterface

Source code in src/qgis_server_light/interface/common.py
246
247
248
249
250
251
252
253
@dataclass(repr=False)
class Style(BaseInterface):
    name: str = field(metadata={"type": "Element"})
    definition: str = field(metadata={"type": "Element"})

    @property
    def shortened_fields(self) -> set:
        return {"definition"}
definition: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
name: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
shortened_fields: set property
__init__(name: str, definition: str) -> None

dispatcher

common

Status

Bases: Enum

Source code in src/qgis_server_light/interface/dispatcher/common.py
4
5
6
7
8
class Status(Enum):
    SUCCESS = "succeed"
    FAILURE = "failed"
    RUNNING = "running"
    QUEUED = "queued"
FAILURE = 'failed' class-attribute instance-attribute
QUEUED = 'queued' class-attribute instance-attribute
RUNNING = 'running' class-attribute instance-attribute
SUCCESS = 'succeed' class-attribute instance-attribute

redis_asio

This contains the interface definition about how a job info is passed around a redis queue.

RedisQueue
Source code in src/qgis_server_light/interface/dispatcher/redis_asio.py
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
class RedisQueue:
    job_queue_name: str = "jobs"
    job_info_key: str = "info"
    job_info_type_key: str = "info_type"
    job_channel_name: str = "notifications"
    job_status_key: str = "status"
    job_duration_key: str = "duration"
    job_timestamp_key: str = "timestamp"
    job_last_update_key: str = f"{job_timestamp_key}.last_update"

    def __init__(self, redis_client: redis_aio.Redis) -> None:
        # we use this to hold connections to redis in a pool, this way we are
        # event loop safe and when creating the redis client for every call of
        # post, we only instantiate a minimal wrapper object which is cheap.

        self.client = redis_client

    @classmethod
    def create(cls, url: str):
        redis_client = redis_aio.Redis.from_url(url)
        return cls(redis_client)

    async def set_job_runtime_status(
        self,
        job_id,
        pipeline: Pipeline,
        status: str,
        start_time: float,
    ):
        duration = time.time() - start_time
        ts = datetime.datetime.now().isoformat()
        await pipeline.hset(f"job:{job_id}", self.job_status_key, status)
        await pipeline.hset(
            f"job:{job_id}",
            f"{self.job_timestamp_key}.{status}",
            ts,
        )
        await pipeline.hset(f"job:{job_id}", self.job_last_update_key, ts)
        await pipeline.hset(f"job:{job_id}", self.job_duration_key, str(duration))
        await pipeline.execute()

    async def post(
        self,
        job_parameter: (
            QslJobParameterRender
            | QslJobParameterFeatureInfo
            | QslJobParameterLegend
            | QslJobParameterFeature
        ),
        to: float = 10.0,
    ) -> tuple[JobResult, str]:
        """
        Posts a new `runner` to the runner queue and waits maximum `timeout` seconds to complete.
        Will return a JobResult if successful or raise an error.

        Args:
            job_parameter: The parameter for the job which should be executed.
            to: The timeout a job is expected to be waited for before canceling
                job execution.
        """
        job_id = str(uuid4())
        start_time = time.time()
        if isinstance(job_parameter, QslJobParameterRender):
            job_info = QslJobInfoRender(
                id=job_id, type=QslJobInfoRender.__name__, job=job_parameter
            )
        elif isinstance(job_parameter, QslJobParameterFeatureInfo):
            job_info = QslJobInfoFeatureInfo(
                id=job_id, type=QslJobParameterFeatureInfo.__name__, job=job_parameter
            )
        elif isinstance(job_parameter, QslJobParameterLegend):
            job_info = QslJobInfoLegend(
                id=job_id, type=QslJobInfoLegend.__name__, job=job_parameter
            )
        elif isinstance(job_parameter, QslJobParameterFeature):
            job_info = QslJobInfoFeature(
                id=job_id, type=QslJobInfoFeature.__name__, job=job_parameter
            )
        else:
            return (
                JobResult(
                    id=job_id,
                    data=f"Unsupported runner type: {type(job_parameter)}",
                    content_type="application/text",
                ),
                Status.FAILURE.value,
            )
        async with self.client.pipeline() as p:
            # Putting job info into redis
            await p.hset(
                f"job:{job_id}", self.job_info_key, JsonSerializer().render(job_info)
            )
            await p.hset(
                f"job:{job_id}", self.job_info_type_key, job_info.__class__.__name__
            )
            # Queuing the job onto the list/queue
            await p.rpush(self.job_queue_name, job_id)
            await p.execute()

            logging.info(f"{job_id} queued")

            # we inform, that the job was queued
            await self.set_job_runtime_status(
                job_id, p, Status.QUEUED.value, start_time
            )
            try:
                async with self.client.pubsub() as ps:
                    # we tell redis to let us know if a message is published
                    # for this channel `notifications:{job_id}`.
                    await ps.subscribe(f"{self.job_channel_name}:{job_id}")
                    try:
                        # this puts a timeout trigger on the subscription, after timeout
                        # an asyncio.TimeoutError or asyncio.exceptions.CancelledError
                        # is raised. See except block below.
                        async with timeout(to):
                            while True:
                                message = await ps.get_message(
                                    timeout=to, ignore_subscribe_messages=True
                                )
                                if not message:
                                    continue  # https://github.com/redis/redis-py/issues/733
                                status_binary = await self.client.hget(
                                    f"job:{job_id}", "status"
                                )
                                status = status_binary.decode()
                                result: JobResult = pickle.loads(message["data"])
                                duration = time.time() - start_time
                                if status == Status.SUCCESS.value:
                                    logging.info(
                                        f"Job id: {job_id}, status: {status}, "
                                        f"duration: {duration}"
                                    )
                                elif status == Status.FAILURE.value:
                                    logging.info(
                                        f"Job id: {job_id}, status: {status}, "
                                        f"duration: {duration}, error: {result.data}"
                                    )
                                return result, status
                    except (asyncio.TimeoutError, asyncio.exceptions.CancelledError):
                        logging.info(f"{job_id} timeout")
                        raise
            except Exception as e:
                duration = time.time() - start_time
                logging.info(
                    f"Job id: {job_id}, status: {Status.FAILURE.value}, duration: "
                    f"{duration}",
                    exc_info=True,
                )
                return (
                    JobResult(
                        id=job_id,
                        data=str(e),
                        content_type="application/text",
                    ),
                    Status.FAILURE.value,
                )
            finally:
                try:
                    await self.client.delete(f"job:{job_id}")
                except Exception:
                    logging.warning(
                        f"Cleanup failed for {job_id}",
                        exc_info=True,
                    )
client = redis_client instance-attribute
job_channel_name: str = 'notifications' class-attribute instance-attribute
job_duration_key: str = 'duration' class-attribute instance-attribute
job_info_key: str = 'info' class-attribute instance-attribute
job_info_type_key: str = 'info_type' class-attribute instance-attribute
job_last_update_key: str = f'{job_timestamp_key}.last_update' class-attribute instance-attribute
job_queue_name: str = 'jobs' class-attribute instance-attribute
job_status_key: str = 'status' class-attribute instance-attribute
job_timestamp_key: str = 'timestamp' class-attribute instance-attribute
__init__(redis_client: redis_aio.Redis) -> None
Source code in src/qgis_server_light/interface/dispatcher/redis_asio.py
48
49
50
51
52
53
def __init__(self, redis_client: redis_aio.Redis) -> None:
    # we use this to hold connections to redis in a pool, this way we are
    # event loop safe and when creating the redis client for every call of
    # post, we only instantiate a minimal wrapper object which is cheap.

    self.client = redis_client
create(url: str) classmethod
Source code in src/qgis_server_light/interface/dispatcher/redis_asio.py
55
56
57
58
@classmethod
def create(cls, url: str):
    redis_client = redis_aio.Redis.from_url(url)
    return cls(redis_client)
post(job_parameter: QslJobParameterRender | QslJobParameterFeatureInfo | QslJobParameterLegend | QslJobParameterFeature, to: float = 10.0) -> tuple[JobResult, str] async

Posts a new runner to the runner queue and waits maximum timeout seconds to complete. Will return a JobResult if successful or raise an error.

Parameters:

Source code in src/qgis_server_light/interface/dispatcher/redis_asio.py
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
async def post(
    self,
    job_parameter: (
        QslJobParameterRender
        | QslJobParameterFeatureInfo
        | QslJobParameterLegend
        | QslJobParameterFeature
    ),
    to: float = 10.0,
) -> tuple[JobResult, str]:
    """
    Posts a new `runner` to the runner queue and waits maximum `timeout` seconds to complete.
    Will return a JobResult if successful or raise an error.

    Args:
        job_parameter: The parameter for the job which should be executed.
        to: The timeout a job is expected to be waited for before canceling
            job execution.
    """
    job_id = str(uuid4())
    start_time = time.time()
    if isinstance(job_parameter, QslJobParameterRender):
        job_info = QslJobInfoRender(
            id=job_id, type=QslJobInfoRender.__name__, job=job_parameter
        )
    elif isinstance(job_parameter, QslJobParameterFeatureInfo):
        job_info = QslJobInfoFeatureInfo(
            id=job_id, type=QslJobParameterFeatureInfo.__name__, job=job_parameter
        )
    elif isinstance(job_parameter, QslJobParameterLegend):
        job_info = QslJobInfoLegend(
            id=job_id, type=QslJobInfoLegend.__name__, job=job_parameter
        )
    elif isinstance(job_parameter, QslJobParameterFeature):
        job_info = QslJobInfoFeature(
            id=job_id, type=QslJobInfoFeature.__name__, job=job_parameter
        )
    else:
        return (
            JobResult(
                id=job_id,
                data=f"Unsupported runner type: {type(job_parameter)}",
                content_type="application/text",
            ),
            Status.FAILURE.value,
        )
    async with self.client.pipeline() as p:
        # Putting job info into redis
        await p.hset(
            f"job:{job_id}", self.job_info_key, JsonSerializer().render(job_info)
        )
        await p.hset(
            f"job:{job_id}", self.job_info_type_key, job_info.__class__.__name__
        )
        # Queuing the job onto the list/queue
        await p.rpush(self.job_queue_name, job_id)
        await p.execute()

        logging.info(f"{job_id} queued")

        # we inform, that the job was queued
        await self.set_job_runtime_status(
            job_id, p, Status.QUEUED.value, start_time
        )
        try:
            async with self.client.pubsub() as ps:
                # we tell redis to let us know if a message is published
                # for this channel `notifications:{job_id}`.
                await ps.subscribe(f"{self.job_channel_name}:{job_id}")
                try:
                    # this puts a timeout trigger on the subscription, after timeout
                    # an asyncio.TimeoutError or asyncio.exceptions.CancelledError
                    # is raised. See except block below.
                    async with timeout(to):
                        while True:
                            message = await ps.get_message(
                                timeout=to, ignore_subscribe_messages=True
                            )
                            if not message:
                                continue  # https://github.com/redis/redis-py/issues/733
                            status_binary = await self.client.hget(
                                f"job:{job_id}", "status"
                            )
                            status = status_binary.decode()
                            result: JobResult = pickle.loads(message["data"])
                            duration = time.time() - start_time
                            if status == Status.SUCCESS.value:
                                logging.info(
                                    f"Job id: {job_id}, status: {status}, "
                                    f"duration: {duration}"
                                )
                            elif status == Status.FAILURE.value:
                                logging.info(
                                    f"Job id: {job_id}, status: {status}, "
                                    f"duration: {duration}, error: {result.data}"
                                )
                            return result, status
                except (asyncio.TimeoutError, asyncio.exceptions.CancelledError):
                    logging.info(f"{job_id} timeout")
                    raise
        except Exception as e:
            duration = time.time() - start_time
            logging.info(
                f"Job id: {job_id}, status: {Status.FAILURE.value}, duration: "
                f"{duration}",
                exc_info=True,
            )
            return (
                JobResult(
                    id=job_id,
                    data=str(e),
                    content_type="application/text",
                ),
                Status.FAILURE.value,
            )
        finally:
            try:
                await self.client.delete(f"job:{job_id}")
            except Exception:
                logging.warning(
                    f"Cleanup failed for {job_id}",
                    exc_info=True,
                )
set_job_runtime_status(job_id, pipeline: Pipeline, status: str, start_time: float) async
Source code in src/qgis_server_light/interface/dispatcher/redis_asio.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
async def set_job_runtime_status(
    self,
    job_id,
    pipeline: Pipeline,
    status: str,
    start_time: float,
):
    duration = time.time() - start_time
    ts = datetime.datetime.now().isoformat()
    await pipeline.hset(f"job:{job_id}", self.job_status_key, status)
    await pipeline.hset(
        f"job:{job_id}",
        f"{self.job_timestamp_key}.{status}",
        ts,
    )
    await pipeline.hset(f"job:{job_id}", self.job_last_update_key, ts)
    await pipeline.hset(f"job:{job_id}", self.job_duration_key, str(duration))
    await pipeline.execute()

exporter

api

ExportParameters dataclass

Bases: BaseInterface

The serializable request parameters which are accepted by the exporter service.

Source code in src/qgis_server_light/interface/exporter/api.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@dataclass(repr=False)
class ExportParameters(BaseInterface):
    """
    The serializable request parameters which are accepted by the exporter service.
    """

    mandant: str = field(metadata={"type": "Element"})
    project: str = field(metadata={"type": "Element"})
    unify_layer_names_by_group: bool = field(
        metadata={"type": "Element"}, default=False
    )
    output_format: str = field(metadata={"type": "Element"}, default="json")
    pg_service_configs: list[PgServiceConf] = field(
        metadata={"type": "Element"}, default_factory=list
    )

    @property
    def pg_service_configs_dict(self) -> dict:
        configurations = {}
        for config in self.pg_service_configs:
            configurations[config.name] = {
                "host": config.host,
                "port": config.port,
                "user": config.user,
                "dbname": config.dbname,
                "password": config.password,
                "sslmode": config.sslmode,
                "application_name": config.application_name,
                "client_encoding": config.client_encoding,
                "service": config.service,
            }
        return configurations
mandant: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
output_format: str = field(metadata={'type': 'Element'}, default='json') class-attribute instance-attribute
pg_service_configs: list[PgServiceConf] = field(metadata={'type': 'Element'}, default_factory=list) class-attribute instance-attribute
pg_service_configs_dict: dict property
project: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
unify_layer_names_by_group: bool = field(metadata={'type': 'Element'}, default=False) class-attribute instance-attribute
__init__(mandant: str, project: str, unify_layer_names_by_group: bool = False, output_format: str = 'json', pg_service_configs: list[PgServiceConf] = list()) -> None
ExportResult dataclass

Bases: BaseInterface

The serializable response which is provided by the exporter service.

Source code in src/qgis_server_light/interface/exporter/api.py
42
43
44
45
46
47
48
@dataclass(repr=False)
class ExportResult(BaseInterface):
    """
    The serializable response which is provided by the exporter service.
    """

    successful: bool = field(metadata={"type": "Element"})
successful: bool = field(metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(successful: bool) -> None

extract

This module contains all interface definition to translate from QGIS project to QGIS-Server-Light logic and to write the JSON export of the QGIS project

AbstractDataset dataclass

Bases: LayerLike

Source code in src/qgis_server_light/interface/exporter/extract.py
92
93
94
@dataclass(repr=False)
class AbstractDataset(LayerLike):
    title: str = field(metadata={"type": "Element"})
title: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(id: str, name: str, title: str) -> None
Config dataclass

Bases: BaseInterface

Source code in src/qgis_server_light/interface/exporter/extract.py
635
636
637
638
639
640
@dataclass(repr=False)
class Config(BaseInterface):
    project: Project = field(metadata={"type": "Element"})
    meta_data: MetaData = field(metadata={"type": "Element"})
    tree: Tree = field(metadata={"type": "Element"})
    datasets: Datasets = field(metadata={"type": "Element"})
datasets: Datasets = field(metadata={'type': 'Element'}) class-attribute instance-attribute
meta_data: MetaData = field(metadata={'type': 'Element'}) class-attribute instance-attribute
project: Project = field(metadata={'type': 'Element'}) class-attribute instance-attribute
tree: Tree = field(metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(project: Project, meta_data: MetaData, tree: Tree, datasets: Datasets) -> None
Crs dataclass

Bases: BaseInterface

Source code in src/qgis_server_light/interface/exporter/extract.py
 97
 98
 99
100
101
102
103
104
105
@dataclass(repr=False)
class Crs(BaseInterface):
    auth_id: str = field(default=None, metadata={"type": "Element"})
    postgis_srid: int = field(
        default=None,
        metadata={"type": "Element"},
    )
    ogc_uri: str = field(default=None, metadata={"type": "Element"})
    ogc_urn: str = field(default=None, metadata={"type": "Element"})
auth_id: str = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
ogc_uri: str = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
ogc_urn: str = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
postgis_srid: int = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(auth_id: str = None, postgis_srid: int = None, ogc_uri: str = None, ogc_urn: str = None) -> None
Custom dataclass

Bases: DataSet

Source code in src/qgis_server_light/interface/exporter/extract.py
523
524
525
@dataclass(repr=False)
class Custom(DataSet):
    pass
__init__(id: str, name: str, title: str, source: DataSource, driver: str, bbox: BBox | None = None, bbox_wgs84: BBox | None = None, crs: Crs | None = None, styles: List[Style] = list(), minimum_scale: float | None = None, maximum_scale: float | None = None, style_name: str = 'default', is_spatial: bool = True) -> None
DataSet dataclass

Bases: AbstractDataset

Source code in src/qgis_server_light/interface/exporter/extract.py
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
@dataclass(repr=False)
class DataSet(AbstractDataset):
    source: DataSource = field(metadata={"type": "Element"})
    driver: str = field(metadata={"type": "Element"})
    bbox: BBox | None = field(default=None, metadata={"type": "Element"})
    bbox_wgs84: BBox | None = field(default=None, metadata={"type": "Element"})
    crs: Crs | None = field(default=None, metadata={"type": "Element"})
    styles: List[Style] = field(default_factory=list, metadata={"type": "Element"})
    minimum_scale: float | None = field(default=None, metadata={"type": "Element"})
    maximum_scale: float | None = field(default=None, metadata={"type": "Element"})
    style_name: str = field(default="default", metadata={"type": "Element"})
    is_spatial: bool = field(default=True, metadata={"type": "Element"})

    def get_style_by_name(self, name: str) -> Style | None:
        for style in self.styles:
            if name == style.name:
                return style
        return None

    def style(self) -> Style | None:
        return self.get_style_by_name(self.style_name)
bbox: BBox | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
bbox_wgs84: BBox | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
crs: Crs | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
driver: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
is_spatial: bool = field(default=True, metadata={'type': 'Element'}) class-attribute instance-attribute
maximum_scale: float | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
minimum_scale: float | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
source: DataSource = field(metadata={'type': 'Element'}) class-attribute instance-attribute
style_name: str = field(default='default', metadata={'type': 'Element'}) class-attribute instance-attribute
styles: List[Style] = field(default_factory=list, metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(id: str, name: str, title: str, source: DataSource, driver: str, bbox: BBox | None = None, bbox_wgs84: BBox | None = None, crs: Crs | None = None, styles: List[Style] = list(), minimum_scale: float | None = None, maximum_scale: float | None = None, style_name: str = 'default', is_spatial: bool = True) -> None
get_style_by_name(name: str) -> Style | None
Source code in src/qgis_server_light/interface/exporter/extract.py
478
479
480
481
482
def get_style_by_name(self, name: str) -> Style | None:
    for style in self.styles:
        if name == style.name:
            return style
    return None
style() -> Style | None
Source code in src/qgis_server_light/interface/exporter/extract.py
484
485
def style(self) -> Style | None:
    return self.get_style_by_name(self.style_name)
DataSource dataclass

Bases: BaseInterface

Source code in src/qgis_server_light/interface/exporter/extract.py
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
@dataclass(repr=False)
class DataSource(BaseInterface):
    postgres: PostgresSource | None = field(default=None, metadata={"type": "Element"})
    wmts: WmtsSource | None = field(default=None, metadata={"type": "Element"})
    wms: WmsSource | None = field(default=None, metadata={"type": "Element"})
    ogr: OgrSource | None = field(default=None, metadata={"type": "Element"})
    gdal: GdalSource | None = field(default=None, metadata={"type": "Element"})
    wfs: WfsSource | None = field(default=None, metadata={"type": "Element"})
    vector_tile: VectorTileSource | None = field(
        default=None, metadata={"type": "Element"}
    )
    xyz: XYZSource | None = field(default=None, metadata={"type": "Element"})

    @property
    def definition(
        self,
    ) -> (
        PostgresSource
        | WmtsSource
        | WmsSource
        | OgrSource
        | GdalSource
        | WfsSource
        | VectorTileSource
        | XYZSource
        | None
    ):
        for dataclass_field in fields(self):
            name = dataclass_field.name
            value = getattr(self, name)
            if value:
                return value
        logging.error(
            f"No source was definied at {self.__class__.__name__}, this is not expected"
        )
        return None
definition: PostgresSource | WmtsSource | WmsSource | OgrSource | GdalSource | WfsSource | VectorTileSource | XYZSource | None property
gdal: GdalSource | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
ogr: OgrSource | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
postgres: PostgresSource | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
vector_tile: VectorTileSource | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
wfs: WfsSource | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
wms: WmsSource | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
wmts: WmtsSource | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
xyz: XYZSource | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(postgres: PostgresSource | None = None, wmts: WmtsSource | None = None, wms: WmsSource | None = None, ogr: OgrSource | None = None, gdal: GdalSource | None = None, wfs: WfsSource | None = None, vector_tile: VectorTileSource | None = None, xyz: XYZSource | None = None) -> None
Datasets dataclass

Bases: BaseInterface

Source code in src/qgis_server_light/interface/exporter/extract.py
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
@dataclass(repr=False)
class Datasets(BaseInterface):
    vector: list[Vector] = field(
        default_factory=list,
        metadata={"type": "Element"},
    )
    raster: list[Raster] = field(
        default_factory=list,
        metadata={"type": "Element"},
    )
    custom: list[Custom] = field(
        default_factory=list,
        metadata={"type": "Element"},
    )
    group: list[Group] = field(
        default_factory=list,
        metadata={"type": "Element"},
    )
custom: list[Custom] = field(default_factory=list, metadata={'type': 'Element'}) class-attribute instance-attribute
group: list[Group] = field(default_factory=list, metadata={'type': 'Element'}) class-attribute instance-attribute
raster: list[Raster] = field(default_factory=list, metadata={'type': 'Element'}) class-attribute instance-attribute
vector: list[Vector] = field(default_factory=list, metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(vector: list[Vector] = list(), raster: list[Raster] = list(), custom: list[Custom] = list(), group: list[Group] = list()) -> None
Field dataclass

Bases: BaseInterface

Transportable (serializable) form of a QGIS vector job_layer_definition fiel (attribute). It contains the information of the original data datatype and its translated versions and the editor widget one as well.

Attributes:

Source code in src/qgis_server_light/interface/exporter/extract.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
@dataclass(repr=False)
class Field(BaseInterface):
    """
    Transportable (serializable) form of a QGIS vector job_layer_definition fiel (attribute). It contains the information of
    the original data datatype and its translated versions and the editor widget one as well.

    Attributes:
        name: Machine readable name of the field
        type: Original type as defined by data source (PostGIS, GPKG, etc.)
        is_primary_key: if the field is considered to be primary key.
        type_wfs: Translated type for further usage. Based on the simple types of
            [XSD spec](https://www.w3.org/TR/xmlschema11-2/#built-in-primitive-datatypes).
        type_oapif: Translated type based on the types of the
            [OpenAPI Spec](https://spec.openapis.org/oas/latest.html#data-types)
        type_oapif_format: Format of the above-mentioned type based on the
            [OpenAPI Spec](https://spec.openapis.org/oas/latest.html#data-types)
        alias: Human readable name.
        comment: Field description.
        nullable: If this field can be NULL or not.
        length: The limitation in length on the field value.
        precision: The precision of the field value (float types)
        editor_widget_type: The original type how it is defined in the QGIS form.
        editor_widget_type_wfs: The translated type based on the simple types of
            [XSD spec](https://www.w3.org/TR/xmlschema11-2/#built-in-primitive-datatypes).
        editor_widget_type_oapif: Translated type based on the types of the
            [OpenAPI Spec](https://spec.openapis.org/oas/latest.html#data-types)
        editor_widget_type_oapif_format: Format of the above-mentioned type based on the
            [OpenAPI Spec](https://spec.openapis.org/oas/latest.html#data-types)
    """

    name: str = field(metadata={"type": "Element"})
    type: str = field(metadata={"type": "Element"})
    is_primary_key: bool = field(
        default=False,
        metadata={"type": "Element"},
    )
    type_wfs: Optional[str] = field(default=None, metadata={"type": "Element"})
    type_oapif: Optional[str] = field(default=None, metadata={"type": "Element"})
    type_oapif_format: Optional[str] = field(default=None, metadata={"type": "Element"})
    alias: Optional[str] = field(default=None, metadata={"type": "Element"})
    comment: Optional[str] = field(default=None, metadata={"type": "Element"})
    nullable: bool = field(default=True, metadata={"type": "Element"})
    length: Optional[int] = field(default=None, metadata={"type": "Element"})
    precision: Optional[int] = field(default=None, metadata={"type": "Element"})
    editor_widget_type: Optional[str] = field(
        default=None, metadata={"type": "Element"}
    )
    editor_widget_type_wfs: Optional[str] = field(
        default=None, metadata={"type": "Element"}
    )
    editor_widget_type_oapif: Optional[str] = field(
        default=None, metadata={"type": "Element"}
    )
    editor_widget_type_oapif_format: Optional[str] = field(
        default=None, metadata={"type": "Element"}
    )
alias: Optional[str] = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
comment: Optional[str] = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
editor_widget_type: Optional[str] = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
editor_widget_type_oapif: Optional[str] = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
editor_widget_type_oapif_format: Optional[str] = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
editor_widget_type_wfs: Optional[str] = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
is_primary_key: bool = field(default=False, metadata={'type': 'Element'}) class-attribute instance-attribute
length: Optional[int] = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
name: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
nullable: bool = field(default=True, metadata={'type': 'Element'}) class-attribute instance-attribute
precision: Optional[int] = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
type: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
type_oapif: Optional[str] = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
type_oapif_format: Optional[str] = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
type_wfs: Optional[str] = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(name: str, type: str, is_primary_key: bool = False, type_wfs: Optional[str] = None, type_oapif: Optional[str] = None, type_oapif_format: Optional[str] = None, alias: Optional[str] = None, comment: Optional[str] = None, nullable: bool = True, length: Optional[int] = None, precision: Optional[int] = None, editor_widget_type: Optional[str] = None, editor_widget_type_wfs: Optional[str] = None, editor_widget_type_oapif: Optional[str] = None, editor_widget_type_oapif_format: Optional[str] = None) -> None
GdalSource dataclass

Bases: Source

Source code in src/qgis_server_light/interface/exporter/extract.py
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
@dataclass(repr=False)
class GdalSource(Source):
    path: str = field(metadata={"type": "Element"})
    layer_name: str | None = field(default=None, metadata={"type": "Element"})
    vsi_prefix: str | None = field(default=None, metadata={"type": "Element"})

    @property
    def remote(self):
        return self.decide_remote(self.path)

    @property
    def to_qgis_decoded_uri(self) -> dict:
        connection_dict = {"path": self.path}
        if self.layer_name is not None:
            connection_dict["layerName"] = self.layer_name
        if self.vsi_prefix is not None:
            connection_dict["vsiPrefix"] = self.vsi_prefix
        return connection_dict

    @classmethod
    def from_qgis_decoded_uri(cls, decoded_uri: dict):
        return cls(
            path=decoded_uri["path"],
            layer_name=decoded_uri.get("layerName"),
            vsi_prefix=decoded_uri.get("vsiPrefix"),
        )
layer_name: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
path: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
remote property
to_qgis_decoded_uri: dict property
vsi_prefix: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(path: str, layer_name: str | None = None, vsi_prefix: str | None = None) -> None
from_qgis_decoded_uri(decoded_uri: dict) classmethod
Source code in src/qgis_server_light/interface/exporter/extract.py
146
147
148
149
150
151
152
@classmethod
def from_qgis_decoded_uri(cls, decoded_uri: dict):
    return cls(
        path=decoded_uri["path"],
        layer_name=decoded_uri.get("layerName"),
        vsi_prefix=decoded_uri.get("vsiPrefix"),
    )
Group dataclass

Bases: AbstractDataset

Source code in src/qgis_server_light/interface/exporter/extract.py
528
529
530
@dataclass(repr=False)
class Group(AbstractDataset):
    pass
__init__(id: str, name: str, title: str) -> None
LayerLike dataclass

Bases: BaseInterface

Source code in src/qgis_server_light/interface/exporter/extract.py
15
16
17
18
@dataclass(repr=False)
class LayerLike(BaseInterface):
    id: str = field(metadata={"type": "Element"})
    name: str = field(metadata={"type": "Element"})
id: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
name: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(id: str, name: str) -> None
MetaData dataclass

Bases: BaseInterface

Source code in src/qgis_server_light/interface/exporter/extract.py
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
@dataclass(repr=False)
class MetaData(BaseInterface):
    service: Service = field(metadata={"type": "Element"})
    links: Optional[List[str]] = field(
        default_factory=list,
        metadata={"type": "Element"},
    )
    language: Optional[str] = field(
        default=None,
        metadata={"type": "Element"},
    )
    categories: Optional[List[str]] = field(
        default_factory=list,
        metadata={"type": "Element"},
    )
    creationDateTime: str = field(
        default=None,
        metadata={"type": "Element"},
    )
    author: Optional[str] = field(default=None, metadata={"type": "Element"})

    def __post_init__(self):
        if self.creationDateTime is None:
            self.creationDateTime = datetime.now(UTC).isoformat()
author: Optional[str] = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
categories: Optional[List[str]] = field(default_factory=list, metadata={'type': 'Element'}) class-attribute instance-attribute
creationDateTime: str = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
language: Optional[str] = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
service: Service = field(metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(service: Service, links: Optional[List[str]] = list(), language: Optional[str] = None, categories: Optional[List[str]] = list(), creationDateTime: str = None, author: Optional[str] = None) -> None
__post_init__()
Source code in src/qgis_server_light/interface/exporter/extract.py
590
591
592
def __post_init__(self):
    if self.creationDateTime is None:
        self.creationDateTime = datetime.now(UTC).isoformat()
OgrSource dataclass

Bases: GdalSource

Source code in src/qgis_server_light/interface/exporter/extract.py
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
@dataclass(repr=False)
class OgrSource(GdalSource):
    layer_id: str | None = field(default=None, metadata={"type": "Element"})
    subset: str | None = field(default=None, metadata={"type": "Element"})

    @property
    def to_qgis_decoded_uri(self) -> dict:
        connection_dict = super().to_qgis_decoded_uri
        if self.layer_id:
            connection_dict["layerId"] = self.layer_id
        if self.subset:
            connection_dict["subset"] = self.subset
        return connection_dict

    @classmethod
    def from_qgis_decoded_uri(cls, decoded_uri: dict):
        base_class_instance = GdalSource.from_qgis_decoded_uri(decoded_uri)
        return cls(
            path=base_class_instance.path,
            layer_name=base_class_instance.layer_name,
            vsi_prefix=base_class_instance.vsi_prefix,
            layer_id=decoded_uri.get("layerId"),
            subset=decoded_uri.get("subset"),
        )

    @property
    def encoded_uri_separator(self) -> str:
        return "|"
encoded_uri_separator: str property
layer_id: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
subset: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
to_qgis_decoded_uri: dict property
__init__(path: str, layer_name: str | None = None, vsi_prefix: str | None = None, layer_id: str | None = None, subset: str | None = None) -> None
from_qgis_decoded_uri(decoded_uri: dict) classmethod
Source code in src/qgis_server_light/interface/exporter/extract.py
169
170
171
172
173
174
175
176
177
178
@classmethod
def from_qgis_decoded_uri(cls, decoded_uri: dict):
    base_class_instance = GdalSource.from_qgis_decoded_uri(decoded_uri)
    return cls(
        path=base_class_instance.path,
        layer_name=base_class_instance.layer_name,
        vsi_prefix=base_class_instance.vsi_prefix,
        layer_id=decoded_uri.get("layerId"),
        subset=decoded_uri.get("subset"),
    )
PostgresSource dataclass

Bases: Source

Source code in src/qgis_server_light/interface/exporter/extract.py
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
@dataclass(repr=False)
class PostgresSource(Source):
    key: str = field(metadata={"type": "Element"})
    schema: str = field(metadata={"type": "Element"})
    table: str = field(metadata={"type": "Element"})
    geometry_column: str | None = field(default=None, metadata={"type": "Element"})
    dbname: str | None = field(default=None, metadata={"type": "Element"})
    host: str | None = field(default=None, metadata={"type": "Element"})
    password: str | None = field(default=None, metadata={"type": "Element"})
    port: int | None = field(default=None, metadata={"type": "Element"})
    type: int | None = field(default=None, metadata={"type": "Element"})
    username: str | None = field(default=None, metadata={"type": "Element"})
    srid: str | None = field(default=None, metadata={"type": "Element"})
    sslmode: int | None = field(default=None, metadata={"type": "Element"})
    ssl_mode_text: str | None = field(default=None, metadata={"type": "Element"})
    service: str | None = field(default=None, metadata={"type": "Element"})
    check_primary_key_unicity: str | None = field(
        default=None, metadata={"type": "Element"}
    )
    sql: str | None = field(default=None, metadata={"type": "Element"})

    @property
    def redacted_fields(self) -> set:
        return {"password"}

    @property
    def to_qgis_decoded_uri(self) -> dict:
        connection_dict = {"key": self.key, "schema": self.schema, "table": self.table}
        if self.geometry_column is not None:
            connection_dict["geometrycolumn"] = self.geometry_column
        if self.dbname is not None:
            connection_dict["dbname"] = self.dbname
        if self.host is not None:
            connection_dict["host"] = self.host
        if self.password is not None:
            connection_dict["password"] = self.password
        if self.port is not None:
            connection_dict["port"] = self.port
        if self.type is not None:
            connection_dict["type"] = self.type
        if self.username is not None:
            connection_dict["username"] = self.username
        if self.srid is not None:
            connection_dict["srid"] = self.srid
        if self.sslmode is not None:
            connection_dict["sslmode"] = self.sslmode
        if self.service is not None:
            connection_dict["service"] = self.service
        if self.check_primary_key_unicity is not None:
            connection_dict["checkPrimaryKeyUnicity"] = self.check_primary_key_unicity
        if self.sql is not None:
            connection_dict["sql"] = self.sql
        return connection_dict

    @classmethod
    def from_qgis_decoded_uri(cls, decoded_uri: dict):
        return cls(
            key=decoded_uri["key"],
            schema=decoded_uri["schema"],
            table=decoded_uri["table"],
            geometry_column=decoded_uri.get("geometrycolumn"),
            dbname=decoded_uri.get("dbname"),
            host=decoded_uri.get("host"),
            password=decoded_uri.get("password"),
            port=int(decoded_uri.get("port"))
            if decoded_uri.get("port") is not None
            else None,
            type=int(decoded_uri.get("type"))
            if decoded_uri.get("type") is not None
            else None,
            username=decoded_uri.get("username"),
            srid=decoded_uri.get("srid"),
            sslmode=int(decoded_uri.get("sslmode", 2)),
            service=decoded_uri.get("service"),
            check_primary_key_unicity=decoded_uri.get("check_primary_key_unicity"),
            sql=decoded_uri.get("sql"),
        )

    @property
    def remote(self):
        return True
check_primary_key_unicity: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
dbname: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
geometry_column: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
host: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
key: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
password: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
port: int | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
redacted_fields: set property
remote property
schema: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
service: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
sql: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
srid: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
ssl_mode_text: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
sslmode: int | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
table: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
to_qgis_decoded_uri: dict property
type: int | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
username: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(key: str, schema: str, table: str, geometry_column: str | None = None, dbname: str | None = None, host: str | None = None, password: str | None = None, port: int | None = None, type: int | None = None, username: str | None = None, srid: str | None = None, sslmode: int | None = None, ssl_mode_text: str | None = None, service: str | None = None, check_primary_key_unicity: str | None = None, sql: str | None = None) -> None
from_qgis_decoded_uri(decoded_uri: dict) classmethod
Source code in src/qgis_server_light/interface/exporter/extract.py
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
@classmethod
def from_qgis_decoded_uri(cls, decoded_uri: dict):
    return cls(
        key=decoded_uri["key"],
        schema=decoded_uri["schema"],
        table=decoded_uri["table"],
        geometry_column=decoded_uri.get("geometrycolumn"),
        dbname=decoded_uri.get("dbname"),
        host=decoded_uri.get("host"),
        password=decoded_uri.get("password"),
        port=int(decoded_uri.get("port"))
        if decoded_uri.get("port") is not None
        else None,
        type=int(decoded_uri.get("type"))
        if decoded_uri.get("type") is not None
        else None,
        username=decoded_uri.get("username"),
        srid=decoded_uri.get("srid"),
        sslmode=int(decoded_uri.get("sslmode", 2)),
        service=decoded_uri.get("service"),
        check_primary_key_unicity=decoded_uri.get("check_primary_key_unicity"),
        sql=decoded_uri.get("sql"),
    )
Project dataclass

Bases: BaseInterface

Source code in src/qgis_server_light/interface/exporter/extract.py
595
596
597
598
@dataclass(repr=False)
class Project(BaseInterface):
    version: str = field(metadata={"type": "Element"})
    name: str = field(metadata={"type": "Element"})
name: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
version: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(version: str, name: str) -> None
Raster dataclass

Bases: DataSet

A real QGIS Raster job_layer_definition. That are usually all QgsRasterLayer in opposition to QgsVectorTileLayer which is not a real QgsRasterLayer.

Source code in src/qgis_server_light/interface/exporter/extract.py
488
489
490
491
492
493
@dataclass(repr=False)
class Raster(DataSet):
    """
    A real QGIS Raster job_layer_definition. That are usually all `QgsRasterLayer` in opposition to `QgsVectorTileLayer`
    which is not a real `QgsRasterLayer`.
    """
__init__(id: str, name: str, title: str, source: DataSource, driver: str, bbox: BBox | None = None, bbox_wgs84: BBox | None = None, crs: Crs | None = None, styles: List[Style] = list(), minimum_scale: float | None = None, maximum_scale: float | None = None, style_name: str = 'default', is_spatial: bool = True) -> None
Service dataclass

Bases: BaseInterface

Source code in src/qgis_server_light/interface/exporter/extract.py
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
@dataclass(repr=False)
class Service(BaseInterface):
    contact_organization: Optional[str] = field(metadata={"type": "Element"})
    contact_mail: Optional[str] = field(metadata={"type": "Element"})
    contact_person: Optional[str] = field(
        default=None,
        metadata={"type": "Element"},
    )
    contact_phone: Optional[str] = field(
        default=None,
        metadata={"type": "Element"},
    )
    contact_position: Optional[str] = field(
        default=None,
        metadata={"type": "Element"},
    )
    fees: Optional[str] = field(default=None, metadata={"type": "Element"})
    keyword_list: Optional[str] = field(
        default=None,
        metadata={"type": "Element"},
    )
    online_resource: Optional[str] = field(
        default=None,
        metadata={"type": "Element"},
    )
    service_abstract: Optional[str] = field(
        default=None,
        metadata={"type": "Element"},
    )
    service_title: Optional[str] = field(
        default=None,
        metadata={"type": "Element"},
    )
    resource_url: Optional[str] = field(default=None, metadata={"type": "Element"})
contact_mail: Optional[str] = field(metadata={'type': 'Element'}) class-attribute instance-attribute
contact_organization: Optional[str] = field(metadata={'type': 'Element'}) class-attribute instance-attribute
contact_person: Optional[str] = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
contact_phone: Optional[str] = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
contact_position: Optional[str] = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
fees: Optional[str] = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
keyword_list: Optional[str] = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
online_resource: Optional[str] = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
resource_url: Optional[str] = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
service_abstract: Optional[str] = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
service_title: Optional[str] = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(contact_organization: Optional[str], contact_mail: Optional[str], contact_person: Optional[str] = None, contact_phone: Optional[str] = None, contact_position: Optional[str] = None, fees: Optional[str] = None, keyword_list: Optional[str] = None, online_resource: Optional[str] = None, service_abstract: Optional[str] = None, service_title: Optional[str] = None, resource_url: Optional[str] = None) -> None
Source dataclass

Bases: BaseInterface, ABC

Source code in src/qgis_server_light/interface/exporter/extract.py
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
@dataclass(repr=False)
class Source(BaseInterface, ABC):
    @staticmethod
    def decide_remote(path: str) -> bool:
        return path.startswith("http")

    @property
    def to_qgis_decoded_uri(self) -> dict:
        raise NotImplementedError(
            "This is a base class, the method has to be defined at implementation level."
        )

    @classmethod
    def from_qgis_decoded_uri(cls, decoded_uri: dict):
        raise NotImplementedError(
            "This is a base class, the method has to be defined at implementation level."
        )
to_qgis_decoded_uri: dict property
__init__() -> None
decide_remote(path: str) -> bool staticmethod
Source code in src/qgis_server_light/interface/exporter/extract.py
110
111
112
@staticmethod
def decide_remote(path: str) -> bool:
    return path.startswith("http")
from_qgis_decoded_uri(decoded_uri: dict) classmethod
Source code in src/qgis_server_light/interface/exporter/extract.py
120
121
122
123
124
@classmethod
def from_qgis_decoded_uri(cls, decoded_uri: dict):
    raise NotImplementedError(
        "This is a base class, the method has to be defined at implementation level."
    )
Tree dataclass

Bases: BaseInterface

Source code in src/qgis_server_light/interface/exporter/extract.py
601
602
603
604
605
606
607
608
609
610
611
612
@dataclass(repr=False)
class Tree(BaseInterface):
    members: list[TreeGroup] = field(
        default_factory=list,
        metadata={"type": "Element"},
    )

    def find_by_name(self, name: str) -> TreeGroup | None:
        for member in self.members:
            if member.name == name:
                return member
        return None
members: list[TreeGroup] = field(default_factory=list, metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(members: list[TreeGroup] = list()) -> None
find_by_name(name: str) -> TreeGroup | None
Source code in src/qgis_server_light/interface/exporter/extract.py
608
609
610
611
612
def find_by_name(self, name: str) -> TreeGroup | None:
    for member in self.members:
        if member.name == name:
            return member
    return None
TreeGroup dataclass

Bases: TreeLayer

Source code in src/qgis_server_light/interface/exporter/extract.py
26
27
28
29
30
31
@dataclass(repr=False)
class TreeGroup(TreeLayer):
    children: List[str] = field(
        default_factory=list,
        metadata={"type": "Element"},
    )
children: List[str] = field(default_factory=list, metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(id: str, name: str, children: List[str] = list()) -> None
TreeLayer dataclass

Bases: LayerLike

Source code in src/qgis_server_light/interface/exporter/extract.py
21
22
23
@dataclass(repr=False)
class TreeLayer(LayerLike):
    pass
__init__(id: str, name: str) -> None
Vector dataclass

Bases: DataSet

A real QGIS Vector job_layer_definition. That are usually all QgsVectorLayer in opposition to QgsVectorTileLayer which is not a real QgsVectorLayer.

Source code in src/qgis_server_light/interface/exporter/extract.py
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
@dataclass(repr=False)
class Vector(DataSet):
    """
    A real QGIS Vector job_layer_definition. That are usually all `QgsVectorLayer` in opposition to `QgsVectorTileLayer`
    which is not a real `QgsVectorLayer`.
    """

    fields: Optional[List[Field]] = field(
        default_factory=list,
        metadata={"type": "Element"},
    )
    geometry_type_simple: Optional[str] = field(
        default=None,
        metadata={"type": "Element"},
    )
    geometry_type_wkb: Optional[str] = field(
        default=None,
        metadata={"type": "Element"},
    )

    def get_field_by_name(self, name: str) -> Field | None:
        for dataclass_field in self.fields:
            if dataclass_field.name == name:
                return dataclass_field
        return None
fields: Optional[List[Field]] = field(default_factory=list, metadata={'type': 'Element'}) class-attribute instance-attribute
geometry_type_simple: Optional[str] = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
geometry_type_wkb: Optional[str] = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(id: str, name: str, title: str, source: DataSource, driver: str, bbox: BBox | None = None, bbox_wgs84: BBox | None = None, crs: Crs | None = None, styles: List[Style] = list(), minimum_scale: float | None = None, maximum_scale: float | None = None, style_name: str = 'default', is_spatial: bool = True, fields: Optional[List[Field]] = list(), geometry_type_simple: Optional[str] = None, geometry_type_wkb: Optional[str] = None) -> None
get_field_by_name(name: str) -> Field | None
Source code in src/qgis_server_light/interface/exporter/extract.py
516
517
518
519
520
def get_field_by_name(self, name: str) -> Field | None:
    for dataclass_field in self.fields:
        if dataclass_field.name == name:
            return dataclass_field
    return None
VectorTileSource dataclass

Bases: Source

Source code in src/qgis_server_light/interface/exporter/extract.py
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
@dataclass(repr=False)
class VectorTileSource(Source):
    type: str = field(metadata={"type": "Element"})
    zmin: int | None = field(metadata={"type": "Element"})
    zmax: int | None = field(metadata={"type": "Element"})
    url: str | None = field(default=None, metadata={"type": "Element"})
    path: str | None = field(default=None, metadata={"type": "Element"})
    style_url: str | None = field(default=None, metadata={"type": "Element"})

    @property
    def remote(self):
        return self.decide_remote(self.url)

    @property
    def to_qgis_decoded_uri(self) -> dict:
        connection_dict = {"type": self.type, "zmin": self.zmin, "zmax": self.zmax}
        if self.url is not None:
            connection_dict["url"] = self.url
        if self.path is not None:
            connection_dict["path"] = self.path
        if self.style_url is not None:
            connection_dict["styleUrl"] = self.style_url
        return connection_dict

    @classmethod
    def from_qgis_decoded_uri(cls, decoded_uri: dict):
        return cls(
            type=decoded_uri["type"],
            zmin=decoded_uri.get("zmin"),
            zmax=decoded_uri.get("zmax"),
            url=decoded_uri.get("url"),
            path=decoded_uri.get("path"),
            style_url=decoded_uri.get("styleUrl"),
        )
path: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
remote property
style_url: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
to_qgis_decoded_uri: dict property
type: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
url: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
zmax: int | None = field(metadata={'type': 'Element'}) class-attribute instance-attribute
zmin: int | None = field(metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(type: str, zmin: int | None, zmax: int | None, url: str | None = None, path: str | None = None, style_url: str | None = None) -> None
from_qgis_decoded_uri(decoded_uri: dict) classmethod
Source code in src/qgis_server_light/interface/exporter/extract.py
415
416
417
418
419
420
421
422
423
424
@classmethod
def from_qgis_decoded_uri(cls, decoded_uri: dict):
    return cls(
        type=decoded_uri["type"],
        zmin=decoded_uri.get("zmin"),
        zmax=decoded_uri.get("zmax"),
        url=decoded_uri.get("url"),
        path=decoded_uri.get("path"),
        style_url=decoded_uri.get("styleUrl"),
    )
WfsSource dataclass

Bases: BaseInterface

Source code in src/qgis_server_light/interface/exporter/extract.py
185
186
187
188
189
@dataclass(repr=False)
class WfsSource(BaseInterface):
    # currently not implemented because qgis does not allow to
    # use the decode uri approach on that URI
    pass
__init__() -> None
WmsSource dataclass

Bases: Source

Source code in src/qgis_server_light/interface/exporter/extract.py
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
@dataclass(repr=False)
class WmsSource(Source):
    crs: str = field(metadata={"type": "Element"})
    format: str = field(metadata={"type": "Element"})
    layers: str = field(metadata={"type": "Element"})
    url: str = field(metadata={"type": "Element"})
    dpi_mode: str | None = field(default=None, metadata={"type": "Element"})
    feature_count: int | None = field(default=None, metadata={"type": "Element"})
    contextual_wms_legend: str | None = field(
        default=None, metadata={"type": "Element"}
    )
    styles: str | None = field(default=None, metadata={"type": "Element"})

    @property
    def to_qgis_decoded_uri(self) -> dict:
        connection_dict = {
            "crs": self.crs,
            "format": self.format,
            "layers": self.layers,
            "url": self.url,
            "styles": self.styles,
        }
        if self.dpi_mode is not None:
            connection_dict["dpiMode"] = self.dpi_mode
        if self.feature_count is not None:
            connection_dict["featureCount"] = self.feature_count
        if self.contextual_wms_legend is not None:
            connection_dict["contextualWMSLegend"] = self.contextual_wms_legend
        return connection_dict

    @classmethod
    def from_qgis_decoded_uri(cls, decoded_uri: dict):
        return cls(
            crs=decoded_uri["crs"],
            format=decoded_uri["format"],
            layers=decoded_uri["layers"],
            url=decoded_uri["url"],
            dpi_mode=decoded_uri.get("dpiMode"),
            feature_count=decoded_uri.get("featureCount"),
            contextual_wms_legend=decoded_uri.get("contextualWMSLegend"),
            styles=decoded_uri.get("styles"),
        )

    @property
    def remote(self):
        return True
contextual_wms_legend: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
crs: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
dpi_mode: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
feature_count: int | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
format: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
layers: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
remote property
styles: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
to_qgis_decoded_uri: dict property
url: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(crs: str, format: str, layers: str, url: str, dpi_mode: str | None = None, feature_count: int | None = None, contextual_wms_legend: str | None = None, styles: str | None = None) -> None
from_qgis_decoded_uri(decoded_uri: dict) classmethod
Source code in src/qgis_server_light/interface/exporter/extract.py
253
254
255
256
257
258
259
260
261
262
263
264
@classmethod
def from_qgis_decoded_uri(cls, decoded_uri: dict):
    return cls(
        crs=decoded_uri["crs"],
        format=decoded_uri["format"],
        layers=decoded_uri["layers"],
        url=decoded_uri["url"],
        dpi_mode=decoded_uri.get("dpiMode"),
        feature_count=decoded_uri.get("featureCount"),
        contextual_wms_legend=decoded_uri.get("contextualWMSLegend"),
        styles=decoded_uri.get("styles"),
    )
WmtsSource dataclass

Bases: WmsSource

Source code in src/qgis_server_light/interface/exporter/extract.py
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
@dataclass(repr=False, kw_only=True)
class WmtsSource(WmsSource):
    tile_matrix_set: str = field(metadata={"type": "Element"})
    tile_dimensions: str | None = field(
        default=None,
        metadata={"type": "Element"},
    )
    tile_pixel_ratio: str | None = field(default=None, metadata={"type": "Element"})

    @property
    def to_qgis_decoded_uri(self) -> dict:
        connection_dict = super().to_qgis_decoded_uri
        connection_dict["tileMatrixSet"] = self.tile_matrix_set
        if self.tile_dimensions is not None:
            connection_dict["tileDimensions"] = self.tile_dimensions
        if self.tile_pixel_ratio is not None:
            connection_dict["tilePixelRatio"] = self.tile_pixel_ratio
        return connection_dict

    @classmethod
    def from_qgis_decoded_uri(cls, decoded_uri: dict):
        base_class_instance = WmsSource.from_qgis_decoded_uri(decoded_uri)
        return cls(
            crs=base_class_instance.crs,
            format=base_class_instance.format,
            layers=base_class_instance.layers,
            url=base_class_instance.url,
            dpi_mode=base_class_instance.dpi_mode,
            feature_count=base_class_instance.feature_count,
            contextual_wms_legend=base_class_instance.contextual_wms_legend,
            styles=base_class_instance.styles,
            tile_matrix_set=decoded_uri["tileMatrixSet"],
            tile_dimensions=decoded_uri.get("tileDimensions"),
            tile_pixel_ratio=decoded_uri.get("tilePixelRatio"),
        )
tile_dimensions: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
tile_matrix_set: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
tile_pixel_ratio: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
to_qgis_decoded_uri: dict property
__init__(crs: str, format: str, layers: str, url: str, dpi_mode: str | None = None, feature_count: int | None = None, contextual_wms_legend: str | None = None, styles: str | None = None, *, tile_matrix_set: str, tile_dimensions: str | None = None, tile_pixel_ratio: str | None = None) -> None
from_qgis_decoded_uri(decoded_uri: dict) classmethod
Source code in src/qgis_server_light/interface/exporter/extract.py
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
@classmethod
def from_qgis_decoded_uri(cls, decoded_uri: dict):
    base_class_instance = WmsSource.from_qgis_decoded_uri(decoded_uri)
    return cls(
        crs=base_class_instance.crs,
        format=base_class_instance.format,
        layers=base_class_instance.layers,
        url=base_class_instance.url,
        dpi_mode=base_class_instance.dpi_mode,
        feature_count=base_class_instance.feature_count,
        contextual_wms_legend=base_class_instance.contextual_wms_legend,
        styles=base_class_instance.styles,
        tile_matrix_set=decoded_uri["tileMatrixSet"],
        tile_dimensions=decoded_uri.get("tileDimensions"),
        tile_pixel_ratio=decoded_uri.get("tilePixelRatio"),
    )
XYZSource dataclass

Bases: Source

Source code in src/qgis_server_light/interface/exporter/extract.py
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
@dataclass(repr=False)
class XYZSource(Source):
    url: str = field(metadata={"type": "Element"})
    zmin: int | None = field(default=None, metadata={"type": "Element"})
    zmax: int | None = field(default=None, metadata={"type": "Element"})
    type: str | None = field(default=None, metadata={"type": "Element"})

    @property
    def to_qgis_decoded_uri(self) -> dict:
        connection_dict = {
            "url": self.url,
            "zmin": self.zmin,
            "zmax": self.zmax,
            "type": self.type,
        }
        return connection_dict

    @classmethod
    def from_qgis_decoded_uri(cls, decoded_uri: dict):
        return cls(
            url=decoded_uri["url"],
            zmin=decoded_uri.get("zmin"),
            zmax=decoded_uri.get("zmax"),
            type=decoded_uri.get("type"),
        )

    @property
    def remote(self):
        return self.decide_remote(self.url)
remote property
to_qgis_decoded_uri: dict property
type: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
url: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
zmax: int | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
zmin: int | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(url: str, zmin: int | None = None, zmax: int | None = None, type: str | None = None) -> None
from_qgis_decoded_uri(decoded_uri: dict) classmethod
Source code in src/qgis_server_light/interface/exporter/extract.py
209
210
211
212
213
214
215
216
@classmethod
def from_qgis_decoded_uri(cls, decoded_uri: dict):
    return cls(
        url=decoded_uri["url"],
        zmin=decoded_uri.get("zmin"),
        zmax=decoded_uri.get("zmax"),
        type=decoded_uri.get("type"),
    )

job

common

input
AbstractFilter dataclass

Bases: BaseInterface

Source code in src/qgis_server_light/interface/job/common/input.py
48
49
50
51
52
53
54
@dataclass(repr=False)
class AbstractFilter(BaseInterface):
    definition: str = field(metadata={"type": "Element"})

    @property
    def shortened_fields(self) -> set:
        return {"definition"}
definition: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
shortened_fields: set property
__init__(definition: str) -> None
OgcFilter110 dataclass

Bases: AbstractFilter

A filter which definition conforms to https://schemas.opengis.net/filter/1.1.0/filter.xsd and which is consumable by qgis.core.QgsOgcUtils.expressionFromOgcFilter.

Source code in src/qgis_server_light/interface/job/common/input.py
57
58
59
60
61
62
63
@dataclass(repr=False)
class OgcFilter110(AbstractFilter):
    """
    A filter which definition conforms to
    https://schemas.opengis.net/filter/1.1.0/filter.xsd
    and which is consumable by `qgis.core.QgsOgcUtils.expressionFromOgcFilter`.
    """
__init__(definition: str) -> None
OgcFilterFES20 dataclass

Bases: AbstractFilter

A filter which definition conforms to https://www.opengis.net/fes/2.0 and which is consumable by qgis.core.QgsOgcUtils.expressionFromOgcFilter.

Source code in src/qgis_server_light/interface/job/common/input.py
66
67
68
69
70
71
@dataclass(repr=False)
class OgcFilterFES20(AbstractFilter):
    """
    A filter which definition conforms to https://www.opengis.net/fes/2.0
    and which is consumable by `qgis.core.QgsOgcUtils.expressionFromOgcFilter`.
    """
__init__(definition: str) -> None
QslJobInfoParameter dataclass

Bases: ABC

The common minimal interface of a job which is shipped around. Each job for QSL has to implement at least this interface.

Attributes:

  • id (str) –

    The unique identifier which is used to recognize the job all over its lifecycle.

  • type (str) –

    A string based identifier of the job, this is used to quickly determine its nature serialized state.

  • job (QslJobParameter) –

    The actual job parameters. This is a domain specific dataclass depending on the nature of the actual job.

Source code in src/qgis_server_light/interface/job/common/input.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@dataclass
class QslJobInfoParameter(ABC):
    """The common minimal interface of a job which is
    shipped around. Each job for QSL has to implement at least this
    interface.

    Attributes:
        id: The unique identifier which is used to recognize the job
            all over its lifecycle.
        type: A string based identifier of the job, this is used to quickly
            determine its nature serialized state.
        job: The actual job parameters. This is a domain specific dataclass
            depending on the nature of the actual job.
    """

    id: str = field(metadata={"type": "Element"})
    type: str = field(metadata={"type": "Element"})
    job: QslJobParameter = field(metadata={"type": "Element"})
id: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
job: QslJobParameter = field(metadata={'type': 'Element'}) class-attribute instance-attribute
type: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(id: str, type: str, job: QslJobParameter) -> None
QslJobLayer dataclass

Bases: BaseInterface

Source code in src/qgis_server_light/interface/job/common/input.py
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
@dataclass(repr=False)
class QslJobLayer(BaseInterface):
    id: str = field(metadata={"type": "Element"})
    name: str = field(metadata={"type": "Element"})
    source: str = field(metadata={"type": "Element"})
    remote: bool = field(metadata={"type": "Element"})
    folder_name: str = field(metadata={"type": "Element"})
    driver: str = field(metadata={"type": "Element"})
    style: Style | None = field(default=None, metadata={"type": "Element"})
    filter: OgcFilter110 | OgcFilterFES20 | None = field(
        default=None, metadata={"type": "Element"}
    )

    @property
    def redacted_fields(self) -> set:
        return {"source"}
driver: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
filter: OgcFilter110 | OgcFilterFES20 | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
folder_name: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
id: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
name: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
redacted_fields: set property
remote: bool = field(metadata={'type': 'Element'}) class-attribute instance-attribute
source: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
style: Style | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(id: str, name: str, source: str, remote: bool, folder_name: str, driver: str, style: Style | None = None, filter: OgcFilter110 | OgcFilterFES20 | None = None) -> None
QslJobParameter dataclass

Bases: ABC

The minimal interface of a job parameter interface. In the domain specific refinement it holds the relevant information about a job.

Source code in src/qgis_server_light/interface/job/common/input.py
 7
 8
 9
10
11
12
13
@dataclass
class QslJobParameter(ABC):
    """The minimal interface of a job parameter interface. In the domain
    specific refinement it holds the relevant information about a job.
    """

    pass
__init__() -> None
QslJobParameterMapRelated dataclass

Bases: QslJobParameter

The minimal interface of a job parameter interface for jobs rendering things in the end.

Attributes:

  • svg_paths (list[str]) –

    A list of paths to svg's (folders) which are necessary for the job to render nicely.

Source code in src/qgis_server_light/interface/job/common/input.py
36
37
38
39
40
41
42
43
44
45
@dataclass
class QslJobParameterMapRelated(QslJobParameter):
    """The minimal interface of a job parameter interface for jobs rendering things in the end.

    Attributes:
        svg_paths: A list of paths to svg's (folders) which are necessary for
            the job to render nicely.
    """

    svg_paths: list[str] = field(default_factory=list, metadata={"type": "Element"})
svg_paths: list[str] = field(default_factory=list, metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(svg_paths: list[str] = list()) -> None
output
JobResult dataclass

Bases: BaseInterface

Source code in src/qgis_server_light/interface/job/common/output.py
 7
 8
 9
10
11
12
13
14
15
16
17
@dataclass
class JobResult(BaseInterface):
    id: str = field(metadata={"type": "Element"})
    data: Any = field(metadata={"type": "Element"})
    content_type: str = field(metadata={"type": "Element"})
    worker_id: str | None = field(default=None, metadata={"type": "Element"})
    worker_host_name: str | None = field(default=None, metadata={"type": "Element"})

    @property
    def shortened_fields(self) -> set:
        return {"data"}
content_type: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
data: Any = field(metadata={'type': 'Element'}) class-attribute instance-attribute
id: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
shortened_fields: set property
worker_host_name: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
worker_id: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(id: str, data: Any, content_type: str, worker_id: str | None = None, worker_host_name: str | None = None) -> None

feature

input
FeatureQuery dataclass

Bases: BaseInterface

Represents definitions of a query to obtain features from a list of layers. Be aware, that filters are not applied to the QslJobLayer in this implementation since passed filters can contain inter-layer-references.

Attributes:

  • layers (list[QslJobLayer]) –

    A list layers which should only reference vector sources and be queried.

  • aliases (list[str]) –

    An optional list of alias names. This has to be the same length as the list of datasets.

  • filter (OgcFilterFES20) –

    An optional filter which might reference all passed layers thats why layers has to be added

Source code in src/qgis_server_light/interface/job/feature/input.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@dataclass
class FeatureQuery(BaseInterface):
    """Represents definitions of a query to obtain features from a list of layers.
    Be aware, that filters are not applied to the QslJobLayer in this implementation
    since passed filters can contain inter-layer-references.

    Attributes:
        layers: A list layers which should only reference vector sources and be queried.
        aliases: An optional list of alias names. This has to be the same length as the list of datasets.
        filter: An optional filter which might reference all passed layers thats why layers
            has to be added
    """

    layers: list[QslJobLayer] = field(metadata={"type": "Element"})
    aliases: list[str] = field(default_factory=list, metadata={"type": "Element"})
    filter: OgcFilterFES20 = field(default=None, metadata={"type": "Element"})
aliases: list[str] = field(default_factory=list, metadata={'type': 'Element'}) class-attribute instance-attribute
filter: OgcFilterFES20 = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
layers: list[QslJobLayer] = field(metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(layers: list[QslJobLayer], aliases: list[str] = list(), filter: OgcFilterFES20 = None) -> None
QslJobInfoFeature dataclass

Bases: QslJobInfoParameter

Source code in src/qgis_server_light/interface/job/feature/input.py
56
57
58
@dataclass
class QslJobInfoFeature(QslJobInfoParameter):
    job: QslJobParameterFeature = field(metadata={"type": "Element"})
job: QslJobParameterFeature = field(metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(id: str, type: str, job: QslJobParameterFeature) -> None
QslJobParameterFeature dataclass

Bases: QslJobParameter

As defined in WFS 2.0 specs, a request can be subdivided in a list of queries. This class is representing that.

Attributes:

  • queries (list[FeatureQuery]) –

    A list of queries which features should be extracted for.

  • start_index (int) –

    The offset for paging reason.

  • count (int | None) –

    The number of results to return.

Source code in src/qgis_server_light/interface/job/feature/input.py
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@dataclass
class QslJobParameterFeature(QslJobParameter):
    """As defined in WFS 2.0 specs, a request can be subdivided in a list of queries.
    This class is representing that.

    Attributes:
        queries: A list of queries which features should be extracted for.
        start_index: The offset for paging reason.
        count: The number of results to return.
    """

    queries: list[FeatureQuery] = field(metadata={"type": "Element"})
    start_index: int = field(
        default=0,
        metadata={
            "type": "Element",
        },
    )
    count: int | None = field(
        default=None,
        metadata={
            "type": "Element",
        },
    )
count: int | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
queries: list[FeatureQuery] = field(metadata={'type': 'Element'}) class-attribute instance-attribute
start_index: int = field(default=0, metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(queries: list[FeatureQuery], start_index: int = 0, count: int | None = None) -> None
output
Attribute dataclass

Bases: BaseInterface

An attribute belonging to a feature. The aim here is to drill down to simple types which can be used in consuming applications without further handling. This does not include the geometry attribute!

Attributes:

  • name (str) –

    The name of the attribute. Has to match with the name used for exported fields with Field class.

  • value (int | float | str | bool | bytes | None) –

    Value as simple as possible. It has to be pickleable

Source code in src/qgis_server_light/interface/job/feature/output.py
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@dataclass(repr=False)
class Attribute(BaseInterface):
    """An attribute belonging to a feature. The aim here is to
    drill down to simple types which can be used in consuming
    applications without further handling. This does not include
    the geometry attribute!

    Attributes:
        name: The name of the attribute. Has to match with the
            name used for exported fields with `Field` class.
        value: Value as simple as possible. It has to be
            [pickleable](https://docs.python.org/3/library/pickle.html#what-can-be-pickled-and-unpickled)

    """

    name: str = field(metadata={"type": "Element"})
    value: int | float | str | bool | bytes | None = field(
        metadata={"type": "Element", "format": "base64"}
    )
name: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
value: int | float | str | bool | bytes | None = field(metadata={'type': 'Element', 'format': 'base64'}) class-attribute instance-attribute
__init__(name: str, value: int | float | str | bool | bytes | None) -> None
Feature dataclass

Bases: BaseInterface

Feature to hold information of extracted QgsFeature.

Attributes:

Source code in src/qgis_server_light/interface/job/feature/output.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
@dataclass(repr=False)
class Feature(BaseInterface):
    """Feature to hold information of extracted QgsFeature.

    Attributes:
        geometry: The geometry representing the feature.
        attributes: List of attributes defined in this feature.
    """

    geometry: Geometry | None = field(default=None, metadata={"type": "Element"})
    attributes: list[Attribute] = field(
        default_factory=list,
        metadata={"type": "Element"},
    )
attributes: list[Attribute] = field(default_factory=list, metadata={'type': 'Element'}) class-attribute instance-attribute
geometry: Geometry | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(geometry: Geometry | None = None, attributes: list[Attribute] = list()) -> None
FeatureCollection dataclass

Bases: BaseInterface

This construction is used to abstract the content of extracted features for pickelable transportation from QSL to the queue. This way we ensure how things are constructed and transported.

Attributes:

  • name (str) –

    The name of the feature collection. This is the key to match it to requested layers.

  • features (list[Feature]) –

    The features belonging to the feature collection.

Source code in src/qgis_server_light/interface/job/feature/output.py
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
@dataclass(repr=False)
class FeatureCollection(BaseInterface):
    """This construction is used to abstract the content of extracted
    features for pickelable transportation from QSL to the queue.
    This way we ensure how things are constructed and transported.

    Attributes:
        name: The name of the feature collection. This is the key to
            match it to requested layers.
        features: The features belonging to the feature collection.
    """

    name: str = field(metadata={"type": "Element"})
    features: list[Feature] = field(
        default_factory=list,
        metadata={"type": "Element"},
    )
features: list[Feature] = field(default_factory=list, metadata={'type': 'Element'}) class-attribute instance-attribute
name: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(name: str, features: list[Feature] = list()) -> None
Geometry dataclass

Bases: Attribute

Source code in src/qgis_server_light/interface/job/feature/output.py
27
28
29
30
31
32
33
34
35
36
@dataclass(repr=False)
class Geometry(Attribute):
    name: str = field(default="geometry", metadata={"type": "Element"})
    value: bytes | None = field(
        default=None, metadata={"type": "Element", "format": "base64"}
    )

    @property
    def shortened_fields(self) -> set:
        return {"value"}
name: str = field(default='geometry', metadata={'type': 'Element'}) class-attribute instance-attribute
shortened_fields: set property
value: bytes | None = field(default=None, metadata={'type': 'Element', 'format': 'base64'}) class-attribute instance-attribute
__init__(name: str = 'geometry', value: bytes | None = None) -> None
QueryCollection dataclass

Bases: BaseInterface

Holds all feature collections which are bound to the passed queries. The order in the list has to be not changed, so that consuming applications can map the response to the passed queries.

Attributes:

Source code in src/qgis_server_light/interface/job/feature/output.py
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
@dataclass(repr=False)
class QueryCollection(BaseInterface):
    """Holds all feature collections which are bound to the
    passed queries. The order in the list has to be not changed,
    so that consuming applications can map the response to the
    passed queries.

    Attributes:
        numbers_matched: Information about how many matches are fund for the executed query.
        feature_collections: The feature collections belonging to the passed queries.
    """

    numbers_matched: str | int = field(
        default="unknown",
        metadata={"type": "Element"},
    )
    feature_collections: list[FeatureCollection] = field(
        default_factory=list,
        metadata={"type": "Element"},
    )
feature_collections: list[FeatureCollection] = field(default_factory=list, metadata={'type': 'Element'}) class-attribute instance-attribute
numbers_matched: str | int = field(default='unknown', metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(numbers_matched: str | int = 'unknown', feature_collections: list[FeatureCollection] = list()) -> None

feature_info

input
QslJobInfoFeatureInfo dataclass

Bases: QslJobInfoParameter

Source code in src/qgis_server_light/interface/job/feature_info/input.py
44
45
46
47
48
@dataclass
class QslJobInfoFeatureInfo(QslJobInfoParameter):
    job: QslJobParameterFeatureInfo = field(
        metadata={"type": "Element", "required": True}
    )
job: QslJobParameterFeatureInfo = field(metadata={'type': 'Element', 'required': True}) class-attribute instance-attribute
__init__(id: str, type: str, job: QslJobParameterFeatureInfo) -> None
QslJobParameterFeatureInfo dataclass

Bases: QslJobParameter

A runner to extract feature info

Source code in src/qgis_server_light/interface/job/feature_info/input.py
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@dataclass(kw_only=True)
class QslJobParameterFeatureInfo(QslJobParameter):
    """A runner to extract feature info"""

    # mime type, only application/json supported
    INFO_FORMAT: str = field(metadata={"type": "Element"})
    QUERY_LAYERS: str = field(metadata={"type": "Element"})
    X: str | None = field(default=None, metadata={"type": "Element"})
    Y: str | None = field(default=None, metadata={"type": "Element"})
    I: str | None = field(default=None, metadata={"type": "Element"})  # noqa: E741
    J: str | None = field(default=None, metadata={"type": "Element"})

    def __post_init__(self):
        x = int(self.I or self.X)
        y = int(self.J or self.Y)
        if x is None or y is None:
            raise KeyError(
                "Parameter `I` or `X` and `J` or `Y`  are mandatory for GetFeatureInfo"
            )
        if self.QUERY_LAYERS is None:
            raise KeyError("QUERY_LAYERS is mandatory in this request")

    @property
    def decide_x(self) -> int:
        return int(self.I or self.X)

    @property
    def decide_y(self) -> int:
        return int(self.J or self.Y)

    @property
    def query_layers_list(self):
        return self.QUERY_LAYERS.split(",")
I: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
INFO_FORMAT: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
J: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
QUERY_LAYERS: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
X: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
Y: str | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
decide_x: int property
decide_y: int property
query_layers_list property
__init__(*, INFO_FORMAT: str, QUERY_LAYERS: str, X: str | None = None, Y: str | None = None, I: str | None = None, J: str | None = None) -> None
__post_init__()
Source code in src/qgis_server_light/interface/job/feature_info/input.py
21
22
23
24
25
26
27
28
29
def __post_init__(self):
    x = int(self.I or self.X)
    y = int(self.J or self.Y)
    if x is None or y is None:
        raise KeyError(
            "Parameter `I` or `X` and `J` or `Y`  are mandatory for GetFeatureInfo"
        )
    if self.QUERY_LAYERS is None:
        raise KeyError("QUERY_LAYERS is mandatory in this request")
output

legend

input
QslJobInfoLegend dataclass

Bases: QslJobInfoParameter

Source code in src/qgis_server_light/interface/job/legend/input.py
14
15
16
@dataclass
class QslJobInfoLegend(QslJobInfoParameter):
    job: QslJobParameterLegend = field(metadata={"type": "Element", "required": True})
job: QslJobParameterLegend = field(metadata={'type': 'Element', 'required': True}) class-attribute instance-attribute
__init__(id: str, type: str, job: QslJobParameterLegend) -> None
QslJobParameterLegend dataclass

Bases: QslJobParameter

Render legend

Source code in src/qgis_server_light/interface/job/legend/input.py
 9
10
11
@dataclass(kw_only=True)
class QslJobParameterLegend(QslJobParameter):
    """Render legend"""
__init__() -> None
output

process

input
output

render

input
QslJobInfoRender dataclass

Bases: QslJobInfoParameter

Source code in src/qgis_server_light/interface/job/render/input.py
30
31
32
@dataclass
class QslJobInfoRender(QslJobInfoParameter):
    job: QslJobParameterRender = field(metadata={"type": "Element", "required": True})
job: QslJobParameterRender = field(metadata={'type': 'Element', 'required': True}) class-attribute instance-attribute
__init__(id: str, type: str, job: QslJobParameterRender) -> None
QslJobParameterRender dataclass

Bases: QslJobParameter

A runner to be rendered as an image

Source code in src/qgis_server_light/interface/job/render/input.py
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@dataclass(kw_only=True)
class QslJobParameterRender(QslJobParameter):
    """A runner to be rendered as an image"""

    layers: list[QslJobLayer] = field(metadata={"type": "Element"})
    bbox: BBox = field(metadata={"type": "Element"})
    crs: str = field(metadata={"type": "Element"})
    width: int = field(metadata={"type": "Element"})
    height: int = field(metadata={"type": "Element"})
    dpi: int | None = field(default=None, metadata={"type": "Element"})
    format: str = field(default="image/png", metadata={"type": "Element"})

    def get_layer_by_name(self, name: str) -> QslJobLayer:
        for layer in self.layers:
            if layer.name == name:
                return layer
        raise LookupError(f'No layer with name "{name} was found."')
bbox: BBox = field(metadata={'type': 'Element'}) class-attribute instance-attribute
crs: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
dpi: int | None = field(default=None, metadata={'type': 'Element'}) class-attribute instance-attribute
format: str = field(default='image/png', metadata={'type': 'Element'}) class-attribute instance-attribute
height: int = field(metadata={'type': 'Element'}) class-attribute instance-attribute
layers: list[QslJobLayer] = field(metadata={'type': 'Element'}) class-attribute instance-attribute
width: int = field(metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(*, layers: list[QslJobLayer], bbox: BBox, crs: str, width: int, height: int, dpi: int | None = None, format: str = 'image/png') -> None
get_layer_by_name(name: str) -> QslJobLayer
Source code in src/qgis_server_light/interface/job/render/input.py
23
24
25
26
27
def get_layer_by_name(self, name: str) -> QslJobLayer:
    for layer in self.layers:
        if layer.name == name:
            return layer
    raise LookupError(f'No layer with name "{name} was found."')
output

worker

info

This part defines the structure how a running QSL worker exposes its capabilities.

EngineInfo dataclass
Source code in src/qgis_server_light/interface/worker/info.py
33
34
35
36
37
38
@dataclass
class EngineInfo:
    id: str = field(metadata={"type": "Element"})
    qgis_info: QgisInfo = field(metadata={"type": "Element"})
    status: Status = field(metadata={"type": "Element"})
    started: float = field(metadata={"type": "Element"})
id: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
qgis_info: QgisInfo = field(metadata={'type': 'Element'}) class-attribute instance-attribute
started: float = field(metadata={'type': 'Element'}) class-attribute instance-attribute
status: Status = field(metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(id: str, qgis_info: QgisInfo, status: Status, started: float) -> None
QgisInfo dataclass

Information container to ship minimal knowledge of the underlying QGIS.

Attributes:

  • version (int) –

    The integer representation of the QGIS version e.g. 34400

  • version_name (str) –

    The string representation which also includes the codename e.g. "QGIS Version 4.0.0-Norrköping"

Source code in src/qgis_server_light/interface/worker/info.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@dataclass
class QgisInfo:
    """
    Information container to ship minimal knowledge of the underlying
    QGIS.

    Attributes:
        version: The integer representation of the QGIS version e.g. 34400
        version_name: The string representation which also includes the codename e.g.
            "QGIS Version 4.0.0-Norrköping"

    """

    version: int = field(metadata={"type": "Element"})
    version_name: str = field(metadata={"type": "Element"})
    path: str = field(metadata={"type": "Element"})
path: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
version: int = field(metadata={'type': 'Element'}) class-attribute instance-attribute
version_name: str = field(metadata={'type': 'Element'}) class-attribute instance-attribute
__init__(version: int, version_name: str, path: str) -> None
Status

Bases: str, Enum

Source code in src/qgis_server_light/interface/worker/info.py
 8
 9
10
11
12
class Status(str, Enum):
    STARTING = "starting"
    CRASHED = "crashed"
    WAITING = "waiting"
    PROCESSING = "processing"
CRASHED = 'crashed' class-attribute instance-attribute
PROCESSING = 'processing' class-attribute instance-attribute
STARTING = 'starting' class-attribute instance-attribute
WAITING = 'waiting' class-attribute instance-attribute