Skip to content

Commit

Permalink
Fixed #72
Browse files Browse the repository at this point in the history
  • Loading branch information
khoroshevskyi committed Mar 1, 2024
1 parent f5ddc50 commit fc75de0
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 152 deletions.
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
# bedhost
<h1 align="center">bedhost</h1>

[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Github badge](https://img.shields.io/badge/source-github-354a75?logo=github)](https://github.com/databio/bedhost)


`bedhost` is a Python FastAPI module for the API that powers BEDbase
It needs a path to the *bedbase configuration file*, which can be provided either via `-c`/`--config` argument or read from `$BEDBASE_CONFIG` environment variable.

---

**Deployed public instance**: <a href="https://bedbase.org/" target="_blank">https://bedbase.org/</a>

**Documentation**: <a href="https://docs.bedbase.org/" target="_blank">https://docs.bedbase.org/bedhost</a>

**API**: <a href="https://api.bedbase.org/" target="_blank">https://api.bedbase.org/</a>

**Source Code**: <a href="https://github.com/databio/bedhost/" target="_blank">https://github.com/databio/bedhost/</a>

---


## Running for development

Running with `uvicorn` provides auto-reload. To configure, this assumes you have previously set up `databio/secrets`.
Expand All @@ -29,6 +45,7 @@ Now, you can access the service at [http://127.0.0.1:8000](http://127.0.0.1:8000
- 127.0.0.1:8000/bed/78c0e4753d04b238fc07e4ebe5a02984/metadata
- 127.0.0.1:8000/bed/78c0e4753d04b238fc07e4ebe5a02984/metadata?attr_ids=md5sum&attr_ids=genome

----
## Running the server in Docker

### Building image
Expand All @@ -43,6 +60,7 @@ Existing images can be found [at dockerhub](https://hub.docker.com/r/databio/bed

Configuration settings and deployment instructions are in the `bedbase.org` repository.

---

## Deploying updates automatically

Expand Down
2 changes: 1 addition & 1 deletion bedhost/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.2.0"
__version__ = "0.3.0"
35 changes: 0 additions & 35 deletions bedhost/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,41 +32,6 @@
"number": {"min": 0, "step": 0.01},
"string": None,
}
NUMERIC_OPERATORS = [
"equal",
"not_equal",
"greater",
"greater_or_equal",
"between",
"less",
"less_or_equal",
"is_null",
"is_not_null",
]
TEXT_OPERATORS = ["equal", "not_equal", "in", "not_in", "is_null", "is_not_null"]
OPERATORS_MAPPING = {
"string": TEXT_OPERATORS,
"number": NUMERIC_OPERATORS,
"integer": NUMERIC_OPERATORS,
}
INIT_POSTGRES_CONDITION = "gc_content>0.5"
INIT_QUERYBUILDER = {
"condition": "AND",
"rules": [
{
"id": "gc_content",
"field": "gc_content",
"type": "double",
"input": "number",
"operator": "greater",
"value": 0.5,
}
],
"valid": True,
}
CUR_RESULT = "current_result"
CUR_RULES = "current_rules"


class FIG_FORMAT(str, Enum):
png = "png"
Expand Down
218 changes: 106 additions & 112 deletions bedhost/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,8 @@

from bbconf import BedBaseConf
from starlette.responses import FileResponse, RedirectResponse, JSONResponse
from typing import List
from urllib import parse

from . import _LOGGER
from .const import (
TYPES_MAPPING,
VALIDATIONS_MAPPING,
OPERATORS_MAPPING,
)
from .exceptions import BedHostException


Expand All @@ -22,6 +15,7 @@ class BedHostConf(BedBaseConf):
def __init__(self, config_path: str = None):
super().__init__(config_path)

# TODO: should it be moved to bbconf?
def serve_file(self, path: str, remote: bool = None):
"""
Serve a local or remote file
Expand Down Expand Up @@ -73,111 +67,111 @@ def bedset_retrieve(self, digest: str, column: str) -> dict:
except KeyError:
return {}


def get_search_setup(schema: dict):
"""
Create a query setup for QueryBuilder to interface the DB.
:param dict schema: pipestat schema
:return list[dict]: a list dictionaries with search setup to use
in QueryBuilder
"""
setup_dicts = []
for col_name, col_schema in schema.items():
try:
setup_dict = {
"id": col_name,
"label": col_schema["description"],
"type": TYPES_MAPPING[col_schema["type"]],
"validation": VALIDATIONS_MAPPING[col_schema["type"]],
"operators": OPERATORS_MAPPING[col_schema["type"]],
}
except (AttributeError, KeyError):
_LOGGER.warning(
f"Database column '{col_name}' of type "
f"'{col_schema['type']}' has no query builder "
f"settings predefined, skipping."
)
else:
setup_dicts.append(setup_dict)
return setup_dicts


def construct_search_data(hits: list, request) -> List[List[str]]:
"""
Construct a list of links to display as the search result
:param Iterable[str] hits: ids to compose the list for
:param starlette.requests.Request request: request for the context
:return Iterable[list]: results to display
"""
template_data = []
for h in hits:
bed_data_url_template = (
request.url_for("bedfile") + f"?md5sum={h['md5sum']}&format="
)
template_data.append(
[h["name"]]
+ [bed_data_url_template + ext for ext in ["html", "bed", "json"]]
)
return template_data


def get_mounted_symlink_path(symlink: str) -> str:
"""
Get path to the symlinks target on a mounted filesystem volume.
Accounts for both transformed and non-transformed symlink targets
:param str symlink: absolute symlink path
:return str: path to the symlink target on the mounted volume
"""

def _find_mount_point(path):
path = os.path.abspath(path)
while not os.path.ismount(path):
path = os.path.dirname(path)
return path

link_tgt = os.readlink(symlink)
if not os.path.isabs(link_tgt):
_LOGGER.debug("Volume mounted with symlinks transformation")
return os.path.abspath(os.path.join(os.path.dirname(symlink), link_tgt))
mnt_point = _find_mount_point(symlink)
first = os.path.relpath(symlink, mnt_point).split("/")[0]
common_idx = link_tgt.split("/").index(first)
rel_tgt = os.path.join(*link_tgt.split("/")[common_idx:])
return os.path.join(mnt_point, rel_tgt)


def get_all_bedset_urls_mapping(bbc: BedBaseConf, request):
"""
Get a mapping of all bedset ids and corrsponding splaspages urls
:param bbconf.BedBaseConf bbc: bedbase configuration object
:param starlette.requests.Request request: request context for url generation
:return Mapping: a mapping of bedset ids and the urls to the corresponding splashpages
"""
hits = bbc.bedset.backend.select(columns=["name", "md5sum"])
if not hits:
return
# TODO: don't hardcode url path element name, use operationID?
return {
hit.name: get_param_url(request.url_for("bedsetsplash"), {"md5sum": hit.md5sum})
for hit in hits
}


def get_param_url(url, params):
"""
Create parametrized URL
:param str url: URL base to parametrize
:param Mapping params: a mapping of URL parameters and values
:return str: parametrized URL
"""
if not params:
return url
return url + "?" + parse.urlencode(params)
# TODO: remove this function if everything works
# def get_search_setup(schema: dict):
# """
# Create a query setup for QueryBuilder to interface the DB.
#
# :param dict schema: pipestat schema
# :return list[dict]: a list dictionaries with search setup to use
# in QueryBuilder
# """
# setup_dicts = []
# for col_name, col_schema in schema.items():
# try:
# setup_dict = {
# "id": col_name,
# "label": col_schema["description"],
# "type": TYPES_MAPPING[col_schema["type"]],
# "validation": VALIDATIONS_MAPPING[col_schema["type"]],
# "operators": OPERATORS_MAPPING[col_schema["type"]],
# }
# except (AttributeError, KeyError):
# _LOGGER.warning(
# f"Database column '{col_name}' of type "
# f"'{col_schema['type']}' has no query builder "
# f"settings predefined, skipping."
# )
# else:
# setup_dicts.append(setup_dict)
# return setup_dicts
#
#
# def construct_search_data(hits: list, request) -> List[List[str]]:
# """
# Construct a list of links to display as the search result
#
# :param Iterable[str] hits: ids to compose the list for
# :param starlette.requests.Request request: request for the context
# :return Iterable[list]: results to display
# """
# template_data = []
# for h in hits:
# bed_data_url_template = (
# request.url_for("bedfile") + f"?md5sum={h['md5sum']}&format="
# )
# template_data.append(
# [h["name"]]
# + [bed_data_url_template + ext for ext in ["html", "bed", "json"]]
# )
# return template_data
#
#
# def get_mounted_symlink_path(symlink: str) -> str:
# """
# Get path to the symlinks target on a mounted filesystem volume.
# Accounts for both transformed and non-transformed symlink targets
#
# :param str symlink: absolute symlink path
# :return str: path to the symlink target on the mounted volume
# """
#
# def _find_mount_point(path):
# path = os.path.abspath(path)
# while not os.path.ismount(path):
# path = os.path.dirname(path)
# return path
#
# link_tgt = os.readlink(symlink)
# if not os.path.isabs(link_tgt):
# _LOGGER.debug("Volume mounted with symlinks transformation")
# return os.path.abspath(os.path.join(os.path.dirname(symlink), link_tgt))
# mnt_point = _find_mount_point(symlink)
# first = os.path.relpath(symlink, mnt_point).split("/")[0]
# common_idx = link_tgt.split("/").index(first)
# rel_tgt = os.path.join(*link_tgt.split("/")[common_idx:])
# return os.path.join(mnt_point, rel_tgt)
#
#
# def get_all_bedset_urls_mapping(bbc: BedBaseConf, request):
# """
# Get a mapping of all bedset ids and corrsponding splaspages urls
#
# :param bbconf.BedBaseConf bbc: bedbase configuration object
# :param starlette.requests.Request request: request context for url generation
# :return Mapping: a mapping of bedset ids and the urls to the corresponding splashpages
# """
# hits = bbc.bedset.backend.select(columns=["name", "md5sum"])
# if not hits:
# return
# # TODO: don't hardcode url path element name, use operationID?
# return {
# hit.name: get_param_url(request.url_for("bedsetsplash"), {"md5sum": hit.md5sum})
# for hit in hits
# }


# def get_param_url(url, params):
# """
# Create parametrized URL
#
# :param str url: URL base to parametrize
# :param Mapping params: a mapping of URL parameters and values
# :return str: parametrized URL
# """
# if not params:
# return url
# return url + "?" + parse.urlencode(params)


def get_openapi_version(app):
Expand Down
5 changes: 2 additions & 3 deletions requirements/requirements-all.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
bbconf>=0.4.0
bbconf>=0.4.1
fastapi>=0.103.0
geniml>=0.1.0
logmuse>=0.2.7
markdown
requests
setuptools
uvicorn
yacman>=0.9.2
markdown
yacman>=0.9.2

0 comments on commit fc75de0

Please sign in to comment.