Skip to content

Commit

Permalink
refactor: Add settings and plugins attributes to Project (melta…
Browse files Browse the repository at this point in the history
…no#7273)

Co-authored-by: Edgar R. M. <edgar@meltano.com>
  • Loading branch information
WillDaSilva and edgarrmondragon committed Feb 10, 2023
1 parent dc04f5e commit 32e2d4d
Show file tree
Hide file tree
Showing 108 changed files with 1,263 additions and 2,001 deletions.
6 changes: 2 additions & 4 deletions docs/src/_contribute/style.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,27 +130,25 @@ For new, deprecated, or experimental features, the relevant code path can be wra
```python
# Example feature flag usage
from meltano.core.project import Project
from meltano.core.project_settings_service import ProjectSettingsService
from meltano.core.settings_service import FeatureFlags

class ExistingClass:

def __init__(self):
self.project = Project.find()
self.settings_service = ProjectSettingsService(self.project)

# If this method is called elsewhere in the code and the NEW_BEHAVIOR
# feature flag is not set to 'true' it will throw an error:
def experimental_method(self):
with self.settings_service.feature_flag(FeatureFlags.NEW_BEHAVIOR):
with self.project.settings.feature_flag(FeatureFlags.NEW_BEHAVIOR):
print("Doing new behavior...")

# If this method is called elsewhere, its behavior will vary based on whether
# the feature flag is set in the project
# The same pattern can be used to deprecate existing behavior
# Notice the "raise_error=False" in the feature_flag method call
def existing_method_with_new_behavior(self):
with self.settings_service.feature_flag(FeatureFlags.NEW_BEHAVIOR, raise_error=False) as new_behavior:
with self.project.settings.feature_flag(FeatureFlags.NEW_BEHAVIOR, raise_error=False) as new_behavior:
if new_behavior:
print("Doing the new behavior...")
else:
Expand Down
12 changes: 5 additions & 7 deletions src/meltano/api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
from meltano.api.headers import VERSION_HEADER
from meltano.api.security.auth import HTTP_READONLY_CODE
from meltano.core.db import project_engine
from meltano.core.error import ProjectReadonly
from meltano.core.logging.utils import FORMAT, setup_logging
from meltano.core.project import Project, ProjectReadonly
from meltano.core.project_settings_service import ProjectSettingsService
from meltano.core.project import Project
from meltano.oauth.app import create_app as create_oauth_service

STATUS_SERVER_ERROR = 500
Expand All @@ -39,8 +39,6 @@ def create_app(config: dict = {}) -> Flask: # noqa: WPS210,WPS213,B006
project = Project.find()
setup_logging(project)

settings_service = ProjectSettingsService(project)

project_engine(project, default=True)

app = Flask(
Expand Down Expand Up @@ -105,7 +103,7 @@ def create_app(config: dict = {}) -> Flask: # noqa: WPS210,WPS213,B006
init(app)

# Notifications
if settings_service.get("ui.notification"):
if project.settings.get("ui.notification"):
from .events import notifications

notifications.init_app(app)
Expand Down Expand Up @@ -134,9 +132,9 @@ def setup_js_context():
}

for context_key, setting_name in setting_map.items():
g.jsContext[context_key] = settings_service.get(setting_name)
g.jsContext[context_key] = project.settings.get(setting_name)

providers = settings_service.get("oauth_service.providers")
providers = project.settings.get("oauth_service.providers")
g.jsContext["oauthServiceProviders"] = [
provider for provider in providers.split(",") if provider
]
Expand Down
5 changes: 2 additions & 3 deletions src/meltano/api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

from meltano.api.headers import JSON_SCHEME_HEADER, VERSION_HEADER
from meltano.core.project import Project
from meltano.core.project_settings_service import ProjectSettingsService
from meltano.core.utils import truthy

# Flask
Expand Down Expand Up @@ -88,10 +87,10 @@ class ProjectSettings:
}

def __init__(self, project: Project):
self.settings_service = ProjectSettingsService(project)
self.project = project

def as_dict(self):
return {
config_key: self.settings_service.get(setting_name)
config_key: self.project.settings.get(setting_name)
for config_key, setting_name in self.settings_map.items()
}
38 changes: 8 additions & 30 deletions src/meltano/api/controllers/orchestrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@
from meltano.core.plugin_invoker import invoker_factory
from meltano.core.plugin_test_service import PluginTestServiceFactory
from meltano.core.project import Project
from meltano.core.project_plugins_service import ProjectPluginsService
from meltano.core.project_settings_service import ProjectSettingsService
from meltano.core.schedule_service import (
ScheduleAlreadyExistsError,
ScheduleDoesNotExistError,
Expand Down Expand Up @@ -365,13 +363,8 @@ def get_plugin_configuration(plugin_ref) -> Response:
JSON contain the plugin configuration.
"""
project = Project.find()

plugins_service = ProjectPluginsService(project)
plugin = plugins_service.get_plugin(plugin_ref)

settings = PluginSettingsService(
project, plugin, plugins_service=plugins_service, show_hidden=False
)
plugin = project.plugins.get_plugin(plugin_ref)
settings = PluginSettingsService(project, plugin, show_hidden=False)

try:
settings_group_validation = plugin.settings_group_validation
Expand Down Expand Up @@ -400,13 +393,8 @@ def save_plugin_configuration(plugin_ref) -> Response:
"""
project = Project.find()
payload = request.get_json()
plugins_service = ProjectPluginsService(project)
plugin = plugins_service.get_plugin(plugin_ref)

settings = PluginSettingsService(
project, plugin, plugins_service=plugins_service, show_hidden=False
)

plugin = project.plugins.get_plugin(plugin_ref)
settings = PluginSettingsService(project, plugin, show_hidden=False)
config = payload.get("config", {})
for name, value in config.items():
if not validate_plugin_config(plugin, name, value, project, settings):
Expand Down Expand Up @@ -434,13 +422,8 @@ def test_plugin_configuration(plugin_ref) -> Response: # noqa: WPS210
JSON with the job sucess status.
"""
project = Project.find()
plugins_service = ProjectPluginsService(project)
plugin = plugins_service.get_plugin(plugin_ref)

settings = PluginSettingsService(
project, plugin, plugins_service=plugins_service, show_hidden=False
)

plugin = project.plugins.get_plugin(plugin_ref)
settings = PluginSettingsService(project, plugin, show_hidden=False)
settings.config_override = PluginSettingsService.unredact(
{
name: value
Expand All @@ -450,12 +433,7 @@ def test_plugin_configuration(plugin_ref) -> Response: # noqa: WPS210
)

async def test_extractor():
invoker = invoker_factory(
project,
plugin,
plugins_service=plugins_service,
plugin_settings_service=settings,
)
invoker = invoker_factory(project, plugin, plugin_settings_service=settings)
async with invoker.prepared(db.session):
plugin_test_service = PluginTestServiceFactory(invoker).get_test_service()
success, _ = await plugin_test_service.validate()
Expand Down Expand Up @@ -486,7 +464,7 @@ def get_pipeline_schedules():
schedules = list(map(dict, schedule_service.schedules()))

jobs_in_list = False
with ProjectSettingsService(project).feature_flag(
with project.settings.feature_flag(
FeatureFlags.ENABLE_API_SCHEDULED_JOB_LIST, raise_error=False
) as allow:
if allow:
Expand Down
29 changes: 9 additions & 20 deletions src/meltano/api/controllers/plugins.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"""API Plugin Management Blue Print."""


from __future__ import annotations

import asyncio
import contextlib
import logging

from flask import jsonify, request
Expand All @@ -22,7 +24,6 @@
)
from meltano.core.project import Project
from meltano.core.project_add_service import ProjectAddService
from meltano.core.project_plugins_service import ProjectPluginsService


def plugin_def_json(plugin_def):
Expand Down Expand Up @@ -86,24 +87,18 @@ def installed():
Json of all installed plugins.
"""
project = Project.find()
plugins_service = ProjectPluginsService(project)

def _plugin_json(plugin: ProjectPlugin):
plugin_json = {"name": plugin.name}

try:
with contextlib.suppress(PluginNotFoundError):
plugin_json.update(plugin_def_json(plugin))

plugin_json["variant"] = plugin.variant
plugin_json["docs"] = plugin.docs
except PluginNotFoundError:
pass

return plugin_json

installed_plugins = {
plugin_type: [_plugin_json(plugin) for plugin in plugins]
for plugin_type, plugins in plugins_service.plugins_by_type().items()
for plugin_type, plugins in project.plugins.plugins_by_type().items()
}

return jsonify(installed_plugins)
Expand Down Expand Up @@ -142,13 +137,10 @@ def install_batch(): # noqa: WPS210
"""
payload = request.get_json()
project = Project.find()

plugins_service = ProjectPluginsService(project)
plugin = plugins_service.find_plugin(
plugin = project.plugins.find_plugin(
payload["name"], plugin_type=PluginType(payload["plugin_type"])
)

add_service = ProjectAddService(project, plugins_service=plugins_service)
add_service = ProjectAddService(project)
required_plugins = add_service.add_required(plugin)

# This was added to assist api_worker threads
Expand All @@ -158,7 +150,7 @@ def install_batch(): # noqa: WPS210
logging.debug("/plugins/install/batch no asyncio event loop detected")
asyncio.set_event_loop(asyncio.new_event_loop())

install_service = PluginInstallService(project, plugins_service=plugins_service)
install_service = PluginInstallService(project)
install_results = install_service.install_plugins(
required_plugins, reason=PluginInstallReason.ADD
)
Expand All @@ -183,9 +175,7 @@ def install():
plugin_name = payload["name"]

project = Project.find()

plugins_service = ProjectPluginsService(project)
plugin = plugins_service.find_plugin(plugin_name, plugin_type=plugin_type)
plugin = project.plugins.find_plugin(plugin_name, plugin_type=plugin_type)

# This was added to assist api_worker threads
try:
Expand All @@ -194,7 +184,6 @@ def install():
logging.debug("/plugins/install no asyncio event loop detected")
asyncio.set_event_loop(asyncio.new_event_loop())

install_service = PluginInstallService(project, plugins_service=plugins_service)
install_service.install_plugin(plugin, reason=PluginInstallReason.ADD)
PluginInstallService(project).install_plugin(plugin, reason=PluginInstallReason.ADD)

return jsonify(plugin.canonical())
6 changes: 1 addition & 5 deletions src/meltano/api/controllers/root.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
from meltano.api.api_blueprint import APIBlueprint
from meltano.api.security.auth import block_if_readonly, passes_authentication_checks
from meltano.core.project import Project
from meltano.core.project_settings_service import ProjectSettingsService
from meltano.core.utils import truthy

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -101,15 +100,12 @@ def upgrade():

@api_root.route("/identity")
def identity():
project = Project.find()
settings_service = ProjectSettingsService(project)

if current_user.is_anonymous:
return jsonify(
{
"username": "Anonymous",
"anonymous": True,
"can_sign_in": settings_service.get("ui.authentication"),
"can_sign_in": Project.find().settings.get("ui.authentication"),
}
)

Expand Down
20 changes: 9 additions & 11 deletions src/meltano/api/events/notification_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from meltano.api.signals import PipelineSignals
from meltano.core.plugin import PluginType
from meltano.core.project import Project
from meltano.core.project_plugins_service import ProjectPluginsService

SUCCESS = True
FAILURE = False
Expand All @@ -19,30 +18,29 @@


class NotificationEvents:
def __init__(
self,
project: Project,
mail_service: MailService | None = None,
plugins_service: ProjectPluginsService | None = None,
):
self.project = project
def __init__(self, project: Project, mail_service: MailService | None = None):
"""Initialize a `NotificationEvents` instance.
Args:
project: The Meltano project being operated on.
mail_service: The mail service to send notifications with.
"""
self.project = project
self.mail_service = mail_service or MailService(project)
self.plugins_service = plugins_service or ProjectPluginsService(project)

def init_app(self, app):
# wire the signal handlers
PipelineSignals.completed.connect(self.handle_pipeline_completed, ANY)

def pipeline_data_source(self, schedule) -> str:
"""Return the Data Source name for a Pipeline."""
return self.plugins_service.find_plugin(
return self.project.plugins.find_plugin(
schedule.extractor, plugin_type=PluginType.EXTRACTORS
).label

def pipeline_urls(self, schedule) -> str:
"""Return external URLs to different point of interests for a Pipeline."""
plugin = self.plugins_service.find_plugin(
plugin = self.project.plugins.find_plugin(
schedule.extractor, plugin_type=PluginType.EXTRACTORS
)
return {
Expand Down
7 changes: 2 additions & 5 deletions src/meltano/api/mail.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

from meltano.api.models.subscription import Subscription, SubscriptionEventType
from meltano.core.project import Project
from meltano.core.project_settings_service import ProjectSettingsService

mail = Mail()

Expand All @@ -22,10 +21,8 @@ def __init__(self, project: Project):
project: The project to use the MailService when referencing the project settings or project id.
"""
self.project = project
self._settings = ProjectSettingsService(self.project)

self.project_id = self._settings.get("project_id")
self.sendgrid_unsubscribe_group_id = self._settings.get(
self.project_id = self.project.settings.get("project_id")
self.sendgrid_unsubscribe_group_id = self.project.settings.get(
"mail.sendgrid_unsubscribe_group_id"
)

Expand Down
7 changes: 2 additions & 5 deletions src/meltano/api/security/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
from flask_login import user_logged_in
from flask_principal import identity_loaded

from meltano.core.project_settings_service import ProjectSettingsService

from .auth import ( # noqa: WPS450
from meltano.api.security.auth import ( # noqa: WPS450
_identity_loaded_hook,
_user_logged_in_hook,
unauthorized_callback,
Expand All @@ -26,8 +24,7 @@ def setup_security(app, project):
"confirm_register_form": MeltanoConfirmRegisterForm,
}

settings_service = ProjectSettingsService(project)
if not settings_service.get("ui.authentication"):
if not project.settings.get("ui.authentication"):
options["anonymous_user"] = FreeUser
# Else: use Flask's built-in AnonymousUser, which is not deemed to be
# authenticated and has no roles.
Expand Down

0 comments on commit 32e2d4d

Please sign in to comment.