Coverage for gws-app/gws/base/database/__init__.py: 100%
1 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
1"""Database support.
3Base classes for database connections and for the objects that read and
4write database tables: layers, models and authorization providers. Concrete
5implementations (for PostgreSQL/PostGIS) live in ``gws.plugin.postgres`` and
6extend the classes in this package.
8Submodules
9----------
11- ``manager`` - the database manager (``root.app.databaseMgr``). It creates
12 the providers configured under ``database.providers`` and finds them by uid
13 or type. A provider that fails to configure is logged and skipped. The
14 manager registers itself as the ``db`` middleware.
15- ``provider`` - the base database provider. It wraps an SQLAlchemy ``Engine``,
16 hands out connections, reflects and caches table structures and describes
17 tables and columns.
18- ``connection`` - the connection object returned by ``provider.connect()``,
19 a thin wrapper around an SQLAlchemy ``Connection`` with fetch helpers.
20- ``model`` - the base database model, which reads features with a SELECT built
21 by the model fields and creates, updates and deletes table rows.
22- ``layer`` - the base vector layer for a database table, with a default model
23 and finder for the table. The geometry type and CRS are taken from the table
24 description. Without configured models, the layer gets a default model. If
25 no extent is configured, it is computed from the table bounds. Unless
26 finders are configured or search is disabled, the layer gets a default
27 finder.
28- ``auth_provider`` - the base authorization provider that checks users with
29 SQL queries.
31Providers and connections
32-------------------------
34Layers, models and other objects refer to a provider with ``dbUid``. Without
35``dbUid`` they use the default provider passed by their parent or the first
36provider of their type (see ``gws.config.util.configure_database_provider_for``).
38A provider keeps one SQLAlchemy connection per thread. ``connect()`` calls can
39be nested; inner calls reuse the open connection, and only the outermost one
40closes it::
42 with db.connect() as conn:
43 rows = conn.fetch_all('SELECT * FROM my_table WHERE id = :id', id=1)
44 with db.connect() as conn2:
45 # the same connection
46 n = conn2.fetch_int('SELECT count(*) FROM my_table')
48Because the connection is shared, a ``commit()`` or ``rollback()`` on any level
49ends the transaction for all levels. The ``fetch_*`` helpers roll back after
50reading.
52The engine is created on activation and is not pickled. Connection pooling is
53off unless ``withPool`` is set; the ``pool`` options ``disabled``,
54``pre_ping``, ``size``, ``recycle`` and ``timeout`` are passed to the engine.
56Table structures are reflected per schema and cached for
57``schemaCacheLifeTime`` seconds. ``describe`` and ``describe_column`` turn
58the reflected structure into ``gws.DataSetDescription`` and
59``gws.ColumnDescription`` objects, which models use to derive their fields.
61Models
62------
64A database model builds its SELECT from the contributions of its fields
65(``mc.dbSelect``), the search query (uids, keyword, shape, sort, limit, extra
66conditions and columns) and the configured ``sqlFilter``. If the search asks
67for uids, a keyword or a shape, but the table has no primary key or no field
68contributes a matching condition, nothing is selected. Writes call the field
69hooks (``before_create``, ``after_create`` and so on) around a single INSERT,
70UPDATE or DELETE and commit the connection. Reads and writes check the user's
71permissions on the model and raise ``gws.ForbiddenError`` if they are
72missing. Composite primary keys are not supported. See ``gws.base.model`` for
73the data flow between records, features and props.
75SQL authorization
76-----------------
78The authorization provider runs two configured SELECT queries:
80- ``authorizationSql`` receives the placeholders ``{username}``, ``{password}``
81 and ``{token}`` from an authentication method. If it returns no rows, the
82 provider does not know the user and the next provider is tried. More than one
83 row is an error. Otherwise the row must contain the columns ``validuser``
84 and ``validpassword`` (booleans, both must be true to log in) and ``uid``
85 (the user id). The column ``roles`` is a comma-separated list of roles.
86- ``getUserSql`` receives the placeholder ``{uid}`` and returns the record of
87 this user, for example to restore a user from a session.
89The column names ``validuser``, ``validpassword`` and ``uid`` are
90case-insensitive. Other columns are passed to ``gws.base.auth.user.from_record``.
91In the config file the braces of the placeholders are doubled.
93Example::
95 database.providers+ {
96 uid "main_db"
97 type "postgres"
98 serviceName "LOCAL"
99 }
101 map.layers+ {
102 type "postgres"
103 dbUid "main_db"
104 tableName "edit.poi"
105 models+ {
106 type "postgres"
107 isEditable true
108 }
109 }
111 auth.providers+ {
112 type "postgres"
113 dbUid "main_db"
115 authorizationSql '''
116 SELECT
117 user.id
118 AS uid,
119 user.first_name || ' ' || user.last_name
120 AS displayname,
121 user.login
122 AS login,
123 user.is_enabled
124 AS validuser,
125 ( passwd = crypt({{password}}, passwd) )
126 AS validpassword
127 FROM
128 public.user
129 WHERE
130 user.login = {{username}}
131 '''
133 getUserSql '''
134 SELECT
135 user.id
136 AS uid,
137 user.first_name || ' ' || user.last_name
138 AS displayname,
139 user.login
140 AS login
141 FROM
142 public.user
143 WHERE
144 user.id = {{uid}}
145 '''
146 }
148The ``authorizationSql`` example assumes Postgres with the ``pgcrypto`` extension.
149"""
151from . import provider, manager, model