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

Introspective metadata endpoint #125

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
2 changes: 2 additions & 0 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from app.models.error import HTTPValidationError
from app.redis_cache import RedisSettings, redis_plugin
from app.routers import (
admin,
datafilter,
dataresource,
function,
Expand Down Expand Up @@ -80,6 +81,7 @@ def create_app() -> FastAPI:
""",
)
available_routers = [
admin,
session,
dataresource,
datafilter,
Expand Down
120 changes: 120 additions & 0 deletions app/routers/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
"""Administrative endpoint."""
import asyncio
import platform
from collections import defaultdict
from importlib.metadata import distributions

from fastapi import APIRouter, Request
from fastapi.responses import JSONResponse
from oteapi.plugins.entry_points import (
EntryPointStrategyCollection,
StrategyType,
get_strategy_entry_points,
)

from app import __version__

ROUTER = APIRouter(prefix="/admin", include_in_schema=False)


@ROUTER.get("/info", include_in_schema=False)
async def get_info(request: Request) -> JSONResponse: # pylint: disable=too-many-locals
"""Write out introspective information about the service."""
process_pip_freeze = await asyncio.create_subprocess_shell(

Check warning on line 23 in app/routers/admin.py

View check run for this annotation

Codecov / codecov/patch

app/routers/admin.py#L23

Added line #L23 was not covered by tests
"pip freeze --all", stdout=asyncio.subprocess.PIPE
)
process_pip_list = await asyncio.create_subprocess_shell(

Check warning on line 26 in app/routers/admin.py

View check run for this annotation

Codecov / codecov/patch

app/routers/admin.py#L26

Added line #L26 was not covered by tests
"pip list --format=freeze", stdout=asyncio.subprocess.PIPE
)
pip_freeze_out, _ = await process_pip_freeze.communicate()
pip_list_out, _ = await process_pip_list.communicate()

Check warning on line 30 in app/routers/admin.py

View check run for this annotation

Codecov / codecov/patch

app/routers/admin.py#L29-L30

Added lines #L29 - L30 were not covered by tests

pip_freeze = pip_freeze_out.decode().splitlines()
pip_list = [

Check warning on line 33 in app/routers/admin.py

View check run for this annotation

Codecov / codecov/patch

app/routers/admin.py#L32-L33

Added lines #L32 - L33 were not covered by tests
line for line in pip_list_out.decode().splitlines() if line not in pip_freeze
]

py_dependencies: dict[str, dict[str, str]] = {

Check warning on line 37 in app/routers/admin.py

View check run for this annotation

Codecov / codecov/patch

app/routers/admin.py#L37

Added line #L37 was not covered by tests
"std": {},
"editable": {},
"external": {},
}
for line in pip_freeze:
if "==" in line:
split_line = line.split("==", maxsplit=1)
py_dependencies["std"][split_line[0]] = split_line[1]

Check warning on line 45 in app/routers/admin.py

View check run for this annotation

Codecov / codecov/patch

app/routers/admin.py#L42-L45

Added lines #L42 - L45 were not covered by tests
else:
py_dependencies["external"][line] = ""
for line in pip_list:
split_line = line.split("==", maxsplit=1)
py_dependencies["editable"][split_line[0]] = split_line[1]

Check warning on line 50 in app/routers/admin.py

View check run for this annotation

Codecov / codecov/patch

app/routers/admin.py#L47-L50

Added lines #L47 - L50 were not covered by tests

package_to_dist: dict[str, list[str]] = defaultdict(list)
for distribution in distributions():
if distribution.files and "top_level.txt" in [

Check warning on line 54 in app/routers/admin.py

View check run for this annotation

Codecov / codecov/patch

app/routers/admin.py#L52-L54

Added lines #L52 - L54 were not covered by tests
_.name for _ in distribution.files
]:
for package in distribution.read_text("top_level.txt").splitlines(): # type: ignore
package_to_dist[package].append(distribution.metadata["Name"])

Check warning on line 58 in app/routers/admin.py

View check run for this annotation

Codecov / codecov/patch

app/routers/admin.py#L57-L58

Added lines #L57 - L58 were not covered by tests

oteapi_strategies = EntryPointStrategyCollection()
for strategy_type in StrategyType:
oteapi_strategies.add(*get_strategy_entry_points(strategy_type=strategy_type))
strategies_dict: "dict[str, list[dict[str, str]]]" = defaultdict(list)
for oteapi_strategy in oteapi_strategies:
strategies_dict[oteapi_strategy.type.value].append(

Check warning on line 65 in app/routers/admin.py

View check run for this annotation

Codecov / codecov/patch

app/routers/admin.py#L60-L65

Added lines #L60 - L65 were not covered by tests
{
"name": oteapi_strategy.name,
"module": oteapi_strategy.module,
"cls": oteapi_strategy.implementation_name,
}
)

return JSONResponse(

Check warning on line 73 in app/routers/admin.py

View check run for this annotation

Codecov / codecov/patch

app/routers/admin.py#L73

Added line #L73 was not covered by tests
content={
"version": {
"API": __version__.split(".", maxsplit=1)[0],
"OTEAPI Services": __version__,
"OTEAPI Core": py_dependencies["std"].get(
"oteapi-core",
py_dependencies["editable"].get(
"oteapi-core", _get_local_oteapi_version()
),
),
},
"environment": {
"architecture": platform.architecture() + (platform.machine(),),
"node": platform.node(),
"platform": platform.platform(),
"python": {
"compiler": platform.python_compiler(),
"dependencies": py_dependencies,
"version": platform.python_version(),
"build": platform.python_build(),
"implementation": platform.python_implementation(),
},
},
"oteapi": {
"registered_strategies": strategies_dict,
"plugins": {
package_to_dist[strategy.package][0]: py_dependencies["std"].get(
package_to_dist[strategy.package][0],
py_dependencies["editable"].get(
package_to_dist[strategy.package][0],
"Unknown",
),
)
for strategy in oteapi_strategies
},
},
"routes": [route.path for route in request.app.routes],
}
)


def _get_local_oteapi_version() -> str:
"""Import `oteapi` and return `__version__`."""
# pylint: disable=import-outside-toplevel
from oteapi import __version__ as __oteapi_version__

Check warning on line 118 in app/routers/admin.py

View check run for this annotation

Codecov / codecov/patch

app/routers/admin.py#L118

Added line #L118 was not covered by tests

return __oteapi_version__

Check warning on line 120 in app/routers/admin.py

View check run for this annotation

Codecov / codecov/patch

app/routers/admin.py#L120

Added line #L120 was not covered by tests
1 change: 0 additions & 1 deletion entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ if [ -n "${OTEAPI_PLUGIN_PACKAGES}" ]; then
IFS="|"
for plugin in ${OTEAPI_PLUGIN_PACKAGES}; do echo "* ${plugin}"; done
for plugin in ${OTEAPI_PLUGIN_PACKAGES}; do eval "pip install -q -c constraints.txt ${plugin}" || FAILED_PLUGIN_INSTALLS=${FAILED_PLUGIN_INSTALLS:+$FAILED_PLUGIN_INSTALLS:}${plugin} && continue; done
current_packages="$(pip freeze)"
if [ -n "${FAILED_PLUGIN_INSTALLS}" ]; then
echo "The following plugins failed to install:"
for plugin in ${FAILED_PLUGIN_INSTALLS}; do echo "* ${plugin}"; done
Expand Down
Loading