Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details
Socket
Book a DemoInstallSign in
Socket

invenio-db

Package Overview
Dependencies
Maintainers
1
Versions
50
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

invenio-db - npm Package Compare versions

Comparing version
2.0.0
to
2.1.0
+6
-1
CHANGES.rst
..
This file is part of Invenio.
Copyright (C) 2015-2024 CERN.
Copyright (C) 2024 Graz University of Technology.
Copyright (C) 2024-2025 Graz University of Technology.

@@ -12,2 +12,7 @@ Invenio is free software; you can redistribute it and/or modify it

Version v2.1.0 (released 2025-07-17)
- workflows: switch to centralized workflows
- chores: replaced importlib_xyz with importlib
Version v2.0.0 (released 2024-11-19)

@@ -14,0 +19,0 @@

+0
-1

@@ -6,2 +6,1 @@ [invenio_base.api_apps]

invenio_db = invenio_db:InvenioDB

@@ -1,4 +0,4 @@

Metadata-Version: 2.1
Metadata-Version: 2.4
Name: invenio-db
Version: 2.0.0
Version: 2.1.0
Summary: Database management for Invenio.

@@ -9,184 +9,2 @@ Home-page: https://github.com/inveniosoftware/invenio-db

License: MIT
Description: ..
This file is part of Invenio.
Copyright (C) 2015-2018 CERN.
Invenio is free software; you can redistribute it and/or modify it
under the terms of the MIT License; see LICENSE file for more details.
============
Invenio-DB
============
.. image:: https://img.shields.io/github/license/inveniosoftware/invenio-db.svg
:target: https://github.com/inveniosoftware/invenio-db/blob/master/LICENSE
.. image:: https://github.com/inveniosoftware/invenio-db/workflows/CI/badge.svg
:target: https://github.com/inveniosoftware/invenio-db/actions?query=workflow%3ACI
.. image:: https://img.shields.io/coveralls/inveniosoftware/invenio-db.svg
:target: https://coveralls.io/r/inveniosoftware/invenio-db
.. image:: https://img.shields.io/pypi/v/invenio-db.svg
:target: https://pypi.org/pypi/invenio-db
Database management for Invenio.
Further documentation available on https://invenio-db.readthedocs.io/
..
This file is part of Invenio.
Copyright (C) 2015-2024 CERN.
Copyright (C) 2024 Graz University of Technology.
Invenio is free software; you can redistribute it and/or modify it
under the terms of the MIT License; see LICENSE file for more details.
Changes
=======
Version v2.0.0 (released 2024-11-19)
- uow: possible solution for the rollback problem
- fix: select of BinaryExpressions
- setup: increase flask-sqlalchemy
- ci: change to reusable workflows
- setup: increase min sqlalchemy dependency
- cli: password is per default hided
- refactor: move MockEntryPoint to independent file
- setup: remove upper pins
- fix: remove warning and black problems
- fix: WeakKeyDictionary
- change: add proxy file
- change: remove click 3 compatibility
- fix: tests LocalProxy
- setup: increase flask-sqlalchemy version
Version v1.3.1 (released 2024-11-14)
- uow: improve decorator to create uow if it's None
Version v1.3.0 (released 2024-11-04)
- uow: add "on_exception" lifecycle hook
* When an exception occurs and the unit of work is being rolled back, we
want to add the possibility to perform some clean-up actions that can
also be commited to the database.
* The new `on_exception` method is added because we want to keep
backwards compatibility and also introduce the same "triplet" of
methods as we have for the "happy path" (`on_register`, `on_commit`,
`on_post_commit`).
Version 1.2.0 (released 2024-10-01)
- uow: moved Unit of Work pattern and non-records Operations from
invenio-records-resources to invenio-db package
Version 1.1.5 (released 2023-09-11)
- shared: removed 'text()' from cursor.execute for SQLite
Version 1.1.4 (released 2023-08-18)
- shared: fix sqlalchemy op.execute statements due to latest sqlalchemy-continuum
Version 1.1.3 (released 2023-08-17)
- alembic: fix sqlalchemy op.execute statements due to latest sqlalchemy-continuum
Version 1.1.2 (released 2023-05-23)
- fix db engine dependencies
Version 1.1.1 (released 2023-05-22)
- upgrade minimal python version
- code formatting
- force installing greenlet dependency
- upper pin alembic dependency version
Version 1.0.15 (released 2023-05-17)
- yanked due to mixed commits in the release
Version 1.1.0 (released 2023-04-06)
- yanked, because of an incompatibility with Flask-SQLAlchemy v3.
Version 1.0.14 (released 2022-03-30)
- Adds support for SQLAlchemy 1.4 and Flask v2.1.
Version 1.0.13 (released 2022-02-21)
- Changes alembic migrations to run a single migration in one transaction
instead of all migrations in a single transaction.
Version 1.0.12 (released 2022-02-14)
- Fixes a deprecation warning.
Version 1.0.11 (released 2022-02-08)
- Fixed issue with alembic version locations introduced in v1.0.10 due to the
importlib change.
Version 1.0.10 (released 2022-02-08)
- Adds a utility for creating an alembic test context to centrally manage
fixes for alembic migration tests in other modules.
- Replaces pkg_resources with importlib
Version 1.0.9 (released 2021-03-18)
- Pins Flask-SQLAlchemy below 2.5 due to breaking changes. Perhaps to revisit when fixed.
Version 1.0.8 (released 2020-11-16)
- Pins SQLAlchemy to >=1.2.18 and <1.4 due to incompatibility between
SQLAlchemy and SQLAlchemy-Utils.
Version 1.0.7 (released 2020-11-08)
- Hides password from output when running db init or db create.
- Disables MySQL 8 tests due to issue with Alembic
Version 1.0.6 (released 2020-10-02)
- Bump SQLAlchemy version to ``>=1.2.18`` to add support for PostgreSQL 12
- Integrate ``pytest-invenio`` and ``docker-services-cli`` for testing
- Support Python 3.8
Version 1.0.5 (released 2020-05-11)
- Deprecated Python versions lower than 3.6.0. Now supporting 3.6.0 and 3.7.0
- Use centrally managed Flask version (through Invenio-Base)
- Bumped SQLAlchemy version to ``>=1.1.0``
- SQLAlchemy-Utils set to ``<0.36`` due to breaking changes with MySQL
(``VARCHAR`` length)
- Enriched documentation on DB session management
- Stop using example app
Version 1.0.4 (released 2019-07-29)
- Unpin sqlalchemy-continuum
- Added tests for postgresql 10
Version 1.0.3 (released 2019-02-22)
- Added handling in case of missing Sqlite db file.
Version 1.0.2 (released 2018-06-22)
- Pin SQLAlchemy-Continuum.
Version 1.0.1 (released 2018-05-16)
- Minor fixes in documenation links and the license file.
Version 1.0.0 (released 2018-03-23)
- Initial public release.
Keywords: invenio database

@@ -196,5 +14,209 @@ Platform: any

Requires-Python: >=3.7
License-File: LICENSE
License-File: AUTHORS.rst
Requires-Dist: alembic>=1.10.0
Requires-Dist: Flask-Alembic>=3.0.0
Requires-Dist: Flask-SQLAlchemy>=3.0
Requires-Dist: invenio-base<3.0.0,>=2.3.0
Requires-Dist: SQLAlchemy-Continuum>=1.3.12
Requires-Dist: SQLAlchemy-Utils>=0.33.1
Requires-Dist: SQLAlchemy[asyncio]>=2.0.0
Provides-Extra: tests
Requires-Dist: six>=1.0.0; extra == "tests"
Requires-Dist: pytest-black-ng>=0.4.0; extra == "tests"
Requires-Dist: cryptography>=2.1.4; extra == "tests"
Requires-Dist: pytest-invenio<4.0.0,>=3.0.0; extra == "tests"
Requires-Dist: Sphinx>=4.5.0; extra == "tests"
Provides-Extra: mysql
Requires-Dist: pymysql>=0.10.1; extra == "mysql"
Provides-Extra: postgresql
Requires-Dist: psycopg2-binary>=2.8.6; extra == "postgresql"
Provides-Extra: versioning
Dynamic: license-file
..
This file is part of Invenio.
Copyright (C) 2015-2018 CERN.
Invenio is free software; you can redistribute it and/or modify it
under the terms of the MIT License; see LICENSE file for more details.
============
Invenio-DB
============
.. image:: https://img.shields.io/github/license/inveniosoftware/invenio-db.svg
:target: https://github.com/inveniosoftware/invenio-db/blob/master/LICENSE
.. image:: https://github.com/inveniosoftware/invenio-db/workflows/CI/badge.svg
:target: https://github.com/inveniosoftware/invenio-db/actions?query=workflow%3ACI
.. image:: https://img.shields.io/coveralls/inveniosoftware/invenio-db.svg
:target: https://coveralls.io/r/inveniosoftware/invenio-db
.. image:: https://img.shields.io/pypi/v/invenio-db.svg
:target: https://pypi.org/pypi/invenio-db
Database management for Invenio.
Further documentation available on https://invenio-db.readthedocs.io/
..
This file is part of Invenio.
Copyright (C) 2015-2024 CERN.
Copyright (C) 2024-2025 Graz University of Technology.
Invenio is free software; you can redistribute it and/or modify it
under the terms of the MIT License; see LICENSE file for more details.
Changes
=======
Version v2.1.0 (released 2025-07-17)
- workflows: switch to centralized workflows
- chores: replaced importlib_xyz with importlib
Version v2.0.0 (released 2024-11-19)
- uow: possible solution for the rollback problem
- fix: select of BinaryExpressions
- setup: increase flask-sqlalchemy
- ci: change to reusable workflows
- setup: increase min sqlalchemy dependency
- cli: password is per default hided
- refactor: move MockEntryPoint to independent file
- setup: remove upper pins
- fix: remove warning and black problems
- fix: WeakKeyDictionary
- change: add proxy file
- change: remove click 3 compatibility
- fix: tests LocalProxy
- setup: increase flask-sqlalchemy version
Version v1.3.1 (released 2024-11-14)
- uow: improve decorator to create uow if it's None
Version v1.3.0 (released 2024-11-04)
- uow: add "on_exception" lifecycle hook
* When an exception occurs and the unit of work is being rolled back, we
want to add the possibility to perform some clean-up actions that can
also be commited to the database.
* The new `on_exception` method is added because we want to keep
backwards compatibility and also introduce the same "triplet" of
methods as we have for the "happy path" (`on_register`, `on_commit`,
`on_post_commit`).
Version 1.2.0 (released 2024-10-01)
- uow: moved Unit of Work pattern and non-records Operations from
invenio-records-resources to invenio-db package
Version 1.1.5 (released 2023-09-11)
- shared: removed 'text()' from cursor.execute for SQLite
Version 1.1.4 (released 2023-08-18)
- shared: fix sqlalchemy op.execute statements due to latest sqlalchemy-continuum
Version 1.1.3 (released 2023-08-17)
- alembic: fix sqlalchemy op.execute statements due to latest sqlalchemy-continuum
Version 1.1.2 (released 2023-05-23)
- fix db engine dependencies
Version 1.1.1 (released 2023-05-22)
- upgrade minimal python version
- code formatting
- force installing greenlet dependency
- upper pin alembic dependency version
Version 1.0.15 (released 2023-05-17)
- yanked due to mixed commits in the release
Version 1.1.0 (released 2023-04-06)
- yanked, because of an incompatibility with Flask-SQLAlchemy v3.
Version 1.0.14 (released 2022-03-30)
- Adds support for SQLAlchemy 1.4 and Flask v2.1.
Version 1.0.13 (released 2022-02-21)
- Changes alembic migrations to run a single migration in one transaction
instead of all migrations in a single transaction.
Version 1.0.12 (released 2022-02-14)
- Fixes a deprecation warning.
Version 1.0.11 (released 2022-02-08)
- Fixed issue with alembic version locations introduced in v1.0.10 due to the
importlib change.
Version 1.0.10 (released 2022-02-08)
- Adds a utility for creating an alembic test context to centrally manage
fixes for alembic migration tests in other modules.
- Replaces pkg_resources with importlib
Version 1.0.9 (released 2021-03-18)
- Pins Flask-SQLAlchemy below 2.5 due to breaking changes. Perhaps to revisit when fixed.
Version 1.0.8 (released 2020-11-16)
- Pins SQLAlchemy to >=1.2.18 and <1.4 due to incompatibility between
SQLAlchemy and SQLAlchemy-Utils.
Version 1.0.7 (released 2020-11-08)
- Hides password from output when running db init or db create.
- Disables MySQL 8 tests due to issue with Alembic
Version 1.0.6 (released 2020-10-02)
- Bump SQLAlchemy version to ``>=1.2.18`` to add support for PostgreSQL 12
- Integrate ``pytest-invenio`` and ``docker-services-cli`` for testing
- Support Python 3.8
Version 1.0.5 (released 2020-05-11)
- Deprecated Python versions lower than 3.6.0. Now supporting 3.6.0 and 3.7.0
- Use centrally managed Flask version (through Invenio-Base)
- Bumped SQLAlchemy version to ``>=1.1.0``
- SQLAlchemy-Utils set to ``<0.36`` due to breaking changes with MySQL
(``VARCHAR`` length)
- Enriched documentation on DB session management
- Stop using example app
Version 1.0.4 (released 2019-07-29)
- Unpin sqlalchemy-continuum
- Added tests for postgresql 10
Version 1.0.3 (released 2019-02-22)
- Added handling in case of missing Sqlite db file.
Version 1.0.2 (released 2018-06-22)
- Pin SQLAlchemy-Continuum.
Version 1.0.1 (released 2018-05-16)
- Minor fixes in documenation links and the license file.
Version 1.0.0 (released 2018-03-23)
- Initial public release.
alembic>=1.10.0
Flask-Alembic>=3.0.0
Flask-SQLAlchemy>=3.0
invenio-base>=1.2.10
invenio-base<3.0.0,>=2.3.0
SQLAlchemy-Continuum>=1.3.12

@@ -19,5 +19,5 @@ SQLAlchemy-Utils>=0.33.1

cryptography>=2.1.4
pytest-invenio>=1.4.5
pytest-invenio<4.0.0,>=3.0.0
Sphinx>=4.5.0
[versioning]

@@ -17,19 +17,2 @@ .dockerignore

setup.py
.github/workflows/pypi-publish.yml
.github/workflows/tests.yml
docs/Makefile
docs/alembic.rst
docs/api.rst
docs/authors.rst
docs/changes.rst
docs/conf.py
docs/configuration.rst
docs/contributing.rst
docs/index.rst
docs/installation.rst
docs/license.rst
docs/make.bat
docs/requirements.txt
docs/session_management.rst
docs/usage.rst
invenio_db/__init__.py

@@ -52,13 +35,2 @@ invenio_db/cli.py

invenio_db/alembic/dbdbc1b19cf2_create_transaction_table.py
invenio_db/alembic/script.py.mako
tests/conftest.py
tests/mocks.py
tests/test_db.py
tests/test_uow.py
tests/test_utils.py
tests/test_versioning.py
tests/demo/__init__.py
tests/demo/child.py
tests/demo/parent.py
tests/demo/versioned_a.py
tests/demo/versioned_b.py
invenio_db/alembic/script.py.mako

@@ -95,3 +95,3 @@ # -*- coding: utf-8 -*-

__version__ = "2.0.0"
__version__ = "2.1.0"

@@ -98,0 +98,0 @@ __all__ = (

@@ -14,7 +14,9 @@ # -*- coding: utf-8 -*-

import os
from importlib.metadata import PackageNotFoundError
from importlib.metadata import version as package_version
from importlib.resources import files
import importlib_metadata
import importlib_resources
import sqlalchemy as sa
from flask_alembic import Alembic
from invenio_base.utils import entry_points
from sqlalchemy_utils.functions import get_class_by_table

@@ -41,12 +43,10 @@

def pathify(base_entry):
return str(
importlib_resources.files(base_entry.module)
/ os.path.join(base_entry.attr)
)
return str(files(base_entry.module) / os.path.join(base_entry.attr))
entry_points = importlib_metadata.entry_points(group="invenio_db.alembic")
alembic_entry_points = entry_points(group="invenio_db.alembic")
version_locations = [
(base_entry.name, pathify(base_entry)) for base_entry in entry_points
(base_entry.name, pathify(base_entry))
for base_entry in alembic_entry_points
]
script_location = str(importlib_resources.files("invenio_db") / "alembic")
script_location = str(files("invenio_db") / "alembic")
app.config.setdefault(

@@ -91,3 +91,3 @@ "ALEMBIC",

if entry_point_group:
for base_entry in importlib_metadata.entry_points(group=entry_point_group):
for base_entry in entry_points(group=entry_point_group):
base_entry.load()

@@ -111,4 +111,4 @@

try:
importlib_metadata.version("sqlalchemy_continuum")
except importlib_metadata.PackageNotFoundError: # pragma: no cover
package_version("sqlalchemy_continuum")
except PackageNotFoundError: # pragma: no cover
default_versioning = False

@@ -137,4 +137,4 @@ else:

try:
importlib_metadata.version("invenio_accounts")
except importlib_metadata.PackageNotFoundError:
package_version("invenio_accounts")
except PackageNotFoundError:
user_cls = None

@@ -141,0 +141,0 @@ else:

+206
-184

@@ -1,4 +0,4 @@

Metadata-Version: 2.1
Metadata-Version: 2.4
Name: invenio-db
Version: 2.0.0
Version: 2.1.0
Summary: Database management for Invenio.

@@ -9,184 +9,2 @@ Home-page: https://github.com/inveniosoftware/invenio-db

License: MIT
Description: ..
This file is part of Invenio.
Copyright (C) 2015-2018 CERN.
Invenio is free software; you can redistribute it and/or modify it
under the terms of the MIT License; see LICENSE file for more details.
============
Invenio-DB
============
.. image:: https://img.shields.io/github/license/inveniosoftware/invenio-db.svg
:target: https://github.com/inveniosoftware/invenio-db/blob/master/LICENSE
.. image:: https://github.com/inveniosoftware/invenio-db/workflows/CI/badge.svg
:target: https://github.com/inveniosoftware/invenio-db/actions?query=workflow%3ACI
.. image:: https://img.shields.io/coveralls/inveniosoftware/invenio-db.svg
:target: https://coveralls.io/r/inveniosoftware/invenio-db
.. image:: https://img.shields.io/pypi/v/invenio-db.svg
:target: https://pypi.org/pypi/invenio-db
Database management for Invenio.
Further documentation available on https://invenio-db.readthedocs.io/
..
This file is part of Invenio.
Copyright (C) 2015-2024 CERN.
Copyright (C) 2024 Graz University of Technology.
Invenio is free software; you can redistribute it and/or modify it
under the terms of the MIT License; see LICENSE file for more details.
Changes
=======
Version v2.0.0 (released 2024-11-19)
- uow: possible solution for the rollback problem
- fix: select of BinaryExpressions
- setup: increase flask-sqlalchemy
- ci: change to reusable workflows
- setup: increase min sqlalchemy dependency
- cli: password is per default hided
- refactor: move MockEntryPoint to independent file
- setup: remove upper pins
- fix: remove warning and black problems
- fix: WeakKeyDictionary
- change: add proxy file
- change: remove click 3 compatibility
- fix: tests LocalProxy
- setup: increase flask-sqlalchemy version
Version v1.3.1 (released 2024-11-14)
- uow: improve decorator to create uow if it's None
Version v1.3.0 (released 2024-11-04)
- uow: add "on_exception" lifecycle hook
* When an exception occurs and the unit of work is being rolled back, we
want to add the possibility to perform some clean-up actions that can
also be commited to the database.
* The new `on_exception` method is added because we want to keep
backwards compatibility and also introduce the same "triplet" of
methods as we have for the "happy path" (`on_register`, `on_commit`,
`on_post_commit`).
Version 1.2.0 (released 2024-10-01)
- uow: moved Unit of Work pattern and non-records Operations from
invenio-records-resources to invenio-db package
Version 1.1.5 (released 2023-09-11)
- shared: removed 'text()' from cursor.execute for SQLite
Version 1.1.4 (released 2023-08-18)
- shared: fix sqlalchemy op.execute statements due to latest sqlalchemy-continuum
Version 1.1.3 (released 2023-08-17)
- alembic: fix sqlalchemy op.execute statements due to latest sqlalchemy-continuum
Version 1.1.2 (released 2023-05-23)
- fix db engine dependencies
Version 1.1.1 (released 2023-05-22)
- upgrade minimal python version
- code formatting
- force installing greenlet dependency
- upper pin alembic dependency version
Version 1.0.15 (released 2023-05-17)
- yanked due to mixed commits in the release
Version 1.1.0 (released 2023-04-06)
- yanked, because of an incompatibility with Flask-SQLAlchemy v3.
Version 1.0.14 (released 2022-03-30)
- Adds support for SQLAlchemy 1.4 and Flask v2.1.
Version 1.0.13 (released 2022-02-21)
- Changes alembic migrations to run a single migration in one transaction
instead of all migrations in a single transaction.
Version 1.0.12 (released 2022-02-14)
- Fixes a deprecation warning.
Version 1.0.11 (released 2022-02-08)
- Fixed issue with alembic version locations introduced in v1.0.10 due to the
importlib change.
Version 1.0.10 (released 2022-02-08)
- Adds a utility for creating an alembic test context to centrally manage
fixes for alembic migration tests in other modules.
- Replaces pkg_resources with importlib
Version 1.0.9 (released 2021-03-18)
- Pins Flask-SQLAlchemy below 2.5 due to breaking changes. Perhaps to revisit when fixed.
Version 1.0.8 (released 2020-11-16)
- Pins SQLAlchemy to >=1.2.18 and <1.4 due to incompatibility between
SQLAlchemy and SQLAlchemy-Utils.
Version 1.0.7 (released 2020-11-08)
- Hides password from output when running db init or db create.
- Disables MySQL 8 tests due to issue with Alembic
Version 1.0.6 (released 2020-10-02)
- Bump SQLAlchemy version to ``>=1.2.18`` to add support for PostgreSQL 12
- Integrate ``pytest-invenio`` and ``docker-services-cli`` for testing
- Support Python 3.8
Version 1.0.5 (released 2020-05-11)
- Deprecated Python versions lower than 3.6.0. Now supporting 3.6.0 and 3.7.0
- Use centrally managed Flask version (through Invenio-Base)
- Bumped SQLAlchemy version to ``>=1.1.0``
- SQLAlchemy-Utils set to ``<0.36`` due to breaking changes with MySQL
(``VARCHAR`` length)
- Enriched documentation on DB session management
- Stop using example app
Version 1.0.4 (released 2019-07-29)
- Unpin sqlalchemy-continuum
- Added tests for postgresql 10
Version 1.0.3 (released 2019-02-22)
- Added handling in case of missing Sqlite db file.
Version 1.0.2 (released 2018-06-22)
- Pin SQLAlchemy-Continuum.
Version 1.0.1 (released 2018-05-16)
- Minor fixes in documenation links and the license file.
Version 1.0.0 (released 2018-03-23)
- Initial public release.
Keywords: invenio database

@@ -196,5 +14,209 @@ Platform: any

Requires-Python: >=3.7
License-File: LICENSE
License-File: AUTHORS.rst
Requires-Dist: alembic>=1.10.0
Requires-Dist: Flask-Alembic>=3.0.0
Requires-Dist: Flask-SQLAlchemy>=3.0
Requires-Dist: invenio-base<3.0.0,>=2.3.0
Requires-Dist: SQLAlchemy-Continuum>=1.3.12
Requires-Dist: SQLAlchemy-Utils>=0.33.1
Requires-Dist: SQLAlchemy[asyncio]>=2.0.0
Provides-Extra: tests
Requires-Dist: six>=1.0.0; extra == "tests"
Requires-Dist: pytest-black-ng>=0.4.0; extra == "tests"
Requires-Dist: cryptography>=2.1.4; extra == "tests"
Requires-Dist: pytest-invenio<4.0.0,>=3.0.0; extra == "tests"
Requires-Dist: Sphinx>=4.5.0; extra == "tests"
Provides-Extra: mysql
Requires-Dist: pymysql>=0.10.1; extra == "mysql"
Provides-Extra: postgresql
Requires-Dist: psycopg2-binary>=2.8.6; extra == "postgresql"
Provides-Extra: versioning
Dynamic: license-file
..
This file is part of Invenio.
Copyright (C) 2015-2018 CERN.
Invenio is free software; you can redistribute it and/or modify it
under the terms of the MIT License; see LICENSE file for more details.
============
Invenio-DB
============
.. image:: https://img.shields.io/github/license/inveniosoftware/invenio-db.svg
:target: https://github.com/inveniosoftware/invenio-db/blob/master/LICENSE
.. image:: https://github.com/inveniosoftware/invenio-db/workflows/CI/badge.svg
:target: https://github.com/inveniosoftware/invenio-db/actions?query=workflow%3ACI
.. image:: https://img.shields.io/coveralls/inveniosoftware/invenio-db.svg
:target: https://coveralls.io/r/inveniosoftware/invenio-db
.. image:: https://img.shields.io/pypi/v/invenio-db.svg
:target: https://pypi.org/pypi/invenio-db
Database management for Invenio.
Further documentation available on https://invenio-db.readthedocs.io/
..
This file is part of Invenio.
Copyright (C) 2015-2024 CERN.
Copyright (C) 2024-2025 Graz University of Technology.
Invenio is free software; you can redistribute it and/or modify it
under the terms of the MIT License; see LICENSE file for more details.
Changes
=======
Version v2.1.0 (released 2025-07-17)
- workflows: switch to centralized workflows
- chores: replaced importlib_xyz with importlib
Version v2.0.0 (released 2024-11-19)
- uow: possible solution for the rollback problem
- fix: select of BinaryExpressions
- setup: increase flask-sqlalchemy
- ci: change to reusable workflows
- setup: increase min sqlalchemy dependency
- cli: password is per default hided
- refactor: move MockEntryPoint to independent file
- setup: remove upper pins
- fix: remove warning and black problems
- fix: WeakKeyDictionary
- change: add proxy file
- change: remove click 3 compatibility
- fix: tests LocalProxy
- setup: increase flask-sqlalchemy version
Version v1.3.1 (released 2024-11-14)
- uow: improve decorator to create uow if it's None
Version v1.3.0 (released 2024-11-04)
- uow: add "on_exception" lifecycle hook
* When an exception occurs and the unit of work is being rolled back, we
want to add the possibility to perform some clean-up actions that can
also be commited to the database.
* The new `on_exception` method is added because we want to keep
backwards compatibility and also introduce the same "triplet" of
methods as we have for the "happy path" (`on_register`, `on_commit`,
`on_post_commit`).
Version 1.2.0 (released 2024-10-01)
- uow: moved Unit of Work pattern and non-records Operations from
invenio-records-resources to invenio-db package
Version 1.1.5 (released 2023-09-11)
- shared: removed 'text()' from cursor.execute for SQLite
Version 1.1.4 (released 2023-08-18)
- shared: fix sqlalchemy op.execute statements due to latest sqlalchemy-continuum
Version 1.1.3 (released 2023-08-17)
- alembic: fix sqlalchemy op.execute statements due to latest sqlalchemy-continuum
Version 1.1.2 (released 2023-05-23)
- fix db engine dependencies
Version 1.1.1 (released 2023-05-22)
- upgrade minimal python version
- code formatting
- force installing greenlet dependency
- upper pin alembic dependency version
Version 1.0.15 (released 2023-05-17)
- yanked due to mixed commits in the release
Version 1.1.0 (released 2023-04-06)
- yanked, because of an incompatibility with Flask-SQLAlchemy v3.
Version 1.0.14 (released 2022-03-30)
- Adds support for SQLAlchemy 1.4 and Flask v2.1.
Version 1.0.13 (released 2022-02-21)
- Changes alembic migrations to run a single migration in one transaction
instead of all migrations in a single transaction.
Version 1.0.12 (released 2022-02-14)
- Fixes a deprecation warning.
Version 1.0.11 (released 2022-02-08)
- Fixed issue with alembic version locations introduced in v1.0.10 due to the
importlib change.
Version 1.0.10 (released 2022-02-08)
- Adds a utility for creating an alembic test context to centrally manage
fixes for alembic migration tests in other modules.
- Replaces pkg_resources with importlib
Version 1.0.9 (released 2021-03-18)
- Pins Flask-SQLAlchemy below 2.5 due to breaking changes. Perhaps to revisit when fixed.
Version 1.0.8 (released 2020-11-16)
- Pins SQLAlchemy to >=1.2.18 and <1.4 due to incompatibility between
SQLAlchemy and SQLAlchemy-Utils.
Version 1.0.7 (released 2020-11-08)
- Hides password from output when running db init or db create.
- Disables MySQL 8 tests due to issue with Alembic
Version 1.0.6 (released 2020-10-02)
- Bump SQLAlchemy version to ``>=1.2.18`` to add support for PostgreSQL 12
- Integrate ``pytest-invenio`` and ``docker-services-cli`` for testing
- Support Python 3.8
Version 1.0.5 (released 2020-05-11)
- Deprecated Python versions lower than 3.6.0. Now supporting 3.6.0 and 3.7.0
- Use centrally managed Flask version (through Invenio-Base)
- Bumped SQLAlchemy version to ``>=1.1.0``
- SQLAlchemy-Utils set to ``<0.36`` due to breaking changes with MySQL
(``VARCHAR`` length)
- Enriched documentation on DB session management
- Stop using example app
Version 1.0.4 (released 2019-07-29)
- Unpin sqlalchemy-continuum
- Added tests for postgresql 10
Version 1.0.3 (released 2019-02-22)
- Added handling in case of missing Sqlite db file.
Version 1.0.2 (released 2018-06-22)
- Pin SQLAlchemy-Continuum.
Version 1.0.1 (released 2018-05-16)
- Minor fixes in documenation links and the license file.
Version 1.0.0 (released 2018-03-23)
- Initial public release.

@@ -24,3 +24,3 @@ [metadata]

Flask-SQLAlchemy>=3.0
invenio-base>=1.2.10
invenio-base>=2.3.0,<3.0.0
SQLAlchemy-Continuum>=1.3.12

@@ -35,3 +35,3 @@ SQLAlchemy-Utils>=0.33.1

cryptography>=2.1.4
pytest-invenio>=1.4.5
pytest-invenio>=3.0.0,<4.0.0
Sphinx>=4.5.0

@@ -38,0 +38,0 @@ mysql =

name: Publish
on:
push:
tags:
- v*
jobs:
Publish:
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.7
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel
- name: Build package
run: |
python setup.py sdist bdist_wheel
- name: Publish on PyPI
uses: pypa/gh-action-pypi-publish@v1.3.1
with:
user: __token__
password: ${{ secrets.pypi_token }}
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2020-2024 CERN.
# Copyright (C) 2022-2024 Graz University of Technology.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.
name: CI
on:
push:
branches:
- master
pull_request:
branches:
- master
schedule:
# * is a special character in YAML so you have to quote this string
- cron: "0 3 * * 6"
workflow_dispatch:
inputs:
reason:
description: "Reason"
required: false
default: "Manual trigger"
jobs:
Tests:
uses: inveniosoftware/workflows/.github/workflows/tests-python.yml@master
with:
extras: "tests,postgresql"
search-service: '[""]'
..
This file is part of Invenio.
Copyright (C) 2017-2018 CERN.
Invenio is free software; you can redistribute it and/or modify it
under the terms of the MIT License; see LICENSE file for more details.
Alembic for Invenio
===================
Alembic is a database migration library used with SQLAlchemy ORM. Invenio works with the Flask-Alembic library, its documentation can be found here: http://flask-alembic.readthedocs.io/en/latest/
Invenio-DB fully supports alembic and each Invenio module having a database model is also expected to provide the corresponding alembic revisions.
Alembic migrations do not work with SQLite.
Adding alembic support to existing modules
------------------------------------------
The following procedures assume ``invenio_foo`` is the name of the module for which you are adding alembic support.
The first step would be to add the entrypoint ``invenio_db.alembic`` in your ``invenio_foo`` setup.py as follows:
.. code-block:: python
setup(
...
entry_points={
...
'invenio_db.alembic': [
'invenio_foo = invenio_foo:alembic',
]
})
This will register the ``invenio_foo/alembic`` directory in alembic's ``version_locations``.
Each module should create a branch for its revisions. In order to create a new branch, and in consequence the first revision, one should run:
.. code-block:: console
$ invenio alembic revision "Create foo branch." -b invenio_foo -p <parent-revision> -d <dependencies> --empty
| -b sets the branch label (conventionally the name of the module)
| -p sets the parent revision, as default all branches should root from the revision ``dbdbc1b19cf2`` in invenio-db
| -d sets the dependencies if they exist. For example when there is a foreign key pointing to the table of another invenio module, we need to make sure that table exists before applying this revision, so we add the necessary revision tag as a dependency.
The second revision typically has the message "Create foo tables." and will create the tables defined in the models. This can be created following the procedure below.
Creating a new revision
-----------------------
After making changes to the models of a module, we need to create a new alembic revision so we are able to apply these changes in the DB during a migration. Firstly, to make sure that the DB is up to date we apply any pending revisions with:
.. code-block:: console
$ invenio alembic upgrade heads
and now we can create the new revision with:
.. code-block:: console
$ invenio alembic revision "Revision message." --path ``invenio_foo/alembic``
A short message describing the changes is required and the path parameter should point to the alembic directory of the module. If the path is not given the new revision will be placed in the invenio_db/alembic directory and should be moved.
Show current state
------------------
To see the list of revisions in the order they will be applied, run:
.. code-block:: console
$ invenio alembic log
The list of heads for all branches is given by:
.. code-block:: console
$ invenio alembic heads
in this list, revisions will be labeled as ``(head)`` or ``(effective head)``. The difference being that effective heads are not shown in the ``alembic_version`` table in your database. As they are dependencies of other branches, they will be overwritten. ``alembic_version`` is a table created by alembic to keep the current revision state.
The list of the revisions that have been applied to the current database can be seen with:
.. code-block:: console
$ invenio alembic current
Enabling alembic migrations in existing invenio instances
---------------------------------------------------------
In order to integrate alembic when there is already a DB in place, we have to create an ``alembic_version`` table stamped with the revisions matching the current state of the DB:
.. code-block:: console
$ invenio alembic stamp
Assuming that there have been no changes in the DB, and the models match the alembic revisions, alembic upgrades and downgrades will be working now.
Note that if there are any unnamed constraints, they will get the default names from the DB which can be different from the ones in the alembic revisions.
Naming Constraints
------------------
In http://alembic.zzzcomputing.com/en/latest/naming.html, the need for naming constraints in the models is explained. In invenio-db the '35c1075e6360' revision applies the naming convention for invenio. If models contain constraints that are unnamed an ``InvalidRequestError`` will be raised.
The naming convention rules are:
+---------------+----------------------------------------------------------------+
| index | 'ix_%(column_0_label)s' |
+---------------+----------------------------------------------------------------+
| unique | 'uq_%(table_name)s_%(column_0_name)s' |
+---------------+----------------------------------------------------------------+
| check | 'ck_%(table_name)s_%(constraint_name)s' |
+---------------+----------------------------------------------------------------+
| foreign key | 'fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s' |
+---------------+----------------------------------------------------------------+
| primary key | 'pk_%(table_name)s' |
+---------------+----------------------------------------------------------------+
The constraints that produce a name longer that 64 characters will have to be named explicitly to a truncated form.
Testing revisions
-----------------
When initially creating alembic revisions one has to provide a test case for them.
The test for the created revisions starts from an empty DB, upgrades to the last branch revision and then downgrades to the base. We can check that there are no discrepancies in the state of the DB between the revisions and the models, by asserting that alembic.compare_metadata() returns an empty list. An example can be found here: `test_app.py#L130 <https://github.com/inveniosoftware/invenio-oauthclient/blob/d46de4d5e8269395b69230694ba073af88406404/tests/test_app.py#L130>`_
..
This file is part of Invenio.
Copyright (C) 2015-2018 CERN.
Invenio is free software; you can redistribute it and/or modify it
under the terms of the MIT License; see LICENSE file for more details.
API Docs
========
.. automodule:: invenio_db.ext
:members:
.. automodule:: invenio_db.shared
:members:
.. automodule:: invenio_db.cli
:members:
..
This file is part of Invenio.
Copyright (C) 2015-2018 CERN.
Invenio is free software; you can redistribute it and/or modify it
under the terms of the MIT License; see LICENSE file for more details.
.. include:: ../AUTHORS.rst
..
This file is part of Invenio.
Copyright (C) 2015-2018 CERN.
Invenio is free software; you can redistribute it and/or modify it
under the terms of the MIT License; see LICENSE file for more details.
.. include:: ../CHANGES.rst
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2015-2018 CERN.
# Copyright (C) 2022 Graz University of Technology.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.
"""Sphinx configuration."""
import os
import sys
import sphinx.environment
from invenio_db import __version__
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
# needs_sphinx = '1.0'
# Do not warn on external images.
suppress_warnings = ["image.nonlocal_uri"]
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.coverage",
"sphinx.ext.doctest",
"sphinx.ext.intersphinx",
"sphinx.ext.viewcode",
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
# source_suffix = ['.rst', '.md']
source_suffix = ".rst"
# The encoding of source files.
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = "index"
# General information about the project.
project = "Invenio-DB"
copyright = "2015, CERN"
author = "CERN"
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
# Get the version string. Cannot be done with import!
version = __version__
# The full version, including alpha/beta/rc tags.
release = version
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = "en"
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
# today = ''
# Else, today_fmt is used as the format for a strftime call.
# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = []
# The reST default role (used for this markup: `text`) to use for all
# documents.
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
# keep_warnings = False
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
# -- Options for HTML output ----------------------------------------------
html_theme = "alabaster"
html_theme_options = {
"description": "Database management for Invenio.",
"github_user": "inveniosoftware",
"github_repo": "invenio-db",
"github_button": False,
"github_banner": True,
"show_powered_by": False,
"extra_nav_links": {
"invenio-db@GitHub": "https://github.com/inveniosoftware/invenio-db",
"invenio-db@PyPI": "https://pypi.python.org/pypi/invenio-db/",
},
}
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
# html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
# html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
# html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
# html_static_path = ['_static']
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
# html_extra_path = []
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
# html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
html_sidebars = {
"**": [
"about.html",
"navigation.html",
"relations.html",
"searchbox.html",
"donate.html",
]
}
# Additional templates that should be rendered to pages, maps page names to
# template names.
# html_additional_pages = {}
# If false, no module index is generated.
# html_domain_indices = True
# If false, no index is generated.
# html_use_index = True
# If true, the index is split into individual pages for each letter.
# html_split_index = False
# If true, links to the reST sources are added to the pages.
# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
# html_file_suffix = None
# Language to be used for generating the HTML full-text search index.
# Sphinx supports the following languages:
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
# html_search_language = 'en'
# A dictionary with options for the search language support, empty by default.
# Now only 'ja' uses this config value
# html_search_options = {'type': 'default'}
# The name of a javascript file (relative to the configuration directory) that
# implements a search results scorer. If empty, the default will be used.
# html_search_scorer = 'scorer.js'
# Output file base name for HTML help builder.
htmlhelp_basename = "invenio-db_namedoc"
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
# Latex figure (float) alignment
#'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, "invenio-db.tex", "invenio-db Documentation", "CERN", "manual"),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
# latex_use_parts = False
# If true, show page references after internal links.
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
# latex_show_urls = False
# Documents to append as an appendix to all manuals.
# latex_appendices = []
# If false, no module index is generated.
# latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [(master_doc, "invenio-db", "invenio-db Documentation", [author], 1)]
# If true, show URL addresses after external links.
# man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(
master_doc,
"invenio-db",
"Invenio-DB Documentation",
author,
"invenio-db",
"Database management for Invenio.",
"Miscellaneous",
),
]
# Documents to append as an appendix to all manuals.
# texinfo_appendices = []
# If false, no module index is generated.
# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
# texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
# texinfo_no_detailmenu = False
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {"python": ("https://docs.python.org/3", None)}
# Autodoc configuraton.
autoclass_content = "both"
..
This file is part of Invenio.
Copyright (C) 2017-2018 CERN.
Invenio is free software; you can redistribute it and/or modify it
under the terms of the MIT License; see LICENSE file for more details.
Configuration
=============
The default values of configuration options are guessed based on installed
packages.
.. data:: SQLALCHEMY_DATABASE_URI
The database URI that should be used for the connection. Defaults to
``'sqlite:///<instance_path>/<app.name>.db'``.
.. data:: SQLALCHEMY_ECHO
Enables debug output containing database queries. Defaults to ``True``
if application is in debug mode (``app.debug == True``).
.. data:: DB_VERSIONING
Enables versioning support using SQLAlchemy-Continuum. Defaults to ``True``
if ``sqlalchemy_continuum`` package is installed.
.. data:: DB_VERSIONING_USER_MODEL
User class used by versioning manager. Defaults to ``'User'`` if
``invenio_accounts`` package is installed.
.. data:: ALEMBIC
Dictionary containing general configuration for Flask-Alembic. It contains
defaults for following keys:
* ``'script_location'`` points to location of the migrations directory.
It is **required** key and defaults to location of ``invenio_db.alembic``
package resource.
* ``'version_locations'`` lists location of all independent named branches
specified by Invenio packages in ``invenio_db.alembic`` entry point
group.
Please check following packages for further configuration options:
1. `Flask-SQLAlchemy <https://flask-sqlalchemy.readthedocs.io/en/stable/config/>`_
2. `Flask-Alembic <https://flask-alembic.readthedocs.io/en/stable/#configuration>`_
3. `SQLAlchemy-Continuum <https://sqlalchemy-continuum.readthedocs.io/en/latest/configuration.html>`_
..
This file is part of Invenio.
Copyright (C) 2015-2018 CERN.
Invenio is free software; you can redistribute it and/or modify it
under the terms of the MIT License; see LICENSE file for more details.
.. include:: ../CONTRIBUTING.rst
..
This file is part of Invenio.
Copyright (C) 2015-2018 CERN.
Invenio is free software; you can redistribute it and/or modify it
under the terms of the MIT License; see LICENSE file for more details.
.. include:: ../README.rst
User's Guide
------------
This part of the documentation will show you how to get started in using
Invenio-DB.
.. toctree::
:maxdepth: 2
installation
configuration
usage
alembic
session_management
API Reference
-------------
If you are looking for information on a specific function, class or method,
this part of the documentation is for you.
.. toctree::
:maxdepth: 2
api
Additional Notes
----------------
Notes on how to contribute, legal information and changes are here for the
interested.
.. toctree::
:maxdepth: 1
contributing
changes
license
authors
..
This file is part of Invenio.
Copyright (C) 2015-2018 CERN.
Invenio is free software; you can redistribute it and/or modify it
under the terms of the MIT License; see LICENSE file for more details.
.. include:: ../INSTALL.rst
License
=======
.. include:: ../LICENSE
.. note::
In applying this license, CERN does not waive the privileges and immunities
granted to it by virtue of its status as an Intergovernmental Organization or
submit itself to any jurisdiction.
@ECHO OFF
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set BUILDDIR=_build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
set I18NSPHINXOPTS=%SPHINXOPTS% .
if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
)
if "%1" == "" goto help
if "%1" == "help" (
:help
echo.Please use `make ^<target^>` where ^<target^> is one of
echo. html to make standalone HTML files
echo. dirhtml to make HTML files named index.html in directories
echo. singlehtml to make a single large HTML file
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and a HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. devhelp to make HTML files and a Devhelp project
echo. epub to make an epub
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. text to make text files
echo. man to make manual pages
echo. texinfo to make Texinfo files
echo. gettext to make PO message catalogs
echo. changes to make an overview over all changed/added/deprecated items
echo. xml to make Docutils-native XML files
echo. pseudoxml to make pseudoxml-XML files for display purposes
echo. linkcheck to check all external links for integrity
echo. doctest to run all doctests embedded in the documentation if enabled
echo. coverage to run coverage check of the documentation if enabled
goto end
)
if "%1" == "clean" (
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
del /q /s %BUILDDIR%\*
goto end
)
REM Check if sphinx-build is available and fallback to Python version if any
%SPHINXBUILD% 2> nul
if errorlevel 9009 goto sphinx_python
goto sphinx_ok
:sphinx_python
set SPHINXBUILD=python -m sphinx.__init__
%SPHINXBUILD% 2> nul
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
:sphinx_ok
if "%1" == "html" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
goto end
)
if "%1" == "dirhtml" (
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
goto end
)
if "%1" == "singlehtml" (
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
goto end
)
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
goto end
)
if "%1" == "qthelp" (
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Invenio-DB.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Invenio-DB.ghc
goto end
)
if "%1" == "devhelp" (
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished.
goto end
)
if "%1" == "epub" (
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The epub file is in %BUILDDIR%/epub.
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
if errorlevel 1 exit /b 1
echo.
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "latexpdf" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
cd %BUILDDIR%/latex
make all-pdf
cd %~dp0
echo.
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "latexpdfja" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
cd %BUILDDIR%/latex
make all-pdf-ja
cd %~dp0
echo.
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "text" (
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The text files are in %BUILDDIR%/text.
goto end
)
if "%1" == "man" (
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The manual pages are in %BUILDDIR%/man.
goto end
)
if "%1" == "texinfo" (
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
goto end
)
if "%1" == "gettext" (
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
if errorlevel 1 exit /b 1
echo.
echo.The overview file is in %BUILDDIR%/changes.
goto end
)
if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
if errorlevel 1 exit /b 1
echo.
echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
goto end
)
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
if errorlevel 1 exit /b 1
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
goto end
)
if "%1" == "coverage" (
%SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage
if errorlevel 1 exit /b 1
echo.
echo.Testing of coverage in the sources finished, look at the ^
results in %BUILDDIR%/coverage/python.txt.
goto end
)
if "%1" == "xml" (
%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The XML files are in %BUILDDIR%/xml.
goto end
)
if "%1" == "pseudoxml" (
%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
goto end
)
:end
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
endif
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " applehelp to make an Apple Help Book"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " xml to make Docutils-native XML files"
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
@echo " coverage to run coverage check of the documentation (if enabled)"
clean:
rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Invenio-DB.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Invenio-DB.qhc"
applehelp:
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
@echo
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
@echo "N.B. You won't be able to view it unless you put it in" \
"~/Library/Documentation/Help or install it in your application" \
"bundle."
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/Invenio-DB"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Invenio-DB"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
latexpdfja:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
coverage:
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
@echo "Testing of coverage in the sources finished, look at the " \
"results in $(BUILDDIR)/coverage/python.txt."
xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
-e .[docs,tests]
..
This file is part of Invenio.
Copyright (C) 2019 CERN.
Invenio is free software; you can redistribute it and/or modify it
under the terms of the MIT License; see LICENSE file for more details.
Database session management
===========================
Invenio uses `SQLAlchemy Toolkit and Object Relational Mapper <https://www.sqlalchemy.org>`_
for all database related operations.
Transactions are tied to requests
---------------------------------
In Invenio, a transaction is tied to an HTTP request. By default, the transaction is
automatically rolled back unless you explicitly call ``db.session.commit()``.
Exceptions cause rollback
--------------------------
If an exception occurs during request handling, then the entire transaction will be
rolled back unless there has been an explicit call to ``db.session.commit()``
before the exception was raised. This is because the default behavior is to rollback.
Why are transactions tied to requests?
--------------------------------------
Transactions are tied to requests, because the outer view, in charge of handling the
request, needs full control of when a transaction is committed. If the view was not
in charge, you could end up with inconsistent data in the database - for instance
persistent identifier may have been committed, but the associated record was not committed.
That is why Invenio makes use of SQLAlchemy’s version counter feature to provide `optimistic
concurrency control (OCC) <https://invenio-records.readthedocs.io/en/latest/concurrency.html>`_
on the records table when the database transaction isolation level is below repeatable read
isolation level.
When are SQL statements sent to the database server?
----------------------------------------------------
SQLAlchemy only sends the SQL statements (INSERT, UPDATE, SELECT, …) to the database
server when needed, or when explicitly requested via e.g. ``db.session.flush()`` or
``db.session.commit()``.
This means that in many cases, SQL INSERT and UPDATE statements are not sent to the
server until a commit is called.
What about nested transactions?
-------------------------------
Nested transactions are using database save points, which allow you to do a partial
rollback. Also, nested transactions cause a flush to the database, meaning that
the SQL statements are sent to the server.
When are partial rollbacks useful?
----------------------------------
Partial rollbacks can be useful for instance if you want to try to insert a user,
but the user already exists in the table. Then you can rollback the insert and
instead execute an update statement at the application level.
When is flushing useful?
------------------------
Explicitly forcing the flush of SQL statements to the database can be useful
if you need a value from the database (e.g. auto-incrementing primary keys),
and the application needs the primary key to continue. Also, they can be
useful to force integrity constraints to be checked by the database,
which may be needed by the database.
What happens with exceptions in nested transactions?
----------------------------------------------------
If an exception occurs in a nested transaction, first the save point will be
rolled back, and afterwards the entire transaction will be rolled back unless
the exception is caught.
For instance in the following code example, the entire transaction will be rolled back:
.. code:: python
@app.route('/')
def index():
# db operations 1 ....
with db.session.begin_nested():
# db operations 2 ....
raise Exception()
db.session.commit()
On the other hand, in the following example, the propagation of the exception is stopped,
and only the db operations 2 are rolled back, while db operations 1 are committed to
the database.
.. code:: python
@app.route('/')
def index():
# db operations 1 ....
try:
with db.session.begin_nested():
# db operations 2 ....
raise Exception()
db.session.commit()
except Exception:
db.session.rollback()
db.session.commit()
..
This file is part of Invenio.
Copyright (C) 2015-2018 CERN.
Invenio is free software; you can redistribute it and/or modify it
under the terms of the MIT License; see LICENSE file for more details.
Usage
=====
.. automodule:: invenio_db
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2015-2018 CERN.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.
"""Pytest configuration."""
import os
import pytest
from flask import Flask
from invenio_db.utils import alembic_test_context
@pytest.fixture(name="db")
def fixture_db():
"""Database fixture with session sharing."""
import invenio_db
from invenio_db import shared
db = invenio_db.db = shared.db = shared.SQLAlchemy(
metadata=shared.MetaData(naming_convention=shared.NAMING_CONVENTION)
)
return db
@pytest.fixture()
def app():
"""Flask application fixture."""
app = Flask(__name__)
app.config.update(
DB_VERSIONING=False,
DB_VERSIONING_USER_MODEL=None,
SQLALCHEMY_DATABASE_URI=os.environ.get(
"SQLALCHEMY_DATABASE_URI", "sqlite:///test.db"
),
ALEMBIC_CONTEXT=alembic_test_context(),
)
return app
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2015-2018 CERN.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.
"""Demo module."""
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2015-2018 CERN.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.
"""Child model."""
from invenio_db import db
from .parent import Parent
class Child(db.Model):
"""Child demo model."""
__tablename__ = "child"
pk = db.Column(db.Integer, primary_key=True)
fk = db.Column(db.Integer, db.ForeignKey(Parent.pk))
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2015-2018 CERN.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.
"""Parent model."""
from invenio_db import db
class Parent(db.Model):
"""Parent demo model."""
__tablename__ = "parent"
pk = db.Column(db.Integer, primary_key=True)
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2015-2018 CERN.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.
"""Normal and versioned models."""
from invenio_db import db
class UnversionedArticle(db.Model):
"""Unversioned test model."""
__tablename__ = "unversioned_article_a"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(length=50))
class VersionedArticle(db.Model):
"""Versioned test model."""
__tablename__ = "versioned_article_a"
__versioned__ = {}
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(length=50))
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2015-2018 CERN.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.
"""Normal and versioned models."""
from invenio_db import db
class UnversionedArticle(db.Model):
"""Unversioned test model."""
__tablename__ = "unversioned_article_b"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(length=50))
class VersionedArticle(db.Model):
"""Versioned test model."""
__tablename__ = "versioned_article_b"
__versioned__ = {}
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(length=50))
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2023 Graz University of Technology.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.
"""Test database integration layer."""
from importlib_metadata import EntryPoint
from werkzeug.utils import import_string
class MockEntryPoint(EntryPoint):
"""Mocking of entrypoint."""
def load(self):
"""Mock load entry point."""
if self.name == "importfail":
raise ImportError()
else:
return import_string(self.name)
def _mock_entry_points(name):
def fn(group):
data = {
"invenio_db.models": [
MockEntryPoint(name="demo.child", value="demo.child", group="test"),
MockEntryPoint(name="demo.parent", value="demo.parent", group="test"),
],
"invenio_db.models_a": [
MockEntryPoint(
name="demo.versioned_a", value="demo.versioned_a", group="test"
),
],
"invenio_db.models_b": [
MockEntryPoint(
name="demo.versioned_b", value="demo.versioned_b", group="test"
),
],
}
if group:
return data.get(group, [])
if name:
return {name: data.get(name)}
return data
return fn
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2015-2018 CERN.
# Copyright (C) 2022 RERO.
# Copyright (C) 2024 Graz University of Technology.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.
"""Test database integration layer."""
from unittest.mock import patch
import pytest
import sqlalchemy as sa
from flask import Flask
from mocks import _mock_entry_points
from sqlalchemy import inspect
from sqlalchemy.exc import IntegrityError
from sqlalchemy_continuum import VersioningManager, remove_versioning
from sqlalchemy_utils.functions import create_database, drop_database
from invenio_db import InvenioDB
from invenio_db.cli import db as db_cmd
from invenio_db.shared import NAMING_CONVENTION, MetaData, SQLAlchemy
from invenio_db.utils import drop_alembic_version_table, has_table
def test_init(db, app):
"""Test extension initialization."""
class Demo(db.Model):
__tablename__ = "demo"
pk = sa.Column(sa.Integer, primary_key=True)
class Demo2(db.Model):
__tablename__ = "demo2"
pk = sa.Column(sa.Integer, primary_key=True)
fk = sa.Column(sa.Integer, sa.ForeignKey(Demo.pk))
app.config["DB_VERSIONING"] = False
InvenioDB(app, entry_point_group=False, db=db)
with app.app_context():
db.create_all()
assert len(db.metadata.tables) == 2
# Test foreign key constraint checking
d1 = Demo()
db.session.add(d1)
db.session.flush()
d2 = Demo2(fk=d1.pk)
db.session.add(d2)
db.session.commit()
with app.app_context():
# Fails fk check
d3 = Demo2(fk=10)
db.session.add(d3)
pytest.raises(IntegrityError, db.session.commit)
db.session.rollback()
with app.app_context():
db.session.query(Demo2).delete()
db.session.query(Demo).delete()
db.session.commit()
db.drop_all()
def test_alembic(db, app):
"""Test alembic recipes."""
ext = InvenioDB(app, entry_point_group=False, db=db)
with app.app_context():
if db.engine.name == "sqlite":
raise pytest.skip("Upgrades are not supported on SQLite.")
ext.alembic.upgrade()
ext.alembic.downgrade(target="96e796392533")
def test_naming_convention(db, app):
"""Test naming convention."""
InvenioDB(app, entry_point_group=False, db=db)
cfg = dict(
DB_VERSIONING=True,
DB_VERSIONING_USER_MODEL=None,
SQLALCHEMY_DATABASE_URI=app.config["SQLALCHEMY_DATABASE_URI"],
)
with app.app_context():
if db.engine.name == "sqlite":
raise pytest.skip("Upgrades are not supported on SQLite.")
def model_factory(base):
"""Create test models."""
class Master(base):
__tablename__ = "master"
pk = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.String(100), unique=True)
city = sa.Column(sa.String(100), index=True)
active = sa.Column(sa.Boolean(name="active"), server_default="1")
class Slave(base):
__tablename__ = "slave"
pk = sa.Column(sa.Integer, primary_key=True)
fk = sa.Column(sa.Integer, sa.ForeignKey(Master.pk))
code = sa.Column(sa.Integer, index=True, unique=True)
source = sa.Column(sa.String(100))
__table_args__ = (
sa.Index(None, source),
# do not add anything after
getattr(base, "__table_args__", {}),
)
return Master, Slave
source_db = SQLAlchemy(
metadata=MetaData(
naming_convention={
"ix": "source_ix_%(table_name)s_%(column_0_label)s",
"uq": "source_uq_%(table_name)s_%(column_0_name)s",
"ck": "source_ck_%(table_name)s_%(constraint_name)s",
"fk": "source_fk_%(table_name)s_%(column_0_name)s_"
"%(referred_table_name)s",
"pk": "source_pk_%(table_name)s",
}
),
)
source_app = Flask("source_app")
source_app.config.update(**cfg)
source_models = model_factory(source_db.Model)
source_ext = InvenioDB(
source_app,
entry_point_group=False,
db=source_db,
versioning_manager=VersioningManager(),
)
with source_app.app_context():
source_db.metadata.bind = source_db.engine
source_db.create_all()
source_ext.alembic.stamp("dbdbc1b19cf2")
assert not source_ext.alembic.compare_metadata()
source_constraints = set(
[
cns
for model in source_models
for cns in list(model.__table__.constraints)
+ list(model.__table__.indexes)
]
)
remove_versioning(manager=source_ext.versioning_manager)
target_db = SQLAlchemy(metadata=MetaData(naming_convention=NAMING_CONVENTION))
target_app = Flask("target_app")
target_app.config.update(**cfg)
target_models = model_factory(target_db.Model)
target_ext = InvenioDB(
target_app,
entry_point_group=False,
db=target_db,
versioning_manager=VersioningManager(),
)
with target_app.app_context():
target_db.metadata.bind = target_db.engine
assert target_ext.alembic.compare_metadata()
target_ext.alembic.upgrade("35c1075e6360")
assert not target_ext.alembic.compare_metadata()
target_db.drop_all()
target_constraints = set(
[
cns.name
for model in target_models
for cns in list(model.__table__.constraints)
+ list(model.__table__.indexes)
]
)
remove_versioning(manager=target_ext.versioning_manager)
assert source_constraints ^ target_constraints
def test_transaction(db, app):
"""Test transcation commit and rollback.
This is necessary to make sure that pysqlite hacks are properly working.
"""
class Demo(db.Model):
__tablename__ = "demo"
pk = sa.Column(sa.Integer, primary_key=True)
app.config["DB_VERSIONING"] = False
InvenioDB(app, entry_point_group=False, db=db)
with app.app_context():
db.drop_all()
db.create_all()
assert len(db.metadata.tables) == 1
# Test rollback
with app.app_context():
d1 = Demo()
d1.pk = 1
db.session.add(d1)
db.session.rollback()
with app.app_context():
res = Demo.query.all()
assert len(res) == 0
db.session.rollback()
# Test nested session rollback
with app.app_context():
with db.session.begin_nested():
d1 = Demo()
d1.pk = 1
db.session.add(d1)
db.session.rollback()
with app.app_context():
res = Demo.query.all()
assert len(res) == 0
db.session.rollback()
# Test commit
with app.app_context():
d1 = Demo()
d1.pk = 1
db.session.add(d1)
db.session.commit()
with app.app_context():
res = Demo.query.all()
assert len(res) == 1
assert res[0].pk == 1
db.session.commit()
with app.app_context():
db.drop_all()
@patch("importlib_metadata.entry_points", _mock_entry_points("invenio_db.models"))
def test_entry_points(db, app):
"""Test entrypoints loading."""
InvenioDB(app, db=db)
runner = app.test_cli_runner()
assert len(db.metadata.tables) == 2
# Test merging a base another file.
with runner.isolated_filesystem():
result = runner.invoke(db_cmd, [])
assert result.exit_code == 0
result = runner.invoke(db_cmd, ["init"])
assert result.exit_code == 0
result = runner.invoke(db_cmd, ["destroy", "--yes-i-know"])
assert result.exit_code == 0
result = runner.invoke(db_cmd, ["init"])
assert result.exit_code == 0
result = runner.invoke(db_cmd, ["create", "-v"])
assert result.exit_code == 0
result = runner.invoke(db_cmd, ["destroy", "--yes-i-know"])
assert result.exit_code == 0
result = runner.invoke(db_cmd, ["init"])
assert result.exit_code == 0
result = runner.invoke(db_cmd, ["create", "-v"])
assert result.exit_code == 1
result = runner.invoke(db_cmd, ["create", "-v"])
assert result.exit_code == 1
result = runner.invoke(db_cmd, ["drop", "-v", "--yes-i-know"])
assert result.exit_code == 0
result = runner.invoke(db_cmd, ["drop"])
assert result.exit_code == 1
result = runner.invoke(db_cmd, ["drop", "-v", "--yes-i-know"])
assert result.exit_code == 0
result = runner.invoke(db_cmd, ["drop", "create"])
assert result.exit_code == 1
result = runner.invoke(db_cmd, ["drop", "--yes-i-know", "create"])
assert result.exit_code == 1
result = runner.invoke(db_cmd, ["destroy", "--yes-i-know"])
assert result.exit_code == 0
result = runner.invoke(db_cmd, ["init"])
assert result.exit_code == 0
result = runner.invoke(db_cmd, ["destroy", "--yes-i-know"])
assert result.exit_code == 0
result = runner.invoke(db_cmd, ["init"])
assert result.exit_code == 0
result = runner.invoke(db_cmd, ["destroy", "--yes-i-know"])
assert result.exit_code == 0
result = runner.invoke(db_cmd, ["init"])
assert result.exit_code == 0
result = runner.invoke(db_cmd, ["drop", "-v", "--yes-i-know"])
assert result.exit_code == 1
result = runner.invoke(db_cmd, ["create", "-v"])
assert result.exit_code == 1
result = runner.invoke(db_cmd, ["drop", "-v", "--yes-i-know"])
assert result.exit_code == 0
def test_local_proxy(app, db):
"""Test local proxy filter."""
InvenioDB(app, db=db)
with app.app_context():
query = db.select(
*[
db.literal("hello") != db.bindparam("a"),
db.literal(0) <= db.bindparam("x"),
db.literal("2") == db.bindparam("y"),
db.literal(None).is_(db.bindparam("z")),
]
)
result = db.session.execute(
query,
{
"a": "world",
"x": 1,
"y": "2",
"z": None,
},
).fetchone()
assert result == (True, True, True, True)
def test_db_create_alembic_upgrade(app, db):
"""Test that 'db create/drop' and 'alembic create' are compatible.
It also checks that "alembic_version" table is processed properly
as it is normally created by alembic and not by sqlalchemy.
"""
app.config["DB_VERSIONING"] = True
ext = InvenioDB(
app, entry_point_group=None, db=db, versioning_manager=VersioningManager()
)
with app.app_context():
try:
if db.engine.name == "sqlite":
raise pytest.skip("Upgrades are not supported on SQLite.")
db.drop_all()
runner = app.test_cli_runner()
# Check that 'db create' creates the same schema as
# 'alembic upgrade'.
result = runner.invoke(db_cmd, ["create", "-v"])
assert result.exit_code == 0
assert has_table(db.engine, "transaction")
assert ext.alembic.migration_context._has_version_table()
# Note that compare_metadata does not detect additional sequences
# and constraints.
# TODO fix failing test on mysql
if db.engine.name != "mysql":
assert not ext.alembic.compare_metadata()
ext.alembic.upgrade()
assert has_table(db.engine, "transaction")
ext.alembic.downgrade(target="96e796392533")
assert inspect(db.engine).get_table_names() == ["alembic_version"]
# Check that 'db drop' removes all tables, including
# 'alembic_version'.
ext.alembic.upgrade()
result = runner.invoke(db_cmd, ["drop", "-v", "--yes-i-know"])
assert result.exit_code == 0
assert len(inspect(db.engine).get_table_names()) == 0
ext.alembic.upgrade()
db.drop_all()
drop_alembic_version_table()
assert len(inspect(db.engine).get_table_names()) == 0
finally:
drop_database(str(db.engine.url.render_as_string(hide_password=False)))
remove_versioning(manager=ext.versioning_manager)
create_database(str(db.engine.url.render_as_string(hide_password=False)))
# -*- coding: utf-8 -*-
#
# Copyright (C) 2024 CERN.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.
"""Unit of work tests."""
from unittest.mock import MagicMock
from invenio_db import InvenioDB
from invenio_db.uow import ModelCommitOp, Operation, UnitOfWork
def test_uow_lifecycle(db, app):
InvenioDB(app, entry_point_group=False, db=db)
with app.app_context():
mock_op = MagicMock()
# Test normal lifecycle
with UnitOfWork(db.session) as uow:
uow.register(mock_op)
uow.commit()
mock_op.on_register.assert_called_once()
mock_op.on_commit.assert_called_once()
mock_op.on_post_commit.assert_called_once()
mock_op.on_exception.assert_not_called()
mock_op.on_rollback.assert_not_called()
mock_op.on_post_rollback.assert_not_called()
# Test rollback lifecycle
mock_op.reset_mock()
with UnitOfWork(db.session) as uow:
uow.register(mock_op)
uow.rollback()
mock_op.on_register.assert_called_once()
mock_op.on_commit.assert_not_called()
mock_op.on_post_commit.assert_not_called()
# on_exception is not called (since there was no exception)
mock_op.on_exception.assert_not_called()
# rest of the rollback lifecycle is called
mock_op.on_rollback.assert_called_once()
mock_op.on_post_rollback.assert_called_once()
# Test exception lifecycle
mock_op.reset_mock()
try:
with UnitOfWork(db.session) as uow:
uow.register(mock_op)
raise Exception()
except Exception:
pass
mock_op.on_register.assert_called_once()
mock_op.on_commit.assert_not_called()
mock_op.on_post_commit.assert_not_called()
# both exception and rollback lifecycle are called
mock_op.on_exception.assert_called_once()
mock_op.on_rollback.assert_called_once()
mock_op.on_post_rollback.assert_called_once()
def test_uow_transactions(db, app):
"""Test transaction behavior with the Unit of Work."""
class Data(db.Model):
value = db.Column(db.String(100), primary_key=True)
InvenioDB(app, entry_point_group=False, db=db)
rollback_side_effect = MagicMock()
post_rollback_side_effect = MagicMock()
class CleanUpOp(Operation):
def on_exception(self, uow, exception):
uow.session.add(Data(value="clean-up"))
on_rollback = rollback_side_effect
on_post_rollback = post_rollback_side_effect
with app.app_context():
db.create_all()
# Test normal lifecycle
with UnitOfWork(db.session) as uow:
uow.register(ModelCommitOp(Data(value="persisted")))
uow.commit()
data = db.session.query(Data).all()
assert len(data) == 1
assert data[0].value == "persisted"
# Test rollback lifecycle
with UnitOfWork(db.session) as uow:
uow.register(ModelCommitOp(Data(value="not-persisted")))
uow.register(CleanUpOp())
uow.rollback()
data = db.session.query(Data).all()
assert len(data) == 1
assert data[0].value == "persisted"
rollback_side_effect.assert_called_once()
post_rollback_side_effect.assert_called_once()
# Test exception lifecycle
rollback_side_effect.reset_mock()
post_rollback_side_effect.reset_mock()
try:
with UnitOfWork(db.session) as uow:
uow.register(ModelCommitOp(Data(value="not-persisted")))
uow.register(CleanUpOp())
raise Exception()
except Exception:
pass
data = db.session.query(Data).all()
assert len(data) == 2
assert set([d.value for d in data]) == {"persisted", "clean-up"}
rollback_side_effect.assert_called_once()
post_rollback_side_effect.assert_called_once()
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2017-2018 CERN.
# Copyright (C) 2023 Graz University of Technology.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.
"""Test DB utilities."""
import pytest
import sqlalchemy as sa
from sqlalchemy_continuum import remove_versioning
from sqlalchemy_utils.types import StringEncryptedType
from invenio_db import InvenioDB
from invenio_db.utils import (
rebuild_encrypted_properties,
versioning_model_classname,
versioning_models_registered,
)
def test_rebuild_encrypted_properties(db, app):
old_secret_key = "SECRET_KEY_1"
new_secret_key = "SECRET_KEY_2"
app.secret_key = old_secret_key
def _secret_key():
return app.config.get("SECRET_KEY").encode("utf-8")
class Demo(db.Model):
__tablename__ = "demo"
pk = db.Column(sa.Integer, primary_key=True)
et = db.Column(
StringEncryptedType(length=255, type_in=db.Unicode, key=_secret_key),
nullable=False,
)
InvenioDB(app, entry_point_group=False, db=db)
with app.app_context():
db.create_all()
d1 = Demo(et="something")
db.session.add(d1)
db.session.commit()
app.secret_key = new_secret_key
with app.app_context():
with pytest.raises(ValueError):
db.session.query(Demo).all()
with pytest.raises(AttributeError):
rebuild_encrypted_properties(old_secret_key, Demo, ["nonexistent"], db)
assert app.secret_key == new_secret_key
with app.app_context():
with pytest.raises(ValueError):
db.session.query(Demo).all()
rebuild_encrypted_properties(old_secret_key, Demo, ["et"], db)
d1_after = db.session.query(Demo).first()
assert d1_after.et == "something"
with app.app_context():
db.drop_all()
def test_versioning_model_classname(db, app):
"""Test the versioning model utilities."""
class FooClass(db.Model):
__versioned__ = {}
pk = db.Column(db.Integer, primary_key=True)
app.config["DB_VERSIONING"] = True
idb = InvenioDB(app)
manager = idb.versioning_manager
manager.options["use_module_name"] = True
assert versioning_model_classname(manager, FooClass) == "Test_UtilsFooClassVersion"
manager.options["use_module_name"] = False
assert versioning_model_classname(manager, FooClass) == "FooClassVersion"
assert versioning_models_registered(manager, db.Model)
remove_versioning(manager=manager)
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2015-2018 CERN.
# Copyright (C) 2022 RERO.
# Copyright (C) 2024 Graz University of Technology.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.
"""Versioning tests for Invenio-DB"""
from unittest.mock import patch
import pytest
from mocks import _mock_entry_points
from sqlalchemy_continuum import VersioningManager, remove_versioning
from invenio_db import InvenioDB
@patch("importlib_metadata.entry_points", _mock_entry_points("invenio_db.models_a"))
def test_disabled_versioning(db, app):
"""Test SQLAlchemy-Continuum with disabled versioning."""
InvenioDB(app, entry_point_group="invenio_db.models_a")
with app.app_context():
assert 2 == len(db.metadata.tables)
@pytest.mark.parametrize("versioning,tables", [(False, 1), (True, 3)])
def test_disabled_versioning_with_custom_table(db, app, versioning, tables):
"""Test SQLAlchemy-Continuum table loading."""
app.config["DB_VERSIONING"] = versioning
# this class has to be defined here, because the the db has to be the db
# from the fixture. using it "from invenio_db import db" is not working
class EarlyClass(db.Model):
__versioned__ = {}
pk = db.Column(db.Integer, primary_key=True)
idb = InvenioDB(
app, entry_point_group=None, db=db, versioning_manager=VersioningManager()
)
with app.app_context():
db.drop_all()
db.create_all()
ec = EarlyClass()
ec.pk = 1
db.session.add(ec)
db.session.commit()
assert tables == len(db.metadata.tables)
db.drop_all()
if versioning:
remove_versioning(manager=idb.versioning_manager)
@patch("importlib_metadata.entry_points", _mock_entry_points("invenio_db.models_b"))
def test_versioning(db, app):
"""Test SQLAlchemy-Continuum enabled versioning."""
app.config["DB_VERSIONING"] = True
idb = InvenioDB(
app,
entry_point_group="invenio_db.models_b",
db=db,
versioning_manager=VersioningManager(),
)
with app.app_context():
assert 4 == len(db.metadata.tables)
db.create_all()
from demo.versioned_b import UnversionedArticle, VersionedArticle
original_name = "original_name"
versioned = VersionedArticle()
unversioned = UnversionedArticle()
versioned.name = original_name
unversioned.name = original_name
db.session.add(versioned)
db.session.add(unversioned)
db.session.commit()
assert unversioned.name == versioned.name
modified_name = "modified_name"
versioned.name = modified_name
unversioned.name = modified_name
db.session.commit()
assert unversioned.name == modified_name
assert versioned.name == modified_name
assert versioned.versions[0].name == original_name
assert versioned.versions[1].name == versioned.name
versioned.versions[0].revert()
db.session.commit()
assert unversioned.name == modified_name
assert versioned.name == original_name
versioned.versions[1].revert()
db.session.commit()
assert unversioned.name == versioned.name
with app.app_context():
db.drop_all()
remove_versioning(manager=idb.versioning_manager)
def test_versioning_without_versioned_tables(db, app):
"""Test SQLAlchemy-Continuum without versioned tables."""
app.config["DB_VERSIONING"] = True
idb = InvenioDB(
app, db=db, entry_point_group=None, versioning_manager=VersioningManager()
)
with app.app_context():
assert "transaction" in db.metadata.tables
remove_versioning(manager=idb.versioning_manager)