File: flask.rst

package info (click to toggle)
python-advanced-alchemy 1.8.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 4,904 kB
  • sloc: python: 36,227; makefile: 153; sh: 4
file content (262 lines) | stat: -rw-r--r-- 8,170 bytes parent folder | download
1
2
3
4
5
6
7
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
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
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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
Flask Integration
=================

Advanced Alchemy provides seamless integration with Flask applications through its Flask extension.

Installation
------------

The Flask extension is included with Advanced Alchemy by default. No additional installation is required.

Basic Usage
-----------

Here's a basic example of using Advanced Alchemy with Flask:

.. code-block:: python

    from flask import Flask
    from sqlalchemy import select
    from advanced_alchemy.extensions.flask import (
        AdvancedAlchemy,
        SQLAlchemySyncConfig,
        EngineConfig,
    )

    app = Flask(__name__)
    alchemy_config = SQLAlchemySyncConfig(connection_string="sqlite:///local.db", commit_mode="autocommit", create_all=True)
    alchemy = AdvancedAlchemy(alchemy_config, app)

    # Use standard SQLAlchemy session in your routes
    @app.route("/users")
    def list_users():
        db_session = alchemy.get_sync_session()
        users = db_session.execute(select(User))
        return {"users": [user.dict() for user in users.scalars()]}

Multiple Databases
------------------

Advanced Alchemy supports multiple database configurations:

.. note::

    The ``bind_key`` option is used to specify the database to use for a given session.

    When using multiple databases and you do not have at least one database with a ``bind_key`` of ``default``, and exception will be raised when calling ``db.get_session()`` without a bind key.

    This only applies when using multiple configuration.  If you are using a single configuration, the engine will be returned even if the ``bind_key`` is not ``default``.

.. code-block:: python

    configs = [
        SQLAlchemySyncConfig(connection_string="sqlite:///users.db", bind_key="users"),
        SQLAlchemySyncConfig(connection_string="sqlite:///products.db", bind_key="products"),
    ]

    alchemy = AdvancedAlchemy(configs, app)

    # Get session for specific database
    users_session = alchemy.get_sync_session("users")
    products_session = alchemy.get_sync_session("products")

Async Support
-------------

Advanced Alchemy supports async SQLAlchemy with Flask:

.. code-block:: python

    from advanced_alchemy.extensions.flask import (
        AdvancedAlchemy,
        SQLAlchemyAsyncConfig,
    )
    from sqlalchemy import select

    app = Flask(__name__)
    alchemy_config = SQLAlchemyAsyncConfig(connection_string="postgresql+asyncpg://user:pass@localhost/db", create_all=True)
    alchemy = AdvancedAlchemy(alchemy_config, app)

    # Use async session in your routes
    @app.route("/users")
    async def list_users():
        db_session = alchemy.get_async_session()
        users = await db_session.execute(select(User))
        return {"users": [user.dict() for user in users.scalars()]}

You can also safely use an AsyncSession in your routes within a sync context.


.. warning::

    This is experimental and may change in the future.

.. code-block:: python

    @app.route("/users")
    def list_users():
        db_session = alchemy.get_async_session()
        users = alchemy.portal.call(db_session.execute, select(User))
        return {"users": [user.dict() for user in users.scalars()]}

Configuration
-------------

SQLAlchemy Configuration
~~~~~~~~~~~~~~~~~~~~~~~~

Both sync and async configurations support these options:

.. list-table::
   :header-rows: 1

   * - Option
     - Type
     - Description
     - Default
   * - ``engine_config``
     - ``EngineConfig``
     - SQLAlchemy engine configuration
     - Required
   * - ``bind_key``
     - ``str``
     - Key for multiple database support
     - "default"
   * - ``create_all``
     - ``bool``
     - Create tables on startup
     - ``False``
   * - ``commit_mode``
     - ``"autocommit", "autocommit_include_redirect", "manual"``
     - Session commit behavior
     - ``"manual"``

Commit Modes
~~~~~~~~~~~~

The ``commit_mode`` option controls how database sessions are committed:

- ``"manual"`` (default): No automatic commits
- ``"autocommit"``: Commit on successful responses (2xx status codes)
- ``"autocommit_include_redirect"``: Commit on successful responses and redirects (2xx and 3xx status codes)

Services
--------

The ``FlaskServiceMixin`` adds Flask-specific functionality to services:

Here's an example of a service that uses the ``FlaskServiceMixin`` with all CRUD operations, route pagination, and msgspec serialization for JSON

.. code-block:: python

    import datetime
    from typing import Optional
    from uuid import UUID

    from msgspec import Struct
    from flask import Flask
    from sqlalchemy.orm import Mapped, mapped_column
    from advanced_alchemy.extensions.flask import (
        AdvancedAlchemy,
        FlaskServiceMixin,
        service,
        repository,
        SQLAlchemySyncConfig,
        base,
    )

    class Author(base.UUIDBase):
        """Author model."""

        name: Mapped[str]
        dob: Mapped[Optional[datetime.date]]

    class AuthorSchema(Struct):
        """Author schema."""

        name: str
        id: Optional[UUID] = None
        dob: Optional[datetime.date] = None


    class AuthorService(FlaskServiceMixin, service.SQLAlchemySyncRepositoryService[Author]):
        class Repo(repository.SQLAlchemySyncRepository[Author]):
            model_type = Author

        repository_type = Repo

    app = Flask(__name__)
    alchemy_config = SQLAlchemySyncConfig(connection_string="sqlite:///local.db", commit_mode="autocommit", create_all=True)
    alchemy = AdvancedAlchemy(alchemy_config, app)


    @app.route("/authors", methods=["GET"])
    def list_authors():
        """List authors with pagination."""
        page, page_size = request.args.get("currentPage", 1, type=int), request.args.get("pageSize", 10, type=int)
        limit_offset = filters.LimitOffset(limit=page_size, offset=page_size * (page - 1))
        service = AuthorService(session=alchemy.get_sync_session())
        results, total = service.list_and_count(limit_offset)
        response = service.to_schema(results, total, filters=[limit_offset], schema_type=AuthorSchema)
        return service.jsonify(response)


    @app.route("/authors", methods=["POST"])
    def create_author():
        """Create a new author."""
        service = AuthorService(session=alchemy.get_sync_session())
        obj = service.create(**request.get_json())
        return service.jsonify(obj)


    @app.route("/authors/<uuid:author_id>", methods=["GET"])
    def get_author(author_id: UUID):
        """Get an existing author."""
        service = AuthorService(session=alchemy.get_sync_session(), load=[Author.books])
        obj = service.get(author_id)
        return service.jsonify(obj)


    @app.route("/authors/<uuid:author_id>", methods=["PATCH"])
    def update_author(author_id: UUID):
        """Update an author."""
        service = AuthorService(session=alchemy.get_sync_session(), load=[Author.books])
        obj = service.update(**request.get_json(), item_id=author_id)
        return service.jsonify(obj)


    @app.route("/authors/<uuid:author_id>", methods=["DELETE"])
    def delete_author(author_id: UUID):
        """Delete an author."""
        service = AuthorService(session=alchemy.get_sync_session())
        service.delete(author_id)
        return "", 204

The ``jsonify`` method is analogous to Flask's ``jsonify`` function.  However, this implementation will serialize with the configured Advanced Alchemy serialize (i.e. Msgspec or Orjson based on installation).

Database Migrations
-------------------

When the extension is configured for Flask, database commands are automatically added to the Flask CLI.  These are the same commands available to you when running the ``alchemy`` standalone CLI.

Here's an example of the commands available to Flask

.. code-block:: bash

    # Initialize migrations
    flask database init

    # Create a new migration
    flask database revision --autogenerate -m "Add users table"

    # Apply migrations
    flask database upgrade

    # Revert migrations
    flask database downgrade

    # Show migration history
    flask database history

    # Show all commands
    flask database --help