Skip to content
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

Decouple extensions from Flask app. #3569

Merged
merged 15 commits into from
May 26, 2019
Merged

Decouple extensions from Flask app. #3569

merged 15 commits into from
May 26, 2019

Conversation

jezdez
Copy link
Member

@jezdez jezdez commented Mar 11, 2019

What type of PR is this? (check all applicable)

  • Refactor
  • Feature
  • Bug Fix

Description

This separates the extension registry from the Flask app and also introduces a separate registry for preriodic tasks.

Related Tickets & Documents

Fixes #3466.
Example change in redash-stmo: mozilla/redash-stmo#25

@jezdez jezdez requested review from arikfr and emtwo March 11, 2019 22:57
@ghost ghost assigned jezdez Mar 11, 2019
@ghost ghost added the in progress label Mar 11, 2019
bin/bundle-extensions Outdated Show resolved Hide resolved
bin/bundle-extensions Outdated Show resolved Hide resolved
redash/extensions.py Outdated Show resolved Hide resolved
redash/extensions.py Outdated Show resolved Hide resolved
Copy link
Member

@arikfr arikfr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See some comments, but there is a higher level thing I want to discuss --

Wouldn't it be better to have a single entrypoint (redash_extension?) which returns a dictionary. This dictionary can have multiple keys, each defines a different type of possible extension:

{
    'celery_periodic_tasks': [...],
    'flask_extensions': [...],
}

The registry loads this once, and then exposes an API to query it for the different extensions based on the type of functionality you need.

Wdyt?

requirements.txt Outdated Show resolved Hide resolved
redash/extensions.py Outdated Show resolved Hide resolved
@jezdez
Copy link
Member Author

jezdez commented Mar 12, 2019

See some comments, but there is a higher level thing I want to discuss --

Wouldn't it be better to have a single entrypoint (redash_extension?) which returns a dictionary. This dictionary can have multiple keys, each defines a different type of possible extension:

{
    'celery_periodic_tasks': [...],
    'flask_extensions': [...],
}

Yeah, admittedly this PR is a proposal, thank you for looking it through. I forgot to use the PR draft feature 😬

I wondered about how to structure the extension metadata, too. I think it boils down to whether we want to use "entry point groups" ("redash.extensions", "redash.periodic_tasks", like, some, others) as a format to separate extension use cases or whether we want to create an own metadata schema for extensions to treat as an Redash-internal artifact.

Since it's one of the community standards that is being adopted by the Python stdlib via the importlib.metadata, it'll be around for a long time.

Anyway, for this round of the PR I leaned to the first format of using entry point groups since I was worried that we'd reinvent the wheel when we have to parse and handle the returning dicts that the single extension return.

While the entry point system doesn't specifically say anything about the return type of entry point callbacks, in my experience the callback are usually only catering to one specific use case, instead of being an entrypoint for another layer of configuration. E.g. Other than the examples above Pygments for example uses multiple entry points for various extension features. So far I have not seen versioned extensions based on entry points out there, so it seems this is not something people have used it for.

The registry loads this once, and then exposes an API to query it for the different extensions based on the type of functionality you need.

Wdyt?

I'm fairly open to doing it differently of course, but I'm interested in you looking into the examples out there and consider the downsides of maintaining part of an extension system on the long run. I know we don't have many (any?) extensions out there yet, so it's a low risk decision right now.

Lastly -- to look from an even higher perspective -- none of these solution solve the Python/NPM parity problem yet, meaning how should we deal with client extensions that also need backend code, and vice-versa. My gut feel says we can't easily combine them (since we'd have to either have an integration layer in Python to install additional NPM modules, and hook them into webpack, or one in the client code, which seems weird, too).

@arikfr
Copy link
Member

arikfr commented Mar 12, 2019

Thank you for the references. Let's go the way you implemented it: having separate entry points for different types of extensions. But let's make sure that only the "Flask" extensions (not sure how to call them) depend on a Flask app instance.

redash/extensions.py Outdated Show resolved Hide resolved
@jezdez jezdez requested review from emtwo and arikfr March 14, 2019 09:21
@jezdez
Copy link
Member Author

jezdez commented Mar 14, 2019

@arikfr Is this something you'd be interested in merging before v7? We'd lose the ability to add those periodic tasks if not..

Copy link
Member

@arikfr arikfr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comments.

I don't mind having this in v7 if it's finalized in time.
But can't you pick any commit to base your release from?

redash/worker.py Outdated Show resolved Hide resolved
redash/worker.py Show resolved Hide resolved
redash/worker.py Outdated Show resolved Hide resolved
redash/extensions.py Outdated Show resolved Hide resolved
redash/extensions.py Outdated Show resolved Hide resolved
bin/bundle-extensions Outdated Show resolved Hide resolved
bin/bundle-extensions Outdated Show resolved Hide resolved

def init_app(app):
load_extensions(app)
load_bundles(app)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to load_bundles when we init the app?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly for consistency with the bundle script and so that we can inspect redash.extensions.bundles when needed.

bin/bundle-extensions Outdated Show resolved Hide resolved
jezdez added a commit that referenced this pull request Apr 18, 2019
@jezdez jezdez requested a review from arikfr April 18, 2019 20:04
@jezdez
Copy link
Member Author

jezdez commented Apr 24, 2019

@arikfr This would be ready for another round of reviews 😬

@jezdez jezdez added the Backend label May 4, 2019
@jezdez
Copy link
Member Author

jezdez commented May 4, 2019

@arikfr Is there anything I can do to move this ahead?

@@ -105,11 +107,14 @@ jobs:
path: /tmp/artifacts/
build-docker-image:
docker:
- image: circleci/buildpack-deps:xenial
- image: circleci/node:8
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to make sure npm is available in this step and is consistent with the other steps where bundles are collected.

@arikfr arikfr merged commit 07c9530 into master May 26, 2019
@arikfr arikfr deleted the flaskfree-extensions branch May 26, 2019 11:56
@arikfr
Copy link
Member

arikfr commented May 26, 2019

Thanks!

harveyrendell pushed a commit to pushpay/redash that referenced this pull request Nov 14, 2019
…ash#3601)

## What type of PR is this? (check all applicable)
<!-- Please leave only what's applicable -->

- [x] Refactor
- [x] Bug Fix

## Description

This basically makes sure that when import the redash package we don't accidentally trigger import-time side-effects such as requiring Redis.

Refs getredash#3569 and getredash#3466.
harveyrendell pushed a commit to pushpay/redash that referenced this pull request Nov 14, 2019
* Decouple extensions from Flask app.

This separates the extension registry from the Flask app and also introduces a separate registry for preriodic tasks.

Fix getredash#3466.

* Address review feedback.

* Update redash/extensions.py

Co-Authored-By: jezdez <jannis@leidel.info>

* Minor comment in requirements.

* Refactoring after getting feedback.

* Uncoupled bin/bundle-extensions from Flas app instance.

* Load bundles in bundle script and don’t rely on Flask.

* Upgraded to importlib-metadata 0.9.

* Add missing requirement.

* Fix TypeError.

* Added requirements for bundle_extension script.

* Install bundles requirement file correctly.

* Decouple bundle loading code from Redash.

* Install bundle requirements from requirements.txt.

* Use circleci/node for build-docker-image step, too.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Rewrite Extensions registry not to depend on Flask app
4 participants