Skip to content

Commit

Permalink
support config sections (#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidism authored Jun 16, 2024
2 parents 693e517 + 13d68d3 commit bcb957a
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 22 deletions.
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ Unreleased
- The constructor args `run_mkdir` and `command_name` are keyword only. {pr}`27`
- Deprecate the `init_app` args `run_mkdir` and `command_name`. They can be
passed to the constructor instead. {pr}`27`
- Dict values in the `ALEMBIC` config are treated as Alembic config sections,
allowing use of Alembic features like `post_write_hooks`. {pr}`28`

## Version 3.0.1

Expand Down
85 changes: 84 additions & 1 deletion docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@
Configuration for Alembic and its migrations uses the following Flask config
keys.

```{module} flask_alembic.config
```{currentmodule} flask_alembic
```

```{data} ALEMBIC
A dictionary containing general configuration, mostly used by
{class}`~alembic.config.Config` and {class}`~alembic.script.ScriptDirectory`.
See Alembic's docs on [config][alembic-config].
If a value is a dict, the key will be equivalent to a section name in
``alembic.ini``, and the value will be the items in the section. This allows
using Alembic's [post_write_hooks], for example.
.. versionchanged:: 3.1
Treat dict values as sections.
```

```{data} ALEMBIC_CONTEXT
Expand All @@ -28,3 +35,79 @@ otherwise default to `False` in Alembic.

[alembic-config]: https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
[alembic-runtime]: https://alembic.sqlalchemy.org/en/latest/api/runtime.html#runtime-objects
[post_write_hooks]: https://alembic.sqlalchemy.org/en/latest/autogenerate.html#applying-post-processing-and-python-code-formatters-to-generated-revisions

## Post Processing Revisions

It's possible to tell Alembic to run one or more commands on a generated
migration file. See [Alembic's docs][post_write_hooks] for full details.
Here are some useful examples.

### black

Run the [black] formatter.

[black]: https://black.readthedocs.io

```python
ALEMBIC = {
"post_write_hooks": {
"hooks": "black",
"pre-commit.type": "console_scripts",
"pre-commit.entrypoint": "black",
}
}
```

### ruff

Run the [ruff] formatter.

[ruff]: https://docs.astral.sh/ruff/formatter/

```python
ALEMBIC = {
"post_write_hooks": {
"hooks": "ruff",
"pre-commit.type": "console_scripts",
"pre-commit.entrypoint": "ruff",
"pre-commit.options": "format REVISION_SCRIPT_FILENAME",
}
}
```

### pre-commit

Run all configured [pre-commit] hooks.

[pre-commit]: https://pre-commit.com

```python
ALEMBIC = {
"post_write_hooks": {
"hooks": "pre-commit",
"pre-commit.type": "console_scripts",
"pre-commit.entrypoint": "pre-commit",
"pre-commit.options": "run --files REVISION_SCRIPT_FILENAME",
}
}
```

### git add

Add the file to git so that you don't forget when committing. This can be run
after other hooks so that all the changes are staged.

```python
ALEMBIC = {
"post_write_hooks": {
"hooks": "pre-commit, git",
"pre-commit.type": "console_scripts",
"pre-commit.entrypoint": "pre-commit",
"pre-commit.options": "run --files REVISION_SCRIPT_FILENAME",
"git.type": "exec",
"git.executable": "git",
"git.options": "add REVISION_SCRIPT_FILENAME",
}
}
```
45 changes: 24 additions & 21 deletions src/flask_alembic/extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,38 +208,41 @@ def config(self) -> Config:
"""
cache = self._get_cache()

if cache.config is None:
cache.config = c = Config()
if cache.config is not None:
return cache.config

script_location = current_app.config["ALEMBIC"]["script_location"]
cache.config = c = Config()
script_location = current_app.config["ALEMBIC"]["script_location"]

if not os.path.isabs(script_location) and ":" not in script_location:
script_location = os.path.join(current_app.root_path, script_location)
if not os.path.isabs(script_location) and ":" not in script_location:
script_location = os.path.join(current_app.root_path, script_location)

version_locations = [script_location]
version_locations = [script_location]

for item in current_app.config["ALEMBIC"]["version_locations"]:
version_location = item if isinstance(item, str) else item[1]
for item in current_app.config["ALEMBIC"]["version_locations"]:
version_location = item if isinstance(item, str) else item[1]

if not os.path.isabs(version_location) and ":" not in version_location:
version_location = os.path.join(
current_app.root_path, version_location
)
if not os.path.isabs(version_location) and ":" not in version_location:
version_location = os.path.join(current_app.root_path, version_location)

version_locations.append(version_location)
version_locations.append(version_location)

c.set_main_option("script_location", script_location)
c.set_main_option("version_locations", ",".join(version_locations))
c.set_main_option("script_location", script_location)
c.set_main_option("version_locations", ",".join(version_locations))

for key, value in current_app.config["ALEMBIC"].items():
if key in ("script_location", "version_locations"):
continue
for key, value in current_app.config["ALEMBIC"].items():
if key in ("script_location", "version_locations"):
continue

if isinstance(value, dict):
for inner_key, inner_value in value.items():
c.set_section_option(key, inner_key, inner_value)
else:
c.set_main_option(key, value)

if len(self.metadatas) > 1:
# Add the names used by the multidb template.
c.set_main_option("databases", ", ".join(self.metadatas))
if len(self.metadatas) > 1:
# Add the names used by the multidb template.
c.set_main_option("databases", ", ".join(self.metadatas))

return cache.config

Expand Down

0 comments on commit bcb957a

Please sign in to comment.