Comments (10)
Thanks for this improvement, we literally went from 10 minutes to 4 minutes in our CI just upgrading to alembic_utils 0.7.7 🎉
FYI we are using both libraries, pytest-alembic and alembic-utils !
from alembic_utils.
Love to see it, just upgraded and all seems well for our migrations. thanks!
from alembic_utils.
I see you're a maintainer of pytest-alembic
. Could this be solved there using a pytest fixture to reset the contents of comparator between tests/calls into env.py
?
from alembic_utils.
Probably not, the internal comparators.dispatch_for calls are at the module-level, and so resetting to pre-env.py state would end up being pretty wonky, if at all possible.
All pytest-alembic is doing is basically just calling parts of alembic's public api for programmatic use of alembic. The fact that using alembic's "command" API happens to reevaluate the env.py
is, i guess, a kind of implementation detail of how alembic works, so other users of that api in combination with alembic_utils will run into the same thing.
I'd consider this more of a bug in my env.py for not being idempotent (Thus my feature request to enable one to write an env.py
that is idempotent with this lib). Moving this call to, i.e. the models package would be another option to achieve the same thing but would unfortunately tie my models to alembic, which they are otherwise not.
As I kind of suggested above, alembic's internals seem to really want you to register comparators statically, and I haven't been able to find any hooks at the env.py level which would only get invoked once
from alembic_utils.
ok, makes sense
If you have any ideas for a backwards compatible way to refactor alembic_utils to avoid this issue I'd be happy to review. Getting the extensions to play nice together would be great
from alembic_utils.
class ReplaceableEntityRegistry:
def __init__(self):
self.entities: List[Type[ReplaceableEntity]] = []
self.schemas: List[str] = []
self.exclude_schemas: List[str] = []
self.entity_typesList[Type[ReplaceableEntity]] = []
def clear(self):
self.entities.clear()
self.schemas.clear()
self.exclude_schemas.clear()
self.entity_types.clear()
def register(
self,
entities: List[T],
schemas: Optional[List[str]] = None,
exclude_schemas: Optional[List[str]] = None,
entity_types: Optional[List[Type[ReplaceableEntity]]] = None,
) -> None:
entities =
self.entities.extend(entites)
if schemas:
self.schemas.extend(schemas)
if exclude_schemas:
self.exclude_schemas.extend(exclude_schemas)
if entity_types:
if not self.entity_types:
self.entity_types.extend(entity_types)
else:
self.entity_types.extend(collect_subclasses(alembic_utils, ReplaceableEntity))
registry = ReplaceableEntityRegistry()
def register_entities(
entities: List[T],
schemas: Optional[List[str]] = None,
exclude_schemas: Optional[List[str]] = None,
entity_types: Optional[List[Type[ReplaceableEntity]]] = None,
) -> None:
registry.register(entities, schemas, exclude_schemas, entity_types)
@comparators.dispatch_for("schema")
def compare_registered_entities(
autogen_context: AutogenContext,
upgrade_ops,
sqla_schemas: Optional[List[Optional[str]]],
):
# identical to the inline function now, but uses of entities, schemas, exclude_schemas, and entity_types
# all become prefixed with `registry.`
This is the method that seems most alike to how alembic itself works. The main semantic difference is that comparators.dispatch_for
is registered at the module level (like how alembic itself does it), so there's only
ever one call to
As a fringe benefit (at least imo) this would make it easier to do other things which are neater by calling register_entites more than once, like
import foo.models
import bar models
register_entities(collect_instances(foo.models, PGView))
register_entities(collect_instances(bar.models, PGView))
I'd be happy to make a PR to this effect if you were into it
Alternatively, you could avoid using dispatch_for
and do it manually, enabling a check
from alembic import comparators
def register_entities(...) -> None:
def compare_registered_entities(...):
registered_comparators = comparators._registry[("schema", "default")]
names = {c.__name__ for c in registered_comparators}
if compare_registered_entities.__name__ not in names:
registered_comparators.append(compare_registered_entities) # or vice versa where you overwrite it
this is kind of bad for a few reasons, but is more similar to how it works today
Alternatively, i could imagine something like
def register_entities(
entities: List[T],
schemas: Optional[List[str]] = None,
exclude_schemas: Optional[List[str]] = None,
entity_types: Optional[List[Type[ReplaceableEntity]]] = None,
) -> None:
dispatcher = comparators.dispatch_for("schema")
compare_registered_entities = compare_registered_entities_factory(entities, schemas, exclude_schemas, entity_types)
dispatcher(compare_registered_entities)
def compare_registered_entities_factory(entities, schemas, exclude_schemas, entity_types):
def compare_registered_entities(
autogen_context: AutogenContext,
upgrade_ops,
sqla_schemas: Optional[List[Optional[str]]],
):
# identical to the inline function now, but not decorated
return compare_registered_entities
but this has a bunch of other drawbacks (which i can get into, if you really dont like the first solution), which means it's not ideal.
from alembic_utils.
thanks for taking the time to write that up 🙌
The ReplaceableEntityRegistry
solution looks great
I'd be happy to make a PR to this effect if you were into it
yes please
Any chance you could also tweak the logic in ReplaceableEntityRegistry.register
to deduplicate entities
, schemas
, exclude_schemas
, and entity_types
in case it is called multiple times?
from alembic_utils.
great! I'll see if I can get to that sometime later today!
from alembic_utils.
thanks! your patch is available in alembic_utils==0.7.7
from alembic_utils.
Thanks for doing this! It has saved me quite a few manual hacks!
from alembic_utils.
Related Issues (20)
- [Question] Using Alembic Utils without SQL Alchemy models HOT 3
- [QUESTION] compatibility with sqlalchemy_utils.functions.create_database HOT 2
- Compatibility with SQLAlchemy 2.0 HOT 4
- How to add a function with an " text[] default array['text', 'text2']"? HOT 1
- PGExtension replace `create` with `create if not exists` HOT 4
- Must be owner of materialized view mat_view_name HOT 2
- Alembic autogenerate broken for 'internal' PG functions HOT 5
- INFO: Transaction approach incompatible with MySQL
- alembic check broken - diff cannot be rendered HOT 1
- Materialized view change detection fails if upstream view has changed HOT 6
- DropOp dependency ordering when dropping multiple associated entities HOT 4
- Publishing next release HOT 1
- remove duplicate "instance" ref
- Duplicated ReplaceableEntity on every migration HOT 5
- PGFunction detection of plpgsql doesn't account for language definition wrapped in quotes HOT 1
- gracefully handle when a migrator needs to modify a column that a view depends on HOT 8
- Creating an ORM Table mapping on a view will generate a migration to create a table HOT 4
- [Question] How to add a unique index to definition for PGMaterializedView HOT 4
- Colon character escaped unnecessarily in view autogeneration HOT 4
- Downgrade does not reflect old definition
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from alembic_utils.