chore(key-value): convert command to dao
SUMMARY
This PR converts the Key Value commands to a DAO, which gives the caller much more control over when commits/rollbacks happen. Other changes:
- Update all code that uses the KV commands to use the new DAO, making sure flushes/commits happen outside the DAO. In particular, this covers the following major components:
- Explore permalinks
- Dashboard permalinks
- Distributed lock. During the refactor, the distributed lock was moved under a module of its own + break out the stateful logic into commands that use the new DAO.
- Metastore cache. I considered breaking out the logic into commands, but it felt more sensible to keep the code as-is under a single python file, as it's under
extensions.
- The old KV command integration tests are converted into functionally equivalent unit tests
BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
TESTING INSTRUCTIONS
ADDITIONAL INFORMATION
- [ ] Has associated issue:
- [ ] Required feature flags:
- [ ] Changes UI
- [ ] Includes DB Migration (follow approval process in SIP-59)
- [ ] Migration is atomic, supports rollback & is backwards-compatible
- [ ] Confirm DB migration upgrade and downgrade tested
- [ ] Runtime estimates and downtime expectations provided
- [ ] Introduces new feature or API
- [ ] Removes existing feature or API
Codecov Report
Attention: Patch coverage is 93.86792% with 13 lines in your changes missing coverage. Please review.
Project coverage is 83.87%. Comparing base (
76d897e) to head (d7752de). Report is 1094 commits behind head on master.
Additional details and impacted files
@@ Coverage Diff @@
## master #29344 +/- ##
===========================================
+ Coverage 60.48% 83.87% +23.38%
===========================================
Files 1931 519 -1412
Lines 76236 37409 -38827
Branches 8568 0 -8568
===========================================
- Hits 46114 31376 -14738
+ Misses 28017 6033 -21984
+ Partials 2105 0 -2105
| Flag | Coverage Δ | |
|---|---|---|
| hive | 49.09% <25.94%> (-0.08%) |
:arrow_down: |
| javascript | ? |
|
| mysql | 77.16% <50.94%> (?) |
|
| postgres | 77.26% <50.94%> (?) |
|
| presto | 53.71% <25.94%> (-0.09%) |
:arrow_down: |
| python | 83.87% <93.86%> (+20.38%) |
:arrow_up: |
| sqlite | 76.74% <50.94%> (?) |
|
| unit | 59.77% <83.01%> (+2.14%) |
:arrow_up: |
Flags with carried forward coverage won't be shown. Click here to find out more.
:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.
@villebro I can't pinpoint why but something in this PR has caused local dev builds to fail with a flask app_context error. Also observed by QA at Preset.
2024-07-01 17:47:46,317:ERROR:superset.app:Failed to create app
Traceback (most recent call last):
File "/Users/user/superset/app.py", line 40, in create_app
app_initializer.init_app()
File "/Users/user/superset/initialization/__init__.py", line 479, in init_app
self.configure_cache()
File "/Users/user/superset/initialization/__init__.py", line 511, in configure_cache
cache_manager.init_app(self.superset_app)
File "/Users/user/superset/utils/cache_manager.py", line 93, in init_app
self._init_cache(
File "/Users/user/superset/utils/cache_manager.py", line 87, in _init_cache
cache.init_app(app, cache_config)
File "/Users/user/site-packages/flask_caching/__init__.py", line 145, in init_app
self._set_cache(app, config)
File "/Users/user/site-packages/flask_caching/__init__.py", line 155, in _set_cache
cache_factory = import_string(import_me)
File "/Users/user/site-packages/werkzeug/utils.py", line 596, in import_string
__import__(import_name)
File "/Users/user/superset/extensions/metastore_cache.py", line 27, in <module>
from superset.daos.key_value import KeyValueDAO
File "/Users/user/superset/daos/key_value.py", line 32, in <module>
from superset.key_value.models import KeyValueEntry
File "/Users/user/superset/key_value/models.py", line 24, in <module>
from superset.models.helpers import AuditMixinNullable, ImportExportMixin
File "/Users/user/superset/models/__init__.py", line 17, in <module>
from . import core, dynamic_plugins, sql_lab, user_attributes # noqa: F401
File "/Users/user/superset/models/core.py", line 74, in <module>
from superset.models.helpers import AuditMixinNullable, ImportExportMixin
File "/Users/user/superset/models/helpers.py", line 107, in <module>
config = app.config
File "/Users/user/site-packages/werkzeug/local.py", line 318, in __get__
obj = instance._get_current_object()
File "/Users/user/site-packages/werkzeug/local.py", line 519, in _get_current_object
raise RuntimeError(unbound_message) from None
RuntimeError: Working outside of application context.
This typically means that you attempted to use functionality that needed
the current application. To solve this, set up an application context
with app.app_context(). See the documentation for more information.
Followed by this
Traceback (most recent call last):
File "/Users/user/bin/superset", line 8, in <module>
sys.exit(superset())
File "/Users/user/lib/python3.10/site-packages/click/core.py", line 1157, in __call__
return self.main(*args, **kwargs)
File "/Users/user/lib/python3.10/site-packages/click/core.py", line 1078, in main
rv = self.invoke(ctx)
File "/Users/user/lib/python3.10/site-packages/click/core.py", line 1685, in invoke
super().invoke(ctx)
File "/Users/user/lib/python3.10/site-packages/click/core.py", line 1434, in invoke
return ctx.invoke(self.callback, **ctx.params)
File "/Users/user/lib/python3.10/site-packages/click/core.py", line 783, in invoke
return __callback(*args, **kwargs)
File "/Users/user/lib/python3.10/site-packages/click/decorators.py", line 33, in new_func
return f(get_current_context(), *args, **kwargs)
File "/Users/user/lib/python3.10/site-packages/flask/cli.py", line 355, in decorator
app = __ctx.ensure_object(ScriptInfo).load_app()
File "/Users/user/lib/python3.10/site-packages/flask/cli.py", line 309, in load_app
app = locate_app(import_name, name)
File "/Users/user/lib/python3.10/site-packages/flask/cli.py", line 238, in locate_app
return find_app_by_string(module, app_name)
File "/Users/user/lib/python3.10/site-packages/flask/cli.py", line 166, in find_app_by_string
app = attr(*args, **kwargs)
File "/Users/user/superset/app.py", line 40, in create_app
app_initializer.init_app()
File "/Users/user/superset/initialization/__init__.py", line 479, in init_app
self.configure_cache()
File "/Users/user/superset/initialization/__init__.py", line 511, in configure_cache
cache_manager.init_app(self.superset_app)
File "/Users/user/superset/utils/cache_manager.py", line 93, in init_app
self._init_cache(
File "/Users/user/superset/utils/cache_manager.py", line 87, in _init_cache
cache.init_app(app, cache_config)
File "/Users/user/lib/python3.10/site-packages/flask_caching/__init__.py", line 145, in init_app
self._set_cache(app, config)
File "/Users/user/lib/python3.10/site-packages/flask_caching/__init__.py", line 155, in _set_cache
cache_factory = import_string(import_me)
File "/Users/user/lib/python3.10/site-packages/werkzeug/utils.py", line 596, in import_string
__import__(import_name)
File "/Users/user/superset/extensions/metastore_cache.py", line 27, in <module>
from superset.daos.key_value import KeyValueDAO
File "/Users/user/superset/daos/key_value.py", line 32, in <module>
from superset.key_value.models import KeyValueEntry
File "/Users/user/superset/key_value/models.py", line 24, in <module>
from superset.models.helpers import AuditMixinNullable, ImportExportMixin
File "/Users/user/superset/models/__init__.py", line 17, in <module>
from . import core, dynamic_plugins, sql_lab, user_attributes # noqa: F401
File "/Users/user/superset/models/core.py", line 74, in <module>
from superset.models.helpers import AuditMixinNullable, ImportExportMixin
File "/Users/user/superset/models/helpers.py", line 107, in <module>
config = app.config
File "/Users/user/lib/python3.10/site-packages/werkzeug/local.py", line 318, in __get__
obj = instance._get_current_object()
File "/Users/user/lib/python3.10/site-packages/werkzeug/local.py", line 519, in _get_current_object
raise RuntimeError(unbound_message) from None
RuntimeError: Working outside of application context.
This typically means that you attempted to use functionality that needed
the current application. To solve this, set up an application context
with app.app_context(). See the documentation for more information.
@rtexelm I was able to reproduce, it was due to a last second cleanup where I moved imports to the top level from the methods. I'll open up a fix shortly after doing some more testing.