-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add InvokeAIAppConfig schema migration system #6243
Conversation
6bc864c
to
1171661
Compare
1171661
to
36495b7
Compare
We don't need separate implementations for this class, let's not complicate it with an ABC
TypeVar is for generics, but the usage here is as an alias
The only pydantic usage was to convert strings to `Version` objects. The reason to do this conversion was to allow the register decorator to accept strings. MigrationEntry is only created inside this class, so we can just create versions from each migration when instantiating MigrationEntry instead. Also, pydantic doesn't provide runtime time checking for arbitrary classes like Version, so we don't get any real benefit.
This was checking a `Version` object against a `MigrationEntry`, but what we want is to check the version object against `MigrationEntry.from_version`
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While reviewing I cleaned up and simplified a few things.
I noticed that the migrator looks for a version between a range to decide which migration to run.
I think we should be very strict here, more like the sqlite migrator class. Migrations work migrate data from an explicit version to one other explicit version, and any other versions result in an error.
I added a failing test case that should pass with stricter migration version logic. This test fails on main
too, but I'd like this PR to improve on the current situation.
@psychedelicious Thanks for the tidying. I've made the migration logic strict by checking for contiguous ranges in the decorator parameters before any migrations begin. In the new test case, I don't understand why this config is expected to fail:
It looks OK to me. Perhaps it was intended to fail when the config file version was 4.0.0? I've changed the port number to "ice cream", and the test correctly raises an invalid schema error now. |
2ef6781
to
8144a26
Compare
@psychedelicious I updated the |
This should fail bc 4.0.1 isn't a valid registered config schema version |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the delay in returning to this PR. I'm being very cautious of making changes to the config file handling because the it is very sensitive - a small error can turn into a lot of user pain, even if it is easy to fix. So I'm being very picky in this review.
if any(from_version == m.from_version for m in cls._migrations): | ||
raise ValueError( | ||
f"function {function.__name__} is trying to register a migration for version {str(from_version)}, but this migration has already been registered." | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the added strictness for contiguous migrations, this should probably raise when either from_version
or to_version
already have a registered migration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
function: MigrationFunction | ||
|
||
|
||
class ConfigMigrator: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because this class is a stateful singleton, testing is difficult. Right now, during tests, this class has all migrations registered in the "live" app. Test failures could possibly pop up over time as more migrations are registered.
Making this an instanced class would mitigate those issues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed. ConfigMigrator
is now an instanced class, and test_config.py
creates new instances of the migrator for testing decorator functionality, and continuity checking.
The migration steps themselves have been moved into their own module now: invokeai.app.services.config.migrations
.
I also relocated get_config()
into invokeai.app.services.config.config_migrate
, and changed all places that are importing config_default
to import from invokeai.app.services.config
instead (get_config()
is reexported from __init__.py
) . This avoided circular import problems and makes config_default.py
cleaner looking overall.
tests/test_config.py
Outdated
@pytest.mark.xfail( | ||
reason=""" | ||
This test fails when run as part of the full test suite. | ||
def test_migration_check() -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no test coverage of the register decorator here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The unit test test_migration_check()
checks that the decorator works when presented with contiguous from and to values and fails when there is a discontinuity. However, I have to rework it for the migrator being an instance rather than a class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check it out now.
tests/test_config.py
Outdated
temp_config_file.write_text(v4_config) | ||
|
||
conf = load_and_migrate_config(temp_config_file) | ||
assert Version(conf.schema_version) >= Version("4.0.1") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When would the output version not being exactly 4.0.1
be acceptable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was written so that the test would continue to work when we move to version 4.0.2 -- because the test is running on the "live" config. I'm reworking this as suggested so that the migrator is an instance, in which case the exact value check will work.
@psychedelicious When you have a chance, please give this the once-over. I've converted the migration class into a migration instance, but I'm not sure I did this in the most elegant way. |
- Remove `Migrations` class - unnecessary complexity on top of `MigrationEntry` - Move common classes to `config_common` - Tidy docstrings, variable names
…e migration classes
Need to sort the migrations first.
This prevents tests from triggering config related parsing on your "live" root.
Ok, I've revisited this PR and made some changes, simplified a few things and made the tests more rigorous. I'm not entirely convinced the extra complexity in this change is warranted. I'm running into a problem where the app creates the custom nodes dir & files in the repo root, whenever I run a test. I believe the reason is that my invoke venv is now inside the repo root, and the I added a workaround - setting the root to a temp dir when args weren't parsed. I'm really wary of this change... I'm out of time on this PR for now, I'll revisit later this week. |
Blah. This is very bad. We need to set up the test environment with a temporary root directory. For the model manager tests, I created two fixtures like this:
Then I use the I'll revert to this on Monday to see if I can make the tests behave properly. When you say "I'm not sure the extra complexity is worth it," what are you referring to? To me, the current code looks more complex than the original version I wrote using decorators. My goal in the original version was to make it so that you could register a new migration by just adding a single decorated function definition to the I'd like to rethink how this is done. It seems like a lot of work just to remove a key from a dict. |
By "I'm not sure the extra complexity is worth it", I meant having a migration system at all for the config file. A couple simple functions serve the need just fine. Where I was coming from I added to the PR: We don't have a need to be able to crank out config migrations quickly, but we do have a need for simple and testable code. It took me a while to understand the intention of ABC, its implementation, the decorator and migration functions. Because the migration functions were only defined inside another function, it was not easy to test each one individually. You had to test the whole migration chain at once. Removing this extra stuff removes a layer of indirection. |
The problem with interactions with the live filesystem aren't caused by tests calling get_config directly. It's that when the app's modules are loaded and classes instantiated in tests, get_config gets called at some point. Another issue is when we read the invocations folder's init, it creates a the custom My last commit hijacks the root dir when the app isn't launched via script. This does work but I'm not confident in it. This issue is unrelated to the changes in this PR. |
Couple related issues with how tests are running and the config and custom nodes loader:
|
@psychedelicious At this point I think it's easier just to add ad hoc code to the |
This looks like a cul-de-sac and I'm closing it. |
Summary
This implements a straightforward system for migrating the
invokeai.yaml
configuration file betweenInvokeAIAppConfig
schema versions.To illustrate how it works, say that we are currently at config schema version 4.0.1, which includes an
attention_type
valuexformers
. Butxformers
becomes unsupported and we need to remove it. So we remove this choice from the list of valid values forattention_type
, and updateCONFIG_SCHEMA_VERSION
to4.0.2
. But some users will have an oldinvokeai.yaml
that containsattention_type: xformers
and if they try to run the app it will fail at startup time with a pydantic schema validation error.Here's what we do to fix that. In
invokeai/app/services/config/migrations.py
we add the following schema migration function to theload()
method:The
migrator.register()
decorator is defined ininvokeai/app/services/config/config_migrate.py
. You provide it with parameters indicating the incoming and outgoing version numbers, and define a function that takes a dictionary dump from the incoming schema and returns a dictionary compatible with the outgoing schema.The function that calls this takes care of inserting the
schema_version
field and validating against the current schema after all the migrations are completed.When
4.0.3
comes around, you add a similar function toconfig_default.py
using the decorator@migrator.register(from_version="4.0.2", to_version="4.0.3")
Related Issues / Discussions
QA Instructions
First test whether it messes up your current up to date
invokeai.yaml
file (it shouldn't) by launching the web app. The code makes a backup ininvokeai.yaml.bak
, so you'll have backup copy just in case.Next create an older version of invokeai.yaml. I have tested with an old 3.7-era file. Run the app. It should load fine, and
invokeai.yaml
will be brought up to date, preserving any customized settings from the 3.7 file.Merge Plan
Squash merge when approved.
Checklist