diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 3b12e16c..f7abd906 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,6 +14,7 @@ jobs: - uses: mamba-org/setup-micromamba@v1 with: environment-file: environment.yml + create-args: python>=3.8 - name: Add micromamba to GITHUB_PATH run: echo "${HOME}/micromamba-bin" >> "$GITHUB_PATH" - run: ln -s "${CONDA_PREFIX}" .venv # Necessary for pyright. diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d364db7a..2f95b84b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,4 +27,4 @@ repos: - types-toml - types-ujson - types-aiofiles - args: [--show-error-codes, --implicit-optional] \ No newline at end of file + args: [--show-error-codes, --implicit-optional] diff --git a/dev_config.toml b/dev_config.toml index 6f7725ab..72f812eb 100644 --- a/dev_config.toml +++ b/dev_config.toml @@ -1,3 +1,4 @@ + [github] # Register the app here: https://github.com/settings/applications/new # Use a callback URL like `http://localhost:8000/auth/github/authorize` @@ -21,4 +22,3 @@ admins = ["dummy:alice"] [profiling] enable_sampling = false - diff --git a/docs/source/deploying/configuration.rst b/docs/source/deploying/configuration.rst index 306118c3..1908755b 100644 --- a/docs/source/deploying/configuration.rst +++ b/docs/source/deploying/configuration.rst @@ -109,6 +109,12 @@ Quetz can also create a channel for a newly connected user: :redirect_http_to_https: Enforces that all incoming requests must be `https`. Any incoming requests to `http` will be redirected to the secure scheme instead. Defaults to `false`. :package_unpack_threads: Number of parallel threads used for unpacking. Defaults to `1`. +.. code:: + + [general] + :dev: Whether to enable development features. Do not enable this in production. + Fills the database with test data and allows http access. + ``session`` section ^^^^^^^^^^^^^^^^^^^ diff --git a/docs/source/deploying/database.rst b/docs/source/deploying/database.rst index 1e6f596d..90075dfb 100644 --- a/docs/source/deploying/database.rst +++ b/docs/source/deploying/database.rst @@ -29,7 +29,7 @@ You can then create a database in PostgreSQL for quetz tables: Deploying Quetz with PostgreSQL backend """"""""""""""""""""""""""""""""""""""" -Then in your configuration file (such as `dev_config.toml`) replace the `[sqlalchemy]` section with: +Then in your configuration file (such as `config.toml`) replace the `[sqlalchemy]` section with: .. code:: diff --git a/pyproject.toml b/pyproject.toml index 2e64b5b7..6d104195 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,6 +58,7 @@ venv = ".venv" venvPath= "." [tool.mypy] +python_version = "3.8" ignore_missing_imports = true plugins = [ "sqlmypy" diff --git a/quetz/cli.py b/quetz/cli.py index bff53c68..a8eea2be 100644 --- a/quetz/cli.py +++ b/quetz/cli.py @@ -412,15 +412,6 @@ def create( bool, typer.Option(help="Skip the creation if deployment already exists."), ] = False, - dev: Annotated[ - bool, - typer.Option( - help=( - "Enable/disable dev mode " - "(fills the database with test data and allows http access)" - ), - ), - ] = False, ): """Create a new Quetz deployment.""" @@ -462,6 +453,7 @@ def create( ) raise typer.Abort() + config = None if not config_file.exists() and not create_conf and not copy_conf: try: # If no config file is provided or created, try to get config @@ -489,8 +481,7 @@ def create( shutil.copyfile(copy_conf, config_file) if not config_file.exists() and create_conf: - https = "false" if dev else "true" - conf = create_config(https=https) + conf = create_config(https=str(config.is_dev() if config else True).lower()) with open(config_file, "w") as f: f.write(conf) @@ -502,7 +493,7 @@ def create( with working_directory(db_path): db = get_session(config.sqlalchemy_database_url) _run_migrations(config.sqlalchemy_database_url) - if dev: + if config.is_dev(): _fill_test_database(db) _set_user_roles(db, config) @@ -556,7 +547,6 @@ def start( To be started, a deployment has to be already created. At this time, only Uvicorn is supported as manager. """ - logger.info(f"deploying quetz from directory {path}") if path: @@ -591,6 +581,7 @@ def start( import quetz quetz_src = os.path.dirname(quetz.__file__) + uvicorn.run( "quetz.main:app", reload=reload, @@ -630,15 +621,6 @@ def run( bool, typer.Option(help="Skip the creation if deployment already exists."), ] = False, - dev: Annotated[ - bool, - typer.Option( - help=( - "Enable/disable dev mode " - "(fills the database with test data and allows http access)" - ), - ), - ] = False, port: Annotated[int, typer.Option(help="The port to bind")] = 8000, host: Annotated[ str, typer.Option(help="The network interface to bind") @@ -665,7 +647,7 @@ def run( It performs sequentially create and start operations.""" abs_path = os.path.abspath(path) - create(abs_path, copy_conf, create_conf, delete, skip_if_exists, dev) + create(abs_path, copy_conf, create_conf, delete, skip_if_exists) start(abs_path, port, host, proxy_headers, log_level, reload) diff --git a/quetz/config.py b/quetz/config.py index bf26182c..b627c095 100644 --- a/quetz/config.py +++ b/quetz/config.py @@ -5,6 +5,7 @@ import logging.config import os from distutils.util import strtobool +from pathlib import Path from secrets import token_bytes from typing import Any, Dict, Iterable, List, NamedTuple, Optional, Type, Union @@ -59,6 +60,7 @@ class Config: ConfigSection( "general", [ + ConfigEntry("dev", bool, required=True, default=False), ConfigEntry("package_unpack_threads", int, 1), ConfigEntry("frontend_dir", str, default=""), ConfigEntry("redirect_http_to_https", bool, False), @@ -243,7 +245,7 @@ def __new__(cls, deployment_config: str = None): return cls._instances[None] try: - path = os.path.abspath(cls.find_file(deployment_config)) + path = str(Path(cls.find_file(deployment_config)).absolute().resolve()) except TypeError: # if not config path exists, set it to empty string. path = "" @@ -287,7 +289,6 @@ def init(self, path: str) -> None: deployment_config : str, optional The configuration stored at deployment level """ - self.config: Dict[str, Any] = {} # only try to get config from config file if it exists. @@ -407,6 +408,7 @@ def _get_environ_config(self) -> Dict[str, Any]: for key, value in os.environ.items() if key.startswith(_env_prefix) } + for var, value in quetz_var.items(): split_key = var.split("_") config_key = split_key[1].lower() @@ -443,6 +445,16 @@ def _get_environ_config(self) -> Dict[str, Any]: return config + def is_dev(self) -> bool: + """Return whether the application is in development mode. + + Returns + ------- + bool + Whether the application is in development mode + """ + return str(self.config.get("general", {}).get("dev", False)).lower() == "true" + def get_package_store(self) -> pkgstores.PackageStore: """Return the appropriate package store as set in the config. diff --git a/quetz/config.toml b/quetz/config.toml index 1dc06acb..ee538e79 100644 --- a/quetz/config.toml +++ b/quetz/config.toml @@ -1,3 +1,6 @@ +[general] +dev = true + [github] # Register the app here: https://github.com/settings/applications/new client_id = "{}" diff --git a/quetz/deps.py b/quetz/deps.py index 3ef97665..343dfcbe 100644 --- a/quetz/deps.py +++ b/quetz/deps.py @@ -84,7 +84,7 @@ def get_rules( request: Request, session: dict = Depends(get_session), db: Session = Depends(get_db), -): +) -> authorization.Rules: return authorization.Rules(request.headers.get("x-api-key"), session, db) diff --git a/quetz/main.py b/quetz/main.py index db1a36be..8939e73e 100644 --- a/quetz/main.py +++ b/quetz/main.py @@ -270,22 +270,24 @@ async def route_logout(request): return RedirectResponse("/") -@api_router.get("/dummylogin/{username}", tags=["dev"]) -def dummy_login( - username: str, dao: Dao = Depends(get_dao), session=Depends(get_session) -): - user = dao.get_user_by_username(username) - if user is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"User '{username}' not found", - ) +if config.is_dev(): + + @api_router.get("/dummylogin/{username}", tags=["dev"]) + def dummy_login( + username: str, dao: Dao = Depends(get_dao), session=Depends(get_session) + ): + user = dao.get_user_by_username(username) + if user is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"User '{username}' not found", + ) - logout(session) - session["user_id"] = str(uuid.UUID(bytes=user.id)) + logout(session) + session["user_id"] = str(uuid.UUID(bytes=user.id)) - session["identity_provider"] = "dummy" - return RedirectResponse("/") + session["identity_provider"] = "dummy" + return RedirectResponse("/") @api_router.get("/me", response_model=rest_models.Profile, tags=["users"]) diff --git a/quetz/testing/fixtures.py b/quetz/testing/fixtures.py index 80671b39..72a279c9 100644 --- a/quetz/testing/fixtures.py +++ b/quetz/testing/fixtures.py @@ -168,6 +168,9 @@ def config_base(database_url, plugins, config_auth): return f""" {config_auth} +[general] +dev = true + [sqlalchemy] database_url = "{database_url}" diff --git a/quetz/tests/test_cli.py b/quetz/tests/test_cli.py index f6c7769b..9f3fb033 100644 --- a/quetz/tests/test_cli.py +++ b/quetz/tests/test_cli.py @@ -124,11 +124,7 @@ def get_db(_): return db with mock.patch("quetz.cli.get_session", get_db): - cli.create( - Path(config_dir) / "new-deployment", - copy_conf="config.toml", - dev=True, - ) + cli.create(Path(config_dir) / "new-deployment", copy_conf="config.toml") user = db.query(User).filter(User.username == "alice").one_or_none()