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

refactor: Tesk app #197

Closed
wants to merge 4 commits into from
Closed

refactor: Tesk app #197

wants to merge 4 commits into from

Conversation

JaeAeich
Copy link

@JaeAeich JaeAeich commented Jun 30, 2024

Summary

All the configs and commonly used attributes that are parsed at runtime can be reused with this abstraction, eg the config value.

Summary by Sourcery

This pull request refactors the Tesk application by introducing a new TeskApp class that extends the Foca framework for initializing and running the application. It also adds a custom ConfigNotFoundError for better error handling when configuration files are missing.

  • Enhancements:
    • Refactored the application entry point to use a new TeskApp class for initialization and running.
    • Introduced a custom ConfigNotFoundError for handling missing configuration files.

Copy link

sourcery-ai bot commented Jun 30, 2024

Reviewer's Guide by Sourcery

This pull request refactors the Tesk application by introducing a new TeskApp class that extends the Foca framework. The changes consolidate the configuration loading and server initialization logic into this new class, simplifying the main application entry point. Additionally, a new custom exception ConfigNotFoundError is added to handle missing configuration files, and a minor formatting issue is fixed in the TesServiceInfo class.

File-Level Changes

Files Changes
tesk/app.py
tesk/tesk_app.py
Refactored the application entry point to use a new TeskApp class, consolidating configuration loading and server initialization logic.

Tips
  • Trigger a new Sourcery review by commenting @sourcery-ai review on the pull request.
  • You can change your review settings at any time by accessing your dashboard:
    • Enable or disable the Sourcery-generated pull request summary or reviewer's guide;
    • Change the review language;
  • You can always contact us if you have any questions or feedback.

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey @JaeAeich - I've reviewed your changes and they look great!

Here's what I looked at during the review
  • 🟡 General issues: 5 issues found
  • 🟡 Security: 1 issue found
  • 🟢 Testing: all looks good
  • 🟡 Complexity: 1 issue found
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment to tell me if it was helpful.

tesk/app.py Outdated

from connexion import FlaskApp
from foca import Foca
from werkzeug.exceptions import InternalServerError
Copy link

Choose a reason for hiding this comment

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

suggestion: Consider using a more specific exception

Using InternalServerError might be too generic. Consider defining a custom exception or using a more specific one to better handle different error scenarios.

Suggested change
from werkzeug.exceptions import InternalServerError
from werkzeug.exceptions import HTTPException
class CustomServerError(HTTPException):
code = 500
description = 'A custom internal server error occurred'

tesk/app.py Outdated
try:
TeskApp().run()
except Exception as error:
logger.exception("An error occurred while running the application.")
Copy link

Choose a reason for hiding this comment

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

suggestion: Log the actual error message

Consider including the actual error message in the log for better debugging. For example: logger.exception(f"An error occurred while running the application: {error}").

Suggested change
logger.exception("An error occurred while running the application.")
logger.exception(f"An error occurred while running the application: {error}")

@@ -13,6 +13,11 @@
NotFound,
)


class ConfigNotFoundError(FileNotFoundError):
Copy link

Choose a reason for hiding this comment

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

suggestion: Consider adding more context to the custom exception

It might be useful to add more context or attributes to the ConfigNotFoundError to provide additional information about the error, such as the expected location of the config file.

Suggested change
class ConfigNotFoundError(FileNotFoundError):
class ConfigNotFoundError(FileNotFoundError):
def __init__(self, filepath):
super().__init__(f"Configuration file not found: {filepath}")
self.filepath = filepath

tesk/tesk_app.py Outdated
@final
def run(self) -> None:
"""Run the application."""
_environment = self.conf.server.environment or "production"
Copy link

Choose a reason for hiding this comment

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

suggestion: Consider using a constant for default environment

Using a constant for the default environment value (e.g., DEFAULT_ENVIRONMENT = "production") can improve readability and maintainability.

Suggested change
_environment = self.conf.server.environment or "production"
DEFAULT_ENVIRONMENT = "production"
_environment = self.conf.server.environment or DEFAULT_ENVIRONMENT

tesk/tesk_app.py Outdated
"""Run the application."""
_environment = self.conf.server.environment or "production"
logger.info(f"Running application in {_environment} environment...")
_debug = self.conf.server.debug or False
Copy link

Choose a reason for hiding this comment

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

suggestion: Consider using a constant for default debug value

Using a constant for the default debug value (e.g., DEFAULT_DEBUG = False) can improve readability and maintainability.

Suggested change
_debug = self.conf.server.debug or False
DEFAULT_DEBUG = False
_debug = self.conf.server.debug or DEFAULT_DEBUG

tesk/tesk_app.py Outdated
ConfigNotFoundError: If the configuration file is not found.
"""
logger.info("Loading configuration path...")
if config_path_env := os.getenv("TESK_FOCA_CONFIG_FILE"):
Copy link

Choose a reason for hiding this comment

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

🚨 suggestion (security): Consider adding validation for the environment variable

It might be useful to add validation to ensure that the environment variable TESK_FOCA_CONFIG_FILE points to a valid file path.

Suggested change
if config_path_env := os.getenv("TESK_FOCA_CONFIG_FILE"):
if config_path_env := os.getenv("TESK_FOCA_CONFIG_FILE"):
config_file_path = Path(config_path_env).resolve()
if not config_file_path.is_file():
raise ConfigNotFoundError(f"Configuration file not found at {config_file_path}")
self.config_file = config_file_path

from the environment variable `TESK_FOCA_CONFIG_PATH` if set, or from
the default path if not. It raises a `FileNotFoundError` if the
configuration file is not found.
from tesk.tesk_app import TeskApp
Copy link

Choose a reason for hiding this comment

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

issue (complexity): Consider reverting to the original structure while incorporating the new error handling logic.

The new code introduces several complexities and regressions:

  1. Loss of Configuration Flexibility: The original code allowed for dynamic configuration loading from an environment variable or a default path. This flexibility is lost in the new code, making it less adaptable to different deployment environments.

  2. Error Handling: The new code introduces a try-except block that catches all exceptions and raises an InternalServerError. This is less specific and can obscure the root cause of issues, making debugging more difficult. The original code raised a specific FileNotFoundError if the configuration file was missing, which is more informative.

  3. Code Readability and Documentation: The original code had detailed docstrings explaining the purpose and behavior of the init_app function. This documentation is missing in the new code, making it harder for future developers to understand the code's intent and functionality.

  4. Modularity: The original code was modular, with a clear separation of concerns. The init_app function was responsible for initializing the app, while the main function was responsible for running it. The new code combines these responsibilities, reducing modularity and making the code harder to maintain.

Consider reverting to the original structure while incorporating the new error handling logic in a way that retains these benefits. Here is a suggested approach:

"""API server entry point."""
import logging
import os
from pathlib import Path

from connexion import FlaskApp
from foca import Foca
from werkzeug.exceptions import InternalServerError

logger = logging.getLogger(__name__)

def init_app() -> FlaskApp:
    """Initialize and return the FOCA app.

    This function initializes the FOCA app by loading the configuration
    from the environment variable `TESK_FOCA_CONFIG_PATH` if set, or from
    the default path if not. It raises a `FileNotFoundError` if the
    configuration file is not found.

    Returns:
        FlaskApp: A Connexion application instance.

    Raises:
        FileNotFoundError: If the configuration file is not found.
    """
    # Determine the configuration path
    config_path_env = os.getenv("TESK_FOCA_CONFIG_PATH")
    if config_path_env:
        config_path = Path(config_path_env).resolve()
    else:
        config_path = (Path(__file__).parents[1] / "deployment" / "config.yaml").resolve()

    # Check if the configuration file exists
    if not config_path.exists():
        raise FileNotFoundError(f"Config file not found at: {config_path}")

    foca = Foca(
        config_file=config_path,
    )
    return foca.create_app()

def main() -> None:
    """Run FOCA application."""
    try:
        app = init_app()
        app.run(port=app.port)
    except Exception as error:
        logger.exception("An error occurred while running the application.")
        raise InternalServerError(
            "An error occurred while running the application."
        ) from error

if __name__ == "__main__":
    main()

This version retains the flexibility, error specificity, and modularity of the original code while incorporating the new error handling logic.

Copy link
Member

@uniqueg uniqueg left a comment

Choose a reason for hiding this comment

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

I'm still not too sure about this PR. IMO the improvements made here (minus the error handling issues) would make more sense in FOCA. Until that is the case, I don't see the immediate benefits on this code base, relative to the old approach.

Would you care to explain (ideally in the PR description) why you think this change is necessary?

@@ -469,7 +469,7 @@ class TesServiceInfo(Service):
"service",
example=["VmSize"],
)
type: Optional[TesServiceType] = None # type: ignore
type: Optional[TesServiceType] = None # type: ignore
Copy link
Member

Choose a reason for hiding this comment

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

This change should be made in #196.

Also, you should rebase this PR against main, and, depending on the order in which you want to merge PRs, update (by merging) either this branch with the updated main, or the feature branch from #196.

Copy link
Author

Choose a reason for hiding this comment

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

yeah I thought so, but since #196 was majorly written using datamodel plugin, I didn't do it there as this error popped up when I started writing this. See the mypy tests are passign there.

tesk/app.py Outdated Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

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

Except for the env vars, nothing in this module is specific to TESK. Consider leaving the original entry point for now and integrating this new app class in FOCA instead - this will reduce the boilerplate in all of our apps.

Copy link
Author

Choose a reason for hiding this comment

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

OK, please review my reasoning again, and close this is you feel it might be irrelevent, I'll have to change the logic of the endpoints a lil bit so I'll address other reviews of the PR after merging/closing this.

@JaeAeich
Copy link
Author

JaeAeich commented Jul 3, 2024

Would you care to explain (ideally in the PR description) why you think this change is necessary?

SInce the app will be handeling reading and storing config, I think it would be better if that is done at the instatntiation only and not when the endpoint it called, specifically service-info. While writing that endpoint, I could would;ve had to use ConfigParser from FOCA to read the custom attr in the config, but that felt redundant as that info has aready been parsed in the instantiation of the app then why to rewrite the same logic again.

@JaeAeich JaeAeich requested a review from uniqueg July 3, 2024 04:17
@uniqueg
Copy link
Member

uniqueg commented Jul 3, 2024

Summary

All the configs and commonly used attributes that are parsed at runtime can be reused with this abstraction, eg the config value.

Summary by Sourcery

This pull request refactors the Tesk application by introducing a new TeskApp class that extends the Foca framework for initializing and running the application. It also adds a custom ConfigNotFoundError for better error handling when configuration files are missing.

  • Enhancements:

    • Refactored the application entry point to use a new TeskApp class for initialization and running.
    • Introduced a custom ConfigNotFoundError for handling missing configuration files.

I suppose the use case is valid - given that config parsing is tied to the FOCA app creation in the current FOCA version, it makes the config not easily accessible outside of app context. However, I think the proper way of handling this is to decouple the config parsing from the app creation in FOCA and provide an easy entry point to the config in FOCA itself (basically what you did but generalized in FOCA). Apps using FOCA would then use two steps to create the app: 1. Parse the config; 2. Create the app based on the parsed config.

That will address your use case without having to put this code in each repo and without having to parse the config twice.

So I propose to close this and consider reusing (parts of) the code in FOCA after the Connexion 3 upgrade.

@JaeAeich
Copy link
Author

JaeAeich commented Jul 3, 2024

So I propose to close this and consider reusing (parts of) the code in FOCA after the Connexion 3 upgrade.

Please check changes in #198

@JaeAeich JaeAeich closed this Jul 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants