Skip to content

Commit

Permalink
Use FastAPI dependency overrides
Browse files Browse the repository at this point in the history
Instead of manually setting a global variable to configure the test
server's Butler, use FastAPI's dependency injection framework.
  • Loading branch information
dhirving committed Oct 24, 2023
1 parent a7cc1ff commit fc9af88
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 34 deletions.
3 changes: 2 additions & 1 deletion python/lsst/daf/butler/remote_butler/server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from ._server import app
from ._factory import Factory
from ._server import app, factory_dependency
36 changes: 36 additions & 0 deletions python/lsst/daf/butler/remote_butler/server/_factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# This file is part of daf_butler.
#
# Developed for the LSST Data Management System.
# This product includes software developed by the LSST Project
# (http://www.lsst.org).
# See the COPYRIGHT file at the top-level directory of this distribution
# for details of code ownership.
#
# This software is dual licensed under the GNU General Public License and also
# under a 3-clause BSD license. Recipients may choose which of these licenses
# to use; please see the files gpl-3.0.txt and/or bsd_license.txt,
# respectively. If you choose the GPL option then the following text applies
# (but note that there is still no warranty even if you opt for BSD instead):
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from lsst.daf.butler import Butler


class Factory:
def __init__(self, *, butler: Butler):
self._butler = butler

def create_butler(self) -> Butler:
return self._butler
31 changes: 10 additions & 21 deletions python/lsst/daf/butler/remote_butler/server/_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,15 @@
__all__ = ()

import logging
from functools import cache
from typing import Any

from fastapi import Depends, FastAPI
from fastapi.middleware.gzip import GZipMiddleware
from lsst.daf.butler import Butler

from ._factory import Factory

BUTLER_ROOT = "ci_hsc_gen3/DATA"

log = logging.getLogger("excalibur")
Expand All @@ -44,28 +47,13 @@
app.add_middleware(GZipMiddleware, minimum_size=1000)


GLOBAL_READWRITE_BUTLER: Butler | None = None
GLOBAL_READONLY_BUTLER: Butler | None = None


def _make_global_butler() -> None:
global GLOBAL_READONLY_BUTLER, GLOBAL_READWRITE_BUTLER
if GLOBAL_READONLY_BUTLER is None:
GLOBAL_READONLY_BUTLER = Butler.from_config(BUTLER_ROOT, writeable=False)
if GLOBAL_READWRITE_BUTLER is None:
GLOBAL_READWRITE_BUTLER = Butler.from_config(BUTLER_ROOT, writeable=True)


def butler_readonly_dependency() -> Butler:
"""Return global read-only butler."""
_make_global_butler()
return Butler.from_config(butler=GLOBAL_READONLY_BUTLER)
@cache
def _make_global_butler() -> Butler:
return Butler.from_config(BUTLER_ROOT)

Check warning on line 52 in python/lsst/daf/butler/remote_butler/server/_server.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/remote_butler/server/_server.py#L52

Added line #L52 was not covered by tests


def butler_readwrite_dependency() -> Butler:
"""Return read-write butler."""
_make_global_butler()
return Butler.from_config(butler=GLOBAL_READWRITE_BUTLER)
def factory_dependency() -> Factory:
return Factory(butler=_make_global_butler())

Check warning on line 56 in python/lsst/daf/butler/remote_butler/server/_server.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/remote_butler/server/_server.py#L56

Added line #L56 was not covered by tests


@app.get("/butler/")
Expand All @@ -75,6 +63,7 @@ def read_root() -> str:


@app.get("/butler/v1/universe", response_model=dict[str, Any])
def get_dimension_universe(butler: Butler = Depends(butler_readonly_dependency)) -> dict[str, Any]:
def get_dimension_universe(factory: Factory = Depends(factory_dependency)) -> dict[str, Any]:
"""Allow remote client to get dimensions definition."""
butler = factory.create_butler()
return butler.dimensions.dimensionConfig.toDict()
22 changes: 10 additions & 12 deletions tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,15 @@
import unittest

try:
import lsst.daf.butler.remote_butler.server._server

# Failing to import any of these should disable the tests.
from fastapi.testclient import TestClient
from lsst.daf.butler.remote_butler import RemoteButler
from lsst.daf.butler.remote_butler.server._server import app
from lsst.daf.butler.remote_butler.server import Factory, app, factory_dependency
except ImportError:
TestClient = None
app = None

from lsst.daf.butler import CollectionType
from lsst.daf.butler import Butler
from lsst.daf.butler.tests.utils import MetricTestRepo, makeTestTempDir, removeTestTempDir

TESTDIR = os.path.abspath(os.path.dirname(__file__))
Expand Down Expand Up @@ -67,21 +65,21 @@ def setUpClass(cls):
# First create a butler and populate it.
cls.root = makeTestTempDir(TESTDIR)
cls.repo = MetricTestRepo(root=cls.root, configFile=os.path.join(TESTDIR, "config/basic/butler.yaml"))
# Override the server's Butler initialization to point at our test repo
server_butler = Butler.from_config(cls.root)

# Add a collection chain.
cls.repo.butler.registry.registerCollection("chain", CollectionType.CHAINED)
cls.repo.butler.registry.setCollectionChain("chain", ["ingest"])
def create_factory_dependency():
return Factory(butler=server_butler)

# Globally change where the server thinks its butler repository
# is located. This will prevent any other server tests and is
# not a long term fix.
lsst.daf.butler.remote_butler.server._server.BUTLER_ROOT = cls.root
cls.client = TestClient(app)
app.dependency_overrides[factory_dependency] = create_factory_dependency

# Set up the RemoteButler that will connect to the server
cls.client = TestClient(app)
cls.butler = _make_remote_butler(cls.client)

@classmethod
def tearDownClass(cls):
del app.dependency_overrides[factory_dependency]
removeTestTempDir(cls.root)

def test_simple(self):
Expand Down

0 comments on commit fc9af88

Please sign in to comment.