Skip to content

common

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