From 619f63c536edb98f3fcf64048da58d364d8ca3d8 Mon Sep 17 00:00:00 2001 From: Brendon Smith Date: Tue, 14 Sep 2021 19:41:25 -0400 Subject: [PATCH] Drop `toml` dependency https://toml.io/en/ uiri/toml#267 https://github.com/uiri/toml/pull/279#issuecomment-633012586 While the TOML spec is alive and well, the `toml` package that was used in this project is dead (uiri/toml#267). There are several alternatives. `rtoml` is the fastest, but does not provide "round trip" guarantees (meaning that it can't load, then dump, and get an identical result). However, for simple loading and parsing it's fine. `tomli` is another alternative, but it is read-only (writing requires a separate `tomli-w` package), and requires files to be opened in binary mode to parse TOML. `tomlkit` is used by Poetry (from the Poetry author), but is currently 70x slower than `rtoml`. It's also important to note that the TOML parsing and settings loading is somewhat tangential to the focus of the inboard project. A separate project, https://github.com/br3ndonland/fastenv, is more focused on settings management, and may feature TOML support in the future. This commit will remove `toml` from the `fastapi` Poetry extras group. Note that `toml` is still a sub-dependency of some `dev-dependencies`, including pre-commit and pytest. --- inboard/app/main_fastapi.py | 12 ++------- inboard/app/utilities_fastapi.py | 46 ++------------------------------ poetry.lock | 8 +++--- pyproject.toml | 5 ++-- tests/conftest.py | 7 ----- tests/test_metadata.py | 39 --------------------------- 6 files changed, 10 insertions(+), 107 deletions(-) delete mode 100644 tests/test_metadata.py diff --git a/inboard/app/main_fastapi.py b/inboard/app/main_fastapi.py index 959b801..cca10b0 100644 --- a/inboard/app/main_fastapi.py +++ b/inboard/app/main_fastapi.py @@ -4,13 +4,7 @@ from fastapi import Depends, FastAPI, status from fastapi.middleware.cors import CORSMiddleware -from inboard.app.utilities_fastapi import ( - GetRoot, - GetStatus, - GetUser, - Settings, - basic_auth, -) +from inboard.app.utilities_fastapi import GetRoot, GetStatus, GetUser, basic_auth origin_regex = r"^(https?:\/\/)(localhost|([\w\.]+\.)?br3ndon.land)(:[0-9]+)?$" server = ( @@ -20,9 +14,7 @@ ) version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}" -settings = Settings() - -app = FastAPI(title=settings.name, version=settings.version) +app = FastAPI(title="inboard") app.add_middleware( CORSMiddleware, allow_credentials=True, diff --git a/inboard/app/utilities_fastapi.py b/inboard/app/utilities_fastapi.py index 89e1fd6..0292862 100644 --- a/inboard/app/utilities_fastapi.py +++ b/inboard/app/utilities_fastapi.py @@ -1,12 +1,10 @@ import os import secrets -from pathlib import Path -from typing import List, Optional +from typing import Optional -import toml from fastapi import Depends, HTTPException, status from fastapi.security import HTTPBasic, HTTPBasicCredentials -from pydantic import BaseModel, BaseSettings +from pydantic import BaseModel async def basic_auth(credentials: HTTPBasicCredentials = Depends(HTTPBasic())) -> str: @@ -30,46 +28,6 @@ async def basic_auth(credentials: HTTPBasicCredentials = Depends(HTTPBasic())) - return credentials.username -def set_fields_from_pyproject( - fields: dict, - pyproject_path: Path = Path(__file__).parents[2].joinpath("pyproject.toml"), - name: str = "inboard", - version: str = "0.1.0", -) -> dict: - """Create a dictionary of keys and values corresponding to pydantic model fields. - When instantiating the pydantic model, the dictionary can be unpacked and used to - set fields in the model. - """ - try: - pyproject = dict(toml.load(pyproject_path))["tool"]["poetry"] - return {key: pyproject.get(key) for key in fields if pyproject.get(key)} - except Exception: - return {"name": name, "version": version} - - -class Settings(BaseSettings): - """[_pydantic_ settings model](https://pydantic-docs.helpmanual.io/usage/settings/) - --- - Settings are from [`pyproject.toml`](https://python-poetry.org/docs/pyproject/), - and are tested by `test_metadata.py`. The `__fields__` attribute provides a - dictionary of pydantic model fields, without having to instantiate the model. - """ - - name: str - version: str - description: Optional[str] - authors: Optional[List[str]] - license: Optional[str] - homepage: Optional[str] - readme: Optional[str] - include: Optional[List[str]] - keywords: Optional[List[str]] - classifiers: Optional[List[str]] - - def __init__(self, **fields: dict) -> None: - super().__init__(**set_fields_from_pyproject(self.__fields__), **fields) - - class GetRoot(BaseModel): Hello: str = "World" diff --git a/poetry.lock b/poetry.lock index a060061..87eaa42 100644 --- a/poetry.lock +++ b/poetry.lock @@ -687,7 +687,7 @@ full = ["aiofiles", "graphene", "itsdangerous", "jinja2", "python-multipart", "p name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" -category = "main" +category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" @@ -823,14 +823,14 @@ docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [extras] -all = ["fastapi", "toml"] -fastapi = ["fastapi", "toml"] +all = ["fastapi"] +fastapi = ["fastapi"] starlette = ["starlette"] [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "97b09263fe31a7362a7298e7451d0964d36cfbd0532b88fcc04dca7ca667d897" +content-hash = "c34b2532bad50015243fd2989260ff1d2cd84fcaa34c312dc78dfd4fd0409f29" [metadata.files] appdirs = [ diff --git a/pyproject.toml b/pyproject.toml index ed1d0ba..bce2bf3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,6 @@ gunicorn = "^20" uvicorn = {version = "^0.15", extras = ["standard"]} fastapi = {version = "^0.68", optional = true} starlette = {version = "^0.14", optional = true} -toml = {version = ">=0.10", optional = true} [tool.poetry.dev-dependencies] black = {version = "21.7b0", allow-prereleases = true} @@ -50,8 +49,8 @@ pytest-timeout = "^1.4" requests = "^2.24" [tool.poetry.extras] -all = ["fastapi", "toml"] -fastapi = ["fastapi", "toml"] +all = ["fastapi"] +fastapi = ["fastapi"] starlette = ["starlette"] [tool.poetry.urls] diff --git a/tests/conftest.py b/tests/conftest.py index 959f56c..e696946 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,7 +13,6 @@ from inboard.app.main_base import app as base_app from inboard.app.main_fastapi import app as fastapi_app from inboard.app.main_starlette import app as starlette_app -from inboard.app.utilities_fastapi import Settings @pytest.fixture(scope="session") @@ -161,12 +160,6 @@ def pre_start_script_tmp_sh(tmp_path: Path) -> Path: return Path(tmp_file) -@pytest.fixture(scope="session") -def settings() -> Settings: - """Instantiate a _pydantic_ Settings model for testing.""" - return Settings() - - @pytest.fixture(scope="session") def uvicorn_options_default() -> dict: """Return default options used by `uvicorn.run()` for use in test assertions.""" diff --git a/tests/test_metadata.py b/tests/test_metadata.py deleted file mode 100644 index 026d49e..0000000 --- a/tests/test_metadata.py +++ /dev/null @@ -1,39 +0,0 @@ -from pathlib import Path - -from inboard.app.utilities_fastapi import Settings, set_fields_from_pyproject - - -def test_set_fields_from_pyproject(settings: Settings) -> None: - """Assert that pyproject.toml is successfully loaded and parsed.""" - pyproject = set_fields_from_pyproject( - Settings.__fields__, - pyproject_path=Path("pyproject.toml"), - ) - assert pyproject != {"name": "inboard", "version": "0.1.0"} - assert str(pyproject["name"]) == settings.name == "inboard" - assert str(pyproject["version"]) == settings.version - assert str(pyproject["description"]) == settings.description - assert list(pyproject["authors"]) == settings.authors - assert str(pyproject["license"]) == settings.license == "MIT" - assert str(pyproject["homepage"]) == settings.homepage - assert str(pyproject["readme"]) == settings.readme - assert list(pyproject["include"]) == settings.include - assert "inboard/py.typed" in settings.include - assert list(pyproject["keywords"]) == settings.keywords - assert "fastapi" in settings.keywords - assert list(pyproject["classifiers"]) == settings.classifiers - assert "Topic :: Internet :: WWW/HTTP :: HTTP Servers" in settings.classifiers - - -def test_set_fields_error() -> None: - """Assert that default dict is loaded when pyproject.toml is not found.""" - pyproject = set_fields_from_pyproject( - Settings.__fields__, - pyproject_path=Path("pyproject.toml"), - ) - pyproject_default = set_fields_from_pyproject( - Settings.__fields__, - pyproject_path=Path("error"), - ) - assert pyproject != pyproject_default - assert pyproject_default == {"name": "inboard", "version": "0.1.0"}