diff --git a/bedhost/__init__.py b/bedhost/__init__.py index 9c6ca97e..303173f5 100644 --- a/bedhost/__init__.py +++ b/bedhost/__init__.py @@ -1,5 +1,8 @@ +import logging import logmuse from .const import PKG_NAME _LOGGER = logmuse.init_logger(PKG_NAME) + +logging.getLogger("bbconf").setLevel(logging.DEBUG) diff --git a/bedhost/const.py b/bedhost/const.py index bb061416..07420719 100644 --- a/bedhost/const.py +++ b/bedhost/const.py @@ -19,7 +19,7 @@ # for now bedstat version is hard coded ALL_VERSIONS = { - "apiserver_version": SERVER_VERSION, + "bedhost_version": SERVER_VERSION, "bbconf_version": bbconf_v, "python_version": python_version(), } @@ -27,7 +27,7 @@ TEMPLATES_PATH = os.path.join( os.path.dirname(os.path.abspath(__file__)), TEMPLATES_DIRNAME ) -STATIC_DIRNAME = "static" +STATIC_DIRNAME = "../docs" STATIC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), STATIC_DIRNAME) UI_PATH = os.path.join(os.path.dirname(__file__), "static", "bedhost-ui") diff --git a/bedhost/helpers.py b/bedhost/helpers.py index 5d30ac32..739350e8 100644 --- a/bedhost/helpers.py +++ b/bedhost/helpers.py @@ -2,7 +2,7 @@ from bbconf import BedBaseConf from fastapi.staticfiles import StaticFiles -from starlette.responses import FileResponse, RedirectResponse +from starlette.responses import FileResponse, RedirectResponse, JSONResponse from typing import List, Union from urllib import parse @@ -34,7 +34,7 @@ def serve_file(self, path: str, remote: bool = None): :param bool remote: whether to redirect to a remote source or serve local :exception FileNotFoundError: if file not found """ - remote = remote or self.is_remote + remote = remote or True if remote: _LOGGER.info(f"Redirecting to: {path}") return RedirectResponse(path) @@ -208,7 +208,7 @@ def attach_routers(app): return app -def configure(bbconf_file_path): +def configure(bbconf_file_path, app): try: # bbconf_file_path = os.environ.get("BEDBASE_CONFIG") or None _LOGGER.info(f"Loading config: '{bbconf_file_path}'") @@ -238,48 +238,7 @@ def configure(bbconf_file_path): return bbc -# def get_id_map(bbc, table_name, file_type): -# """ -# Get a dict for avalible file/figure ids -# -# :param str table_name: table name to query -# :param st file_type: "file" or "image" -# :return dict -# """ -# -# id_map = {} -# -# schema = serve_schema_for_table(bbc=bbc, table_name=table_name) -# # This is basically just doing this: -# # if table_name == BED_TABLE: -# # schema = bbc.bed.schema -# # if table_name == BEDSET_TABLE: -# # schema = bbc.bedset.schema -# # TODO: Eliminate the need for bedhost to be aware of table names; this should be abstracted away by bbconf/pipestat -# for key, value in schema.sample_level_data.items(): -# if value["type"] == file_type: -# id_map[value["label"]] = key -# -# return id_map - - -# def get_enum_map(bbc, table_name, file_type): -# """ -# Get a dict of file/figure labels - -# :param str table_name: table name to query -# :param st file_type: "file" or "image" -# :return dict -# """ - -# enum_map = {} -# _LOGGER.debug(f"Getting enum map for {file_type} in {table_name}") - -# # TO FIX: I think we need a different way to get the schema -# schema = serve_schema_for_table(bbc=bbc, table_name=table_name) - -# for key, value in schema.sample_level_data.items(): -# if value["type"] == file_type: -# enum_map[value["label"]] = value["label"] - -# return enum_map +def drs_response(status_code, msg): + """Helper function to make quick DRS responses""" + content = {"status_code": status_code, "msg": msg} + return JSONResponse(status_code=status_code, content=content) diff --git a/bedhost/main.py b/bedhost/main.py index fa59b9f4..7b748f67 100644 --- a/bedhost/main.py +++ b/bedhost/main.py @@ -2,12 +2,25 @@ import sys import uvicorn -from fastapi import FastAPI +from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import JSONResponse, HTMLResponse from typing import Dict +from urllib.parse import urlparse +from fastapi import Response, HTTPException + +from bbconf.exceptions import * +from pipestat.exceptions import RecordNotFoundError, ColumnNotFoundError + from . import _LOGGER -from .helpers import FileResponse, configure, attach_routers, get_openapi_version +from .helpers import ( + FileResponse, + configure, + attach_routers, + get_openapi_version, + drs_response, +) from .cli import build_parser from .const import ( ALL_VERSIONS, @@ -19,11 +32,36 @@ SERVER_VERSION, ) +tags_metadata = [ + { + "name": "home", + "description": "General landing page and service info", + }, + { + "name": "objects", + "description": "Download BED files or BEDSET files via [GA4GH DRS standard](https://ga4gh.github.io/data-repository-service-schemas/). For details, see [BEDbase Developer Guide](/docs/guide).", + + }, + { + "name": "bed", + "description": "Endpoints for retrieving metadata for BED records", + }, + { + "name": "bedset", + "description": "Endpoints for retrieving metadata for BEDSET records", + }, + { + "name": "search", + "description": "Discovery-oriented endpoints for finding records of interest", + }, +] + app = FastAPI( title=PKG_NAME, description="BED file/sets statistics and image server API", version=SERVER_VERSION, docs_url="/docs", + openapi_tags=tags_metadata, ) origins = [ @@ -42,23 +80,182 @@ allow_headers=["*"], ) +import markdown +from fastapi.templating import Jinja2Templates +templates = Jinja2Templates(directory="bedhost/templates", autoescape=False) + +@app.get("/", summary="API intro page", tags=["home"]) +async def index(request: Request): + """ + Display the index UI page + """ + return render_markdown("index.md", request) + + +@app.get("/docs/changelog", summary="Release notes", response_class=HTMLResponse, tags=["home"]) +async def changelog(request: Request): + return render_markdown("changelog.md", request) + +@app.get("/docs/guide", summary="Developer guide", response_class=HTMLResponse, tags=["home"]) +async def guide(request: Request): + return render_markdown("guide.md", request) + +def render_markdown(filename: str, request: Request): + with open(os.path.join(STATIC_PATH, filename), "r", encoding="utf-8") as input_file: + text = input_file.read() + content = markdown.markdown(text) + return templates.TemplateResponse("page.html", {"request": request, "content": content}) + + +@app.get("/service-info", summary="GA4GH service info", tags=["home"]) +async def service_info(): + """ + Returns information about this service, such as versions, name, etc. + """ + all_versions = ALL_VERSIONS + service_version = all_versions["bedhost_version"] + all_versions.update({"openapi_version": get_openapi_version(app)}) + ret = { + "id": "org.bedbase.api", + "name": "BEDbase API", + "type": { + "group": "org.databio", + "artifact": "bedbase", + "version": service_version, + }, + "description": "An API providing genomic interval data and metadata", + "organization": {"name": "Databio Lab", "url": "https://databio.org"}, + "contactUrl": "https://github.com/databio/bedbase/issues", + "documentationUrl": "https://bedbase.org", + "updatedAt": "2023-10-25T00:00:00Z", + "environment": "dev", + "version": service_version, + "component_versions": all_versions, + } + return JSONResponse(content=ret) + +@app.get( + "/objects/{object_id}", + summary="Get DRS object metadata", + tags=["objects"], +) +async def get_drs_object_metadata(object_id: str, req: Request): + """ + Returns metadata about a DrsObject. + """ + ids = parse_bedbase_drs_object_id(object_id) + base_uri = urlparse(str(req.url)).netloc + return bbc.get_drs_metadata( + ids["record_type"], ids["record_id"], ids["result_id"], base_uri + ) + + +@app.get( + "/objects/{object_id}/access/{access_id}", + summary="Get URL where you can retrieve files", + tags=["objects"], +) +async def get_object_bytes_url(object_id: str, access_id: str): + """ + Returns a URL that can be used to fetch the bytes of a DrsObject. + """ + ids = parse_bedbase_drs_object_id(object_id) + return bbc.get_object_uri( + ids["record_type"], ids["record_id"], ids["result_id"], access_id + ) + + +@app.head( + "/objects/{object_id}/access/{access_id}/bytes", include_in_schema=False +) # Required by UCSC track hubs +@app.get( + "/objects/{object_id}/access/{access_id}/bytes", + summary="Download actual files", + tags=["objects"], +) +async def get_object_bytes(object_id: str, access_id: str): + """ + Returns the bytes of a DrsObject. + """ + ids = parse_bedbase_drs_object_id(object_id) + return bbc.serve_file( + bbc.get_object_uri( + ids["record_type"], ids["record_id"], ids["result_id"], access_id + ) + ) + -@app.get("/") -async def index(): +@app.get( + "/objects/{object_id}/access/{access_id}/thumbnail", + summary="Download thumbnail", + tags=["objects"], +) +async def get_object_thumbnail(object_id: str, access_id: str): """ - Display the dummy index UI page + Returns the bytes of a thumbnail of a DrsObject """ - return FileResponse(os.path.join(STATIC_PATH, "index.html")) + ids = parse_bedbase_drs_object_id(object_id) + return bbc.serve_file( + bbc.get_thumbnail_uri( + ids["record_type"], ids["record_id"], ids["result_id"], access_id + ) + ) -@app.get("/versions", response_model=Dict[str, str]) -async def get_version_info(): +# DRS-compatible API. +# Requires using `object_id` which has the form: `..` +# for example: `bed.326d5d77c7decf067bd4c7b42340c9a8.bedfile` +# or: `bed.421d2128e183424fcc6a74269bae7934.bedfile` +# bed.326d5d77c7decf067bd4c7b42340c9a8.bedfile +# bed.326d5d77c7decf067bd4c7b42340c9a8.bigbed +def parse_bedbase_drs_object_id(object_id: str): """ - Returns app version information + Parse bedbase object id into its components """ - versions = ALL_VERSIONS - versions.update({"openapi_version": get_openapi_version(app)}) - return versions + try: + record_type, record_id, result_id = object_id.split(".") + except ValueError: + raise HTTPException( + status_code=400, + detail=f"Object ID {object_id} is malformed. Should be of the form ..", + ) + if record_type not in ["bed", "bedset"]: + raise HTTPException( + status_code=400, detail=f"Object type {record_type} is incorrect" + ) + return { + "record_type": record_type, + "record_id": record_id, + "result_id": result_id, + } + + +# General-purpose exception handlers (so we don't have to write try/catch blocks in every endpoint) + +@app.exception_handler(MissingThumbnailError) +async def exc_handler_MissingThumbnailError(req: Request, exc: MissingThumbnailError): + return drs_response(404, "No thumbnail for this object.") + + +@app.exception_handler(BadAccessMethodError) +async def exc_handler_BadAccessMethodError(req: Request, exc: BadAccessMethodError): + return drs_response(404, "Requested access URL was not found.") + + +@app.exception_handler(ColumnNotFoundError) +async def exc_handler_ColumnNotFoundError(req: Request, exc: ColumnNotFoundError): + _LOGGER.error(f"ColumnNotFoundError: {exc}") + return drs_response(404, "Malformed result identifier.") + + +@app.exception_handler(RecordNotFoundError) +async def exc_handler_RecordNotFoundError(req: Request, exc: RecordNotFoundError): + return drs_response(404, "Record not found.") + + +@app.exception_handler(MissingObjectError) +async def exc_handler_MissingObjectError(req: Request, exc: MissingObjectError): + return drs_response(404, "Object not found.") def main(): @@ -73,7 +270,7 @@ def main(): _LOGGER.info(f"Running {PKG_NAME} app...") bbconf_file_path = args.config or os.environ.get("BEDBASE_CONFIG") or None global bbc - bbc = configure(bbconf_file_path) + bbc = configure(bbconf_file_path, app) attach_routers(app) uvicorn.run( app, @@ -87,7 +284,7 @@ def main(): bbconf_file_path = os.environ.get("BEDBASE_CONFIG") or None # must be configured before attaching routers to avoid circular imports global bbc - bbc = configure(bbconf_file_path) + bbc = configure(bbconf_file_path, app) attach_routers(app) else: raise EnvironmentError( diff --git a/bedhost/routers/bed_api.py b/bedhost/routers/bed_api.py index 8d58bd57..50b0d874 100644 --- a/bedhost/routers/bed_api.py +++ b/bedhost/routers/bed_api.py @@ -9,9 +9,10 @@ import tempfile -from fastapi import APIRouter, HTTPException, Query, Response, Path, Depends -from fastapi.responses import PlainTextResponse, StreamingResponse +from fastapi import APIRouter, HTTPException, Path, Query, Response, Request +from fastapi.responses import PlainTextResponse from pipestat.exceptions import RecordNotFoundError +from urllib.parse import urlparse from .. import _LOGGER from ..main import bbc @@ -37,58 +38,58 @@ async def get_bed_genome_assemblies(): """ Returns available genome assemblies in the database """ - return bbc.bed.retrieve_distinct(columns=["genome"]) + return bbc.bed.select_distinct(columns=["genome"]) -@router.get("/count", response_model=int) -async def get_bedfile_count(): +@router.get("/count", summary="Number of BED records in the database", response_model=int) +async def count_bed_record(): """ - Returns the number of bedfiles available in the database + Returns the number of bed records available in the database """ return int(bbc.bed.record_count) -@router.get("/schema", response_model=Dict) +@router.get("/schema", summary="Schema for BED records", response_model=Dict) async def get_bed_schema(): """ - Get bedfiles pipestat schema + Get pipestat schema for BED records used by this database """ - d = bbc.bed.schema.to_dict() + d = bbc.bed.schema.resolved_schema return d -@router.get("/example") -async def get_bed_example(): - x = bbc.bed.get_records(limit=1) - return bbc.bed.retrieve( - record_identifier=x.get("records", [])[0], - ) +@router.get("/example", summary="Get metadata for an example BED record", response_model=Dict) +async def get_example_bed_record(): + return bbc.bed.select_records(limit=1)["records"][0] -@router.get("/all") -async def list_beds(limit: int = 1000, offset: int = 0): - """List all bedfile ids, paged""" - x = bbc.bed.get_records(limit=limit, offset=offset) +@router.get("/list", summary="Paged list of all BED records") +async def list_beds(limit: int = 1000, token: str = None): + """ + To get the first page, leave token field empty. The response will include a + 'next_page_token' field, which can be used to get the next page. + """ + x = bbc.bed.select_records(columns=["name"], limit=limit, cursor=token) return x -@router.get("/{md5sum}/metadata", response_model=DBResponse) -async def get_bedfile_metadata( - md5sum: str = BedDigest, +@router.get("/{bed_id}/metadata", summary="Get metadata for a single BED record") +async def get_bed_metadata( + bed_id: str = BedDigest, attr_id: Optional[str] = Query( None, description="Column name to select from the table" ), ): """ - Returns metadata from selected columns for selected bedfile + Returns metadata from selected columns for selected BED record """ # TODO: should accept a list of columns try: - values = bbc.bed.retrieve(md5sum, attr_id) + values = bbc.bed.retrieve_one(bed_id, attr_id) if not isinstance(values, dict) or attr_id: values = { attr_id: values, - "record_identifier": md5sum, + "record_identifier": bed_id, } if "id" in values: del values["id"] @@ -98,76 +99,13 @@ async def get_bedfile_metadata( _LOGGER.warning("No records matched the query") colnames = [] values = [[]] + return values return {"columns": colnames, "data": values} -# UCSC tool expects a head respond. So we added head request -@router.head("/{md5sum}/file/{file_id}", include_in_schema=False) -@router.get("/{md5sum}/file/{file_id}") -async def get_uri_of_bedfile( - md5sum: str, - file_id: str, -): - res = bbc.bed_retrieve(md5sum, file_id) - path = bbc.get_prefixed_uri(res["path"]) - return bbc.serve_file(path) - - -@router.get("/{md5sum}/file_path/{file_id}") -async def get_file_path_for_bedfile( - md5sum: str, - file_id: str, - remote_class: RemoteClassEnum = Query( - RemoteClassEnum("http"), description="Remote data provider class" - ), -): - try: - res = bbc.bed.retrieve(md5sum, file_id) - except KeyError: - raise HTTPException(status_code=404, detail="Record or attribute not found") - - path = bbc.get_prefixed_uri(res["path"], remote_class.value) - return Response(path, media_type="text/plain") - - -@router.get("/{md5sum}/img/{image_id}") -async def get_image_for_bedfile( - md5sum: str, - image_id: str, - format: FIG_FORMAT = Query("pdf", description="Figure file format"), -): - """ - Returns the specified image associated with the specified bed file. - """ - img = bbc.bed_retrieve(md5sum, image_id) - identifier = img["path" if format == "pdf" else "thumbnail_path"] - path = bbc.get_prefixed_uri(identifier) - return bbc.serve_file(path) - - -@router.get("/{md5sum}/img_path/{image_id}") -async def get_image_path_for_bedfile( - md5sum: str, - image_id: str, - format: Annotated[ - Optional[FIG_FORMAT], Query(description="Figure file format") - ] = "pdf", - remote_class: Annotated[ - Optional[RemoteClassEnum], Query(description="Remote data provider class") - ] = RemoteClassEnum("http"), -): - """ - Returns the bedfile plot with provided ID in provided format - """ - img = bbc.bed_retrieve(md5sum, image_id) - identifier = img["path" if format == "pdf" else "thumbnail_path"] - path = bbc.get_prefixed_uri(identifier, remote_class.value) - return Response(path, media_type="text/plain") - - -@router.get("/{md5sum}/regions/{chr_num}", response_class=PlainTextResponse) +@router.get("/{bed_id}/regions/{chr_num}", summary="Get regions from a BED file that overlap a query region.", response_class=PlainTextResponse) def get_regions_for_bedfile( - md5sum: str = BedDigest, + bed_id: str = BedDigest, chr_num: str = chromosome_number, start: Annotated[ Optional[str], Query(description="query range: start coordinate") @@ -179,23 +117,15 @@ def get_regions_for_bedfile( """ Returns the queried regions with provided ID and optional query parameters """ - hit = bbc.bed.retrieve(record_identifier=md5sum, result_identifier="bigbedfile") - if isinstance(hit, dict): - file = hit.get("bigbedfile") + res = bbc.bed.retrieve_one(bed_id, "bigbedfile") + if isinstance(res, dict): + file = res.get("bigbedfile") else: raise HTTPException( status_code=404, detail="ERROR: bigBed file doesn't exists. Can't query." ) - remote = True if CFG_REMOTE_KEY in bbc.config else False - - path = ( - os.path.join(bbc.config[CFG_REMOTE_KEY]["http"]["prefix"], file["path"]) - if remote - else os.path.join( - bbc.config[CFG_PATH_KEY][CFG_PATH_PIPELINE_OUTPUT_KEY], file["path"] - ) - ) - + path = bbc.get_prefixed_uri(res["path"], access_id="http") + _LOGGER.debug(path) cmd = ["bigBedToBed"] if chr_num: cmd.append(f"-chrom={chr_num}") @@ -227,113 +157,3 @@ def get_regions_for_bedfile( raise HTTPException( status_code=500, detail="ERROR: bigBedToBed is not installed." ) - - -@router.get( - "/search_by_genome_coordinates/regions/{chr_num}/{start}/{end}", - response_model=DBResponse, - include_in_schema=True, -) -async def get_regions_for_bedfile( - start: Annotated[int, Path(description="start coordinate", example=1103243)], - end: Annotated[int, Path(description="end coordinate", example=2103332)], - chr_num: str = chromosome_number, -): - """ - Returns the list of BED files have regions overlapping given genome coordinates - """ - with tempfile.NamedTemporaryFile(mode="w+") as f: - f.write(f"{chr_num}\t{start}\t{end}\n") - - bed_files = await get_all_bed_metadata( - ids=["name", "record_identifier", "bedfile"] - ) - - colnames = ["name", "record_identifier", "overlapped_regions"] - values = [] - for bed in bed_files["data"]: - name = bed[0] - md5sum = bed[1] - remote = True if CFG_REMOTE_KEY in bbc.config else False - path = ( - os.path.join( - bbc.config[CFG_REMOTE_KEY]["http"]["prefix"], bed[2]["path"] - ) - if remote - else os.path.join( - bbc.config[CFG_PATH_KEY][CFG_PATH_PIPELINE_OUTPUT_KEY], - bed[2]["path"], - ) - ) - - cmd = [ - "bedIntersect", - f.name, - path, - "stdout", - ] - - _LOGGER.info(f"Command: {' '.join(map(str, cmd))} | wc -l") - - try: - ct_process = subprocess.Popen( - ["wc", "-l"], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - universal_newlines=True, - ) - - subprocess.Popen( - cmd, - stdout=ct_process.stdin, - text=True, - ) - if int(ct_process.communicate()[0].rstrip("\n")) != 0: - values.append( - [name, md5sum, int(ct_process.communicate()[0].rstrip("\n"))] - ) - - except FileNotFoundError: - _LOGGER.warning("bedIntersect is not installed.") - raise HTTPException( - status_code=500, detail="ERROR: bedIntersect is not installed." - ) - return {"columns": colnames, "data": values} - - -# should it be deleted, or disabled for public use? -@router.get("/all/metadata") -async def get_all_bed_metadata( - ids: Annotated[ - Optional[List[str]], Query(description="Bedfiles table column name") - ] = None, - limit: Annotated[ - Optional[int], Query(description="number of rows returned by the query") - ] = 10, -): - """ - Get bedfiles metadata for selected columns - """ - if ids and "record_identifier" not in ids: - ids.append("record_identifier") - try: - # TODO: remove backend dependency - res = bbc.bed.backend.select(columns=ids, limit=limit) - except AttributeError: - raise HTTPException( - status_code=404, detail=f"Table results for {ids} not found" - ) - - if res: - if ids: - colnames = ids - values = [list(x) if isinstance(x, tuple) else list(x) for x in res] - else: - colnames = list(res[0].__dict__.keys())[1:] - values = [list(x.__dict__.values())[1:] for x in res] - else: - _LOGGER.warning(f"No records matched the query") - return {"columns": [], "data": [[]]} - - _LOGGER.debug(f"Serving data for columns: {colnames}") - return {"columns": colnames, "data": values} diff --git a/bedhost/templates/page.html b/bedhost/templates/page.html new file mode 100644 index 00000000..f1e3dea7 --- /dev/null +++ b/bedhost/templates/page.html @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + BEDbase API + + + + +
+
+ {{ content }} +
+
+
+
+
+ databio + databio + databio +
+
+
+ + + diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..6abffb6e --- /dev/null +++ b/docs/README.md @@ -0,0 +1,3 @@ +# Docs + +These markdown files are hosted by the service to make some simple documentation for the API. \ No newline at end of file diff --git a/docs/about.md b/docs/about.md new file mode 100644 index 00000000..529dfcfc --- /dev/null +++ b/docs/about.md @@ -0,0 +1,6 @@ +# about + +testing about + +[here is a link](/docs) + diff --git a/docs/guide.md b/docs/guide.md new file mode 100644 index 00000000..1c8899c6 --- /dev/null +++ b/docs/guide.md @@ -0,0 +1,35 @@ +# Developer Guide + +## Introduction + +### Data types + +BEDbase stores two types of data, which we call *records*. They are 1. BEDs, and 2. BEDsets. BEDsets are simply collections of BEDs. Each record in the database is either a BED or a BEDset. + +### Endpoint organization + +The endpoints are divided into 3 groups: + +1. `/bed` endpoints are used to interact with metadata for BED records. +2. `/bedset` endpoints are used to interact with metadata for BEDset records. +3. `/objects` endpoints are used to download metadata and get URLs to retrieve the underlying data itself. These endpoints implement the [GA4GH DRS standard](https://ga4gh.github.io/data-repository-service-schemas/). + +Therefore, to get information and statistics about BED or BEDset records, or what is contained in the database, look through the `/bed` and `/bedset` endpoints. But if you need to write a tool that gets the actual underlying files, then you'll need to use the `/objects` endpoints. The type of identifiers used in each case differ. + +## Record identifiers vs. object identifiers + +Each record has an identifier. For example, `eaf9ee97241f300f1c7e76e1f945141f` is a BED identifier. You can use this identifier for the metadata endpoints. To download files, you'll need something slightly different -- you need an *object identifier*. This is because each BED record includes multiple files, such as the original BED file, the BigBed file, analysis plots, and so on. To download a file, you will construct what we call the `object_id`, which identifies the specific file. + +## How to construct object identifiers + +Object IDs take the form `..`. An example of an object_id for a BED file is `bed.eaf9ee97241f300f1c7e76e1f945141f.bedfile` + +So, you can get information about this object like this: + +`GET` [/objects/bed.eaf9ee97241f300f1c7e76e1f945141f.bedfile](/objects/bed.eaf9ee97241f300f1c7e76e1f945141f.bedfile) + +Or, you can get a URL to download the actual file with: + +`GET` [/objects/bed.eaf9ee97241f300f1c7e76e1f945141f.bedfile/access/http](/objects/bed.eaf9ee97241f300f1c7e76e1f945141f.bedfile/access/http) + + diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..1b6ebf05 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,9 @@ +# BEDbase API + +Welcome to the BEDbase API. You might be looking for: + +- [API OpenAPI documentation](/docs) +- [BEDbase API changelog](/docs/changelog) +- [Developer Guide and FAQ](/docs/guide) +- [bedbase.org user interface](https://bedbase.org) +- [Sheffield lab of computational biology](https://databio.org)