-
-
Notifications
You must be signed in to change notification settings - Fork 301
Closed
Description
Describe the bug
Importing local packages creates NameError in version 1.7.2 was not the case in 1.6.5
Expected behavior
Ability to use locally installed packages in alembic revisions
To Reproduce
Local Package "fun_stuff" installed via -e
The details on this file come from replaceable objects cookbook
fun_stuff/alembic_utils.py
from typing import Any, Optional
from alembic.operations import Operations, MigrateOperation
class ReplaceableObject:
def __init__(self, name: str, sqltext: str) -> None:
self.name = name
self.sqltext = sqltext
class ReversibleOp(MigrateOperation):
"""This is the base of our “replaceable” operation, which includes not just a base operation for emitting
CREATE and DROP instructions on a ReplaceableObject, it also assumes a certain model of “reversibility” which
makes use of references to other migration files in order to refer to the “previous” version of an object.
https://alembic.sqlalchemy.org/en/latest/cookbook.html#replaceable-objects
"""
def __init__(self, target: ReplaceableObject) -> None:
self.target = target
@classmethod
def invoke_for_target(cls, operations: Operations, target: ReplaceableObject):
op = cls(target)
return operations.invoke(op)
def reverse(self):
raise NotImplementedError
@classmethod
def _get_object_from_version(cls, operations: Operations, ident: str) -> Any:
version, objectname = ident.split(".")
module = operations.get_context().script.get_revision(version).module
return getattr(module, objectname)
@classmethod
def replace(
cls,
operations: Operations,
target: ReplaceableObject,
replaces: Optional[str] = None,
replace_with: Optional[str] = None,
) -> None:
if replaces is None and replace_with is None:
raise TypeError("replaces or replace_with is required")
old_obj = cls._get_object_from_version(
operations,
replaces if replaces is not None else replace_with,
)
drop_old = cls(old_obj).reverse()
create_new = cls(target)
operations.invoke(drop_old)
operations.invoke(create_new)
"""To create usable operations from this base, we will build a series of stub classes and use
[Operations.register_operation()](https://alembic.sqlalchemy.org/en/latest/ops.html#alembic.operations.Operations.register_operation)
to make them part of the op.* namespace"""
@Operations.register_operation("create_view", "invoke_for_target")
@Operations.register_operation("replace_view", "replace")
class CreateViewOp(ReversibleOp):
def reverse(self):
return DropViewOp(self.target)
@Operations.register_operation("drop_view", "invoke_for_target")
class DropViewOp(ReversibleOp):
def reverse(self):
return CreateViewOp(self.target)
@Operations.implementation_for(CreateViewOp)
def create_view(operations: Operations, operation: ReversibleOp) -> None:
operations.execute(f"CREATE VIEW {operation.target.name} AS {operation.target.sqltext}")
@Operations.implementation_for(DropViewOp)
def drop_view(operations: Operations, operation: ReversibleOp) -> None:
operations.execute(f"DROP VIEW {operation.target.name}")24627210e3e6_swap_old_with_new.py
"""adjust view
Revision ID: 24627210e3e6
Revises: bcf089c643e3
Create Date: 2021-07-16 17:03:05.673405
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy import orm
from fun_stuff import alembic_utils
# revision identifiers, used by Alembic.
revision = '24627210e3e6'
down_revision = 'bcf089c643e3'
branch_labels = None
depends_on = None
new_view = alembic_utils.ReplaceableObject(
name="E_CrazyFunView",
sqltext="""SELECT * FROM WowTable""",
)
old_view = alembic_utils.ReplaceableObject(
name="E_CrazyFunView",
sqltext="""SELECT * FROM NotWowTable""",
)
def upgrade():
op.drop_view(old_view)
op.create_view(new_view)
def downgrade():
op.drop_view(new_view)
op.create_view(old_view)The error below occurs when running
alembic history
Error
Traceback (most recent call last):
File "/usr/local/bin/alembic", line 8, in <module>
sys.exit(main())
File "/usr/local/lib/python3.8/site-packages/alembic/config.py", line 588, in main
CommandLine(prog=prog).main(argv=argv)
File "/usr/local/lib/python3.8/site-packages/alembic/config.py", line 582, in main
self.run_cmd(cfg, options)
File "/usr/local/lib/python3.8/site-packages/alembic/config.py", line 559, in run_cmd
fn(
File "/usr/local/lib/python3.8/site-packages/alembic/command.py", line 461, in history
_display_history(config, script, base, head)
File "/usr/local/lib/python3.8/site-packages/alembic/command.py", line 429, in _display_history
for sc in script.walk_revisions(
File "/usr/local/lib/python3.8/site-packages/alembic/script/base.py", line 277, in walk_revisions
for rev in self.revision_map.iterate_revisions(
File "/usr/local/lib/python3.8/site-packages/alembic/script/revision.py", line 793, in iterate_revisions
revisions, heads = fn(
File "/usr/local/lib/python3.8/site-packages/alembic/script/revision.py", line 1393, in _collect_upgrade_revisions
targets: Collection["Revision"] = self._parse_upgrade_target(
File "/usr/local/lib/python3.8/site-packages/alembic/script/revision.py", line 1193, in _parse_upgrade_target
return self.get_revisions(target)
File "/usr/local/lib/python3.8/site-packages/alembic/script/revision.py", line 527, in get_revisions
resolved_id, branch_label = self._resolve_revision_number(
File "/usr/local/lib/python3.8/site-packages/alembic/script/revision.py", line 747, in _resolve_revision_number
self._revision_map
File "/usr/local/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 1113, in __get__
obj.__dict__[self.__name__] = result = self.fget(obj)
File "/usr/local/lib/python3.8/site-packages/alembic/script/revision.py", line 189, in _revision_map
for revision in self._generator():
File "/usr/local/lib/python3.8/site-packages/alembic/script/base.py", line 136, in _load_revisions
script = Script._from_filename(self, vers, file_)
File "/usr/local/lib/python3.8/site-packages/alembic/script/base.py", line 999, in _from_filename
module = util.load_python_file(dir_, filename)
File "/usr/local/lib/python3.8/site-packages/alembic/util/pyfiles.py", line 92, in load_python_file
module = load_module_py(module_id, path)
File "/usr/local/lib/python3.8/site-packages/alembic/util/pyfiles.py", line 108, in load_module_py
spec.loader.exec_module(module) # type: ignore
File "<frozen importlib._bootstrap_external>", line 843, in exec_module
File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
File "/src/employees/alembic/versions/24627210e3e6_swap_old_with_new.py", line 13, in <module>
from fun_stuff import alembic_utils
File "/src/fun_stuff/alembic_utils.py", line 67, in <module>
class CreateViewOp(ReversibleOp):
File "/usr/local/lib/python3.8/site-packages/alembic/operations/base.py", line 163, in register
exec(func_text, globals_, lcl)
File "<string>", line 1, in <module>
NameError: name 'fun_stuff' is not defined
Versions.
- OS: Python3.8-buster image
- Python: 3.8.11
- Alembic: 1.7.2
- SQLAlchemy: 1.4.23
- Database: MSSQL
Additional context
This is not a problem in alembic 1.6.5. I love the project!