Skip to content

Commit 02ed788

Browse files
committed
Update documentation
1 parent f5b3878 commit 02ed788

File tree

3 files changed

+24
-57
lines changed

3 files changed

+24
-57
lines changed

docs/lifecycle.md

Lines changed: 23 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -17,70 +17,37 @@ From SQLAlchemy documentation:
1717
recommends we create `Session` object at the beginning of a logical operation where
1818
database access is potentially anticipated.
1919

20-
The repository is not built for parallel execution, it keeps a `Session` object scoped to
21-
its lifecycle to avoid unnecessary queries, and executes a transaction for each _operation_
22-
to maintain isolation. This means you can create a repository object almost whenever you want,
23-
as long as you don't run parallel operations.
24-
25-
The session in the repository is not thread safe and [is not safe on concurrent asyncio tasks](https://docs.sqlalchemy.org/en/20/orm/session_basics.html#is-the-session-thread-safe-is-asyncsession-safe-to-share-in-concurrent-tasks)
26-
therefore the repository has the same limitations.
27-
28-
This means:
29-
30-
* Do not assign a repository object to a global variable
31-
(Check the [Notes on multithreaded applications](/manager/session/#note-on-multithreaded-applications))
32-
* Do not share a repository instance in multiple threads or processes (e.g. using `asyncio.to_thread`).
33-
* Do not use the same repository in concurrent asyncio task (e.g. using `asyncio.gather`)
34-
35-
Even using multiple repository instances will work fine, however as they will have completely
36-
different sessions, it's likely that the second repository will fire additional SELECT queries
37-
to get the state of the object prior to saving it.
38-
39-
/// details | Example
40-
```python
41-
from sqlalchemy import String
42-
from sqlalchemy.orm import Mapped, mapped_column
43-
from sqlalchemy_bind_manager import SQLAlchemyBindManager ,SQLAlchemyConfig
44-
from sqlalchemy_bind_manager.repository import SQLAlchemyRepository
45-
46-
config = SQLAlchemyConfig(
47-
engine_url="sqlite:///./sqlite.db",
48-
engine_options=dict(connect_args={"check_same_thread": False}, echo=True),
49-
session_options=dict(expire_on_commit=False),
50-
)
51-
52-
sa_manager = SQLAlchemyBindManager(config={})
53-
54-
class MyModel(sa_manager.get_bind().model_declarative_base):
55-
id: Mapped[int] = mapped_column(primary_key=True)
56-
name: Mapped[str] = mapped_column(String(30))
57-
58-
def update_my_model():
59-
# Create 2 instances of the same repository
60-
repo = SQLAlchemyRepository(sa_manager.get_bind(), model_class=MyModel)
61-
repo2 = SQLAlchemyRepository(sa_manager.get_bind(), model_class=MyModel)
62-
63-
o = repo.get(1)
64-
o.name = "John"
65-
66-
repo2.save(o)
67-
68-
update_my_model()
69-
```
70-
///
20+
The repository keeps a `Session` object scoped to its lifecycle to avoid unnecessary queries,
21+
and executes a transaction for each _operation_ to maintain isolation. This means you can create
22+
a repository object almost whenever you want, as long as you don't run parallel operations.
23+
24+
The repository is safe in multithreaded applications and in concurrent asyncio tasks, this means
25+
that potentially you can save it in a global variable, and it will have a different `Session`
26+
in each thread or asyncio task.
27+
28+
Even if the repository can be used with concurrency or parallelism, remember SQLAlchemy models
29+
belong to a single `Session`, so sharing the same models in multiple threads or asyncio tasks
30+
will cause problems.
31+
32+
What you can do is:
7133

72-
The recommendation is of course to try to write your application in a way you cna use
73-
a single repository instance, where possible.
34+
* Save the repositories in global variables and start a thread / asyncio task to handle
35+
a scoped request (e.g. one thread per HTTP request)
36+
37+
What you cannot do is:
38+
39+
* Get a list of models
40+
* Save the models using `save()` in parallel threads / tasks (each task will have a different session)
41+
42+
/// tip | The recommendation is of course to try to use a single repository instance, where possible.
7443

7544
For example a strategy similar to this would be optimal, if possible:
7645

7746
* Create repositories
7847
* Retrieve all the models you need
79-
* Do the changes you need, as per business logic
48+
* Do the changes you need, as per business logic, eventually using multiple threads / tasks
8049
* Save all the changed models as needed
8150

82-
/// tip | Using multiple repository instances is the only way to safely use concurrent asyncio tasks
83-
8451
///
8552

8653
### Unit of work
File renamed without changes.

mkdocs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ nav:
4545
- Session usage: manager/session.md
4646
- Alembic integration: manager/alembic.md
4747
- Repository:
48-
- Repository usage: repository/repository.md
48+
- Repository usage: repository/usage.md
4949
- Unit of work: repository/uow.md
5050
- Components life cycle: lifecycle.md
5151

0 commit comments

Comments
 (0)