Conversation
|
|
||
| @staticmethod | ||
| def __get_engine(library_dir: Path, in_memory: bool, sql_filename: str): | ||
| def _get_engine(self, library_dir: Path, in_memory: bool, sql_filename: str): |
There was a problem hiding this comment.
Why make that non-static?
| class DBMigrations: | ||
| def __init__(self, library_dir: Path, engine: Engine) -> None: | ||
| from tagstudio.core.library.alchemy.library import Library | ||
| def __init__(self, library: "Library") -> None: |
There was a problem hiding this comment.
I had originally passed the library path and engine because the engine argument will be removed when the migrations are made sql alchemy independent (and thus fully independent of the model that the to-be-migrated lib doesn't yet follow). Once that is done the parameter would only be the library path which is a much looser coupling then passing the full library in1.
For that reason I feel it is undesirable to pass the entire library to the constructor here.
Also, afaict it isn't necessary.
Note: Afaict the statements setting library_dir in Library.create_sqlite_libraryLibrary.open_sqlite_library can also be removed once this is reverted.
Footnotes
-
We want the coupling to be as loose as possible here, because the logic in the Library class will be for the current schema while the migrations deal with old schemas and so we can't (and shouldn't where we still can) rely on that logic. ↩
| @override | ||
| @classmethod | ||
| def run(cls, session: Session, library_dir: Path, fmt_log): | ||
| def run(cls, session: Session, library: "Library", fmt_log: Callable[[str], str]): |
There was a problem hiding this comment.
I like annotating the methods with the type for fmt_log, but I think it would be better to define something like LoggingMethod = Callable[[str], str] and to then set fmt_log: LoggingMethod
| @staticmethod | ||
| def save_library_backup_to_disk(library_dir: Path) -> Path: |
There was a problem hiding this comment.
I had made this static since the idea is to call it when the library isn't loaded yet (and can't be loaded at all due to needing to be migrated), that's why it was static and why I still think it should be here.
We could maybe also just move this to the DBMigrations to be done as the first step of DBMigrations.run.
There was a problem hiding this comment.
The save_library_backup_to_disk() method doesn't require a library to be loaded, it just uses the Library instance's self.library_dir, which I tweaked to be set before the migrations are run in open_sqlite_library().
We could maybe also just move this to the DBMigrations to be done as the first step of DBMigrations.run.
This method is also used outside of the migrations, like in the UI - so there still needs to be a non-static version of it present in the Library class for instances to use without passing the argument of the instance's own library_dir.
There was a problem hiding this comment.
The
save_library_backup_to_disk()method doesn't require a library to be loaded, it just uses the Library instance'sself.library_dir,which I tweaked to be set before the migrations are run inopen_sqlite_library().
Yeah this is exactly what I meant; normally the library_dir means "the directory of the currently open library" but here it effectively is "the directory of the library that should be backed up" (unless a library is actually open, in which the original meaning is correct again).
We could maybe also just move this to the DBMigrations to be done as the first step of DBMigrations.run.
This method is also used outside of the migrations, like in the UI - so there still needs to be a non-static version of it present in the Library class for instances to use without passing the argument of the instance's own
library_dir.
I think having a static method and a non-static method that calls the static one (with self.library_dir as the param) would be best then.
| ) | ||
| ) | ||
| except Exception: | ||
| return 0 |
There was a problem hiding this comment.
This split existed, because it is being used in the migrations where the existing engine should be used to ensure consistency irrespective of future changes to the migrations.
However, I think this can just be moved to the migrations entirely as the only other use can be removed.
(It is in LibraryInfoWindow where get_version(DB_VERSION_CURRENT_KEY) is contrasted to DB_VERSION, which is useless because the migrations fail unless get_version(DB_VERSION_CURRENT_KEY) == DB_VERSION meaning that can be removed.)
There was a problem hiding this comment.
This split existed, because it is being used in the migrations where the existing engine should be used to ensure consistency irrespective of future changes to the migrations.
Where might an inconsistency arise that would be solved by passing a different engine to build a session from?
However, I think this can just be moved to the migrations entirely as the only other use can be removed.
(It is in LibraryInfoWindow where get_version(DB_VERSION_CURRENT_KEY) is contrasted to DB_VERSION, which is useless because the migrations fail unless get_version(DB_VERSION_CURRENT_KEY) == DB_VERSION meaning that can be removed.)
The current UI-facing use of this is working as intended, where it shows the current loaded DB version in comparison to the program's currently known DB_VERSION, which is useful for telling if you have a DB loaded with a minor version greater than the TagStudio version that's opening it. I'd also like to not gate the _get_version() method to the migrations, as that would limit future uses in the UI.
Perhaps the "static" version of this that includes the engine parameter and inspection conditional could be moved to the migrations class, while a simpler instance method that just uses the currently expected logic could remain in the library file? Though that's a tiny bit of code duplication
There was a problem hiding this comment.
This split existed, because it is being used in the migrations where the existing engine should be used to ensure consistency irrespective of future changes to the migrations.
Where might an inconsistency arise that would be solved by passing a different engine to build a session from?
The engine is specific to the DB that is currently open, so if we were to e.g. allow a user to bulk migrate libraries while a different library is open, then this get_version method would cause the migrations to see the incorrect version and not migrate anything because it would see the version of the currently open library and not the one of the DB being migrated.
However, I think this can just be moved to the migrations entirely as the only other use can be removed.
(It is in LibraryInfoWindow where get_version(DB_VERSION_CURRENT_KEY) is contrasted to DB_VERSION, which is useless because the migrations fail unless get_version(DB_VERSION_CURRENT_KEY) == DB_VERSION meaning that can be removed.)The current UI-facing use of this is working as intended, where it shows the current loaded DB version in comparison to the program's currently known
DB_VERSION, which is useful for telling if you have a DB loaded with a minor version greater than the TagStudio version that's opening it.
Good point, I missed that.
I'd also like to not gate the
_get_version()method to the migrations, as that would limit future uses in the UI.Perhaps the "static" version of this that includes the engine parameter and inspection conditional could be moved to the migrations class, while a simpler instance method that just uses the currently expected logic could remain in the library file? Though that's a tiny bit of code duplication
Yes, I think that would be best. That way get_version is basically a one-liner and the complicated DB introspection logic can be relegated to the migrations.
Also, uncoupling the migrations from sqlalchemy would probably require that anyway, so we might as well do it now.
RE the actual fix: I am not quite sure why this fixes anything, because the original setup was not missing any context managers at all. The |
For the non-static reversions, this was mostly motivated by the idea that the Library is not intended to be a singleton class - each instance of the Library class should be completely self-contained and methods for the class that affect instanced versions should only affect data for said instance. While making some methods static didn't necessarily turn the Library class into a singleton, it still separated the self-contained nature of the Library instance by requiring a session to be independently supplied (which raised a deeper issue with the
When turning the So a lot of the static reversions weren't 100% necessary to pull off this fix, but sort of went hand-in-hand with it and also reflect my own intentions for how the Library class should be used. |
This only applies to the Also, the semantics of
The session is in no way associated with the Library instance
If you compare how the session is built now and how it was built then, they are exactly identical, so this cannot be the reason afaict.
Not quite sure what you mean here |
The
I'm referring to how the session context managers in the Library class are constructed using
If you've got an alternate explanation for #1447 I'm all ears, but from all the investigating and testing I've done it really seems like the session currently is getting detached or desycned or something from the rest of the Library, causing a lock on the DB because there's now something outside the scope of the expected session that's using it (in conjunction with using Changes made from there were either due to other obscured issues like the DB9 migration flushing and/or due to wanting to enforce a particular pattern for accessing library methods. |
Summary
This PR fixes issues caused by a missing session context manager in the
all_entries()method in conjunction with the newautocommit=FalseDB parameter, and makes some tweaks to the migration refactors done in #1432 and #1456, most notably removing static methods in theLibraryclass and removing local imports because I'm an idiot and forgot that I didn't completely review #1456 before signing off and pulling itCloses #1447, Closes #1467
Tasks Completed