diff --git a/.gitignore b/.gitignore index dd3ffcc..161abdc 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ build/ *.db *.egg-info/ *.pyc +foo.db diff --git a/Dockerfile b/Dockerfile index dddd430..143b586 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM cs50/cli -RUN sudo apt update && sudo apt install --yes libmysqlclient-dev pgloader pkg-config postgresql +RUN sudo apt update && sudo apt install --yes libmysqlclient-dev pgloader pkg-config postgresql && sudo rm -rf /var/lib/apt/lists/* RUN sudo pip3 install mysqlclient psycopg2-binary WORKDIR /mnt diff --git a/docker-compose.yml b/docker-compose.yml index bcca424..1db4a9b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,7 @@ services: container_name: python-cs50 depends_on: - mysql - - postgres + - postgres environment: MYSQL_HOST: mysql POSTGRESQL_HOST: postgres @@ -30,4 +30,4 @@ services: POSTGRES_PASSWORD: postgres POSTGRES_DB: test ports: - - 5432:5432 + - 5432:5432 diff --git a/src/cs50/flask.py b/src/cs50/flask.py index 6e38971..c079cb5 100644 --- a/src/cs50/flask.py +++ b/src/cs50/flask.py @@ -1,47 +1,92 @@ +import importlib.util import os -import pkgutil import sys +import warnings def _wrap_flask(f): + """ + Wraps the Flask class's __init__ method if necessary, + primarily to add ProxyFix for CS50 IDE online environments. + """ if f is None: return - from packaging.version import Version, InvalidVersion - from .cs50 import _formatException + # Dynamically import packaging only when needed + try: + from packaging.version import Version, InvalidVersion + except ImportError: + # If packaging is not installed, cannot check version, so assume we can't wrap + return + try: + # Check Flask version compatibility if Version(f.__version__) < Version("1.0"): return - except InvalidVersion: + except (InvalidVersion, AttributeError): + # Handle invalid version strings or if Flask doesn't have __version__ + return + except Exception: return + # Apply ProxyFix only in CS50 IDE online environment if os.getenv("CS50_IDE_TYPE") == "online": - from werkzeug.middleware.proxy_fix import ProxyFix + try: + from werkzeug.middleware.proxy_fix import ProxyFix + except ImportError: + # If werkzeug (a Flask dependency) is missing or ProxyFix moved, cannot apply fix + return _flask_init_before = f.Flask.__init__ def _flask_init_after(self, *args, **kwargs): _flask_init_before(self, *args, **kwargs) - self.wsgi_app = ProxyFix( - self.wsgi_app, x_proto=1 - ) # For HTTPS-to-HTTP proxy + # Apply ProxyFix to handle reverse proxies in CS50 IDE (x_proto=1 trusts X-Forwarded-Proto header) + self.wsgi_app = ProxyFix(self.wsgi_app, x_proto=1) + # Monkey-patch Flask's __init__ f.Flask.__init__ = _flask_init_after -# If Flask was imported before cs50 +# --- Main Logic --- + +# Check if Flask was already imported before cs50 library was imported if "flask" in sys.modules: _wrap_flask(sys.modules["flask"]) -# If Flask wasn't imported +# If Flask hasn't been imported yet, set up patching for when it *is* imported else: - flask_loader = pkgutil.get_loader("flask") - if flask_loader: - _exec_module_before = flask_loader.exec_module + # Find the module specification for Flask using the recommended importlib utility + # This replaces the deprecated pkgutil.get_loader + flask_spec = importlib.util.find_spec("flask") + + # Check if the spec was found and if it has a loader + # (Some namespace packages might not have a loader, but Flask should) + if flask_spec and flask_spec.loader: + + # Ensure the loader has the exec_module method (standard loaders do) + if hasattr(flask_spec.loader, 'exec_module'): + + # Get the original exec_module method from the loader + _exec_module_before = flask_spec.loader.exec_module + + # Define a wrapper function for exec_module + # This function will be called by the import system when Flask is loaded + def _exec_module_after(module): + # Execute the original module loading logic first + _exec_module_before(module) + # Now that the module ('flask') is fully loaded and present in sys.modules, + # apply our custom wrapping logic. + _wrap_flask(module) # Pass the loaded module object to _wrap_flask + + # Monkey-patch the loader's exec_module method with our wrapper + flask_spec.loader.exec_module = _exec_module_after - def _exec_module_after(*args, **kwargs): - _exec_module_before(*args, **kwargs) - _wrap_flask(sys.modules["flask"]) + else: + # Handle the unlikely case where the loader doesn't have exec_module + warnings.warn("Flask loader doesn't support the expected exec_module interface") - flask_loader.exec_module = _exec_module_after + else: + # Handle the case where Flask spec wasn't found (Flask not installed?) + warnings.warn("Flask module specification not found. Flask may not be installed.")