diff --git a/Dockerfile b/Dockerfile index e768cf9..9b57d85 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,8 @@ -FROM python:3.6.8 -MAINTAINER albert.merono@vu.nl +#FROM python:3.6.8 +FROM python:3.9.13 +LABEL org.opencontainers.image.authors="ORIGINAL: albert.merono@vu.nl; THIS VERSION: mark.wilkinson@upm.es" +LABEL org.opencontainers.image.documentation="https://github.com/markwilkinson/grlc/blob/master/README.md" +RUN apt-get update && apt-get full-upgrade -y # Default values for env variables ARG GRLC_GITHUB_ACCESS_TOKEN= @@ -22,13 +25,17 @@ ENV GRLC_INSTALL_DIR="${GRLC_HOME}/grlc" \ GRLC_RUNTIME_DIR="${GRLC_CACHE_DIR}/runtime" RUN apt-get update \ - && DEBIAN_FRONTEND=noninteractive apt-get install -y nginx git-core logrotate python-pip locales gettext-base sudo build-essential apt-utils \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y nginx git-core logrotate python3-pip locales gettext-base sudo build-essential apt-utils \ && update-locale LANG=C.UTF-8 LC_MESSAGES=POSIX \ && locale-gen en_US.UTF-8 \ && DEBIAN_FRONTEND=noninteractive dpkg-reconfigure locales \ && rm -rf /var/lib/apt/lists/* -RUN curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash - +RUN apt-get update && apt-get dist-upgrade -y + + +RUN curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash - +RUN chmod a+r /usr/share/keyrings/nodesource.gpg RUN apt-get update && apt-get install -y nodejs COPY ./ ${GRLC_INSTALL_DIR} @@ -48,3 +55,4 @@ VOLUME ["${GRLC_DATA_DIR}", "${GRLC_LOG_DIR}"] WORKDIR ${GRLC_INSTALL_DIR} ENTRYPOINT ["/sbin/entrypoint.sh"] CMD ["app:start"] + diff --git a/README.md b/README.md index aa7b2e7..76e3f70 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,13 @@ +# Shallot +## Kinda like grlc, but not as powerful ;-) +

-[![PyPI version](https://badge.fury.io/py/grlc.svg)](https://badge.fury.io/py/grlc) -[![DOI](https://zenodo.org/badge/46131212.svg)](https://zenodo.org/badge/latestdoi/46131212) -[![Build Status](https://travis-ci.org/CLARIAH/grlc.svg?branch=master)](https://travis-ci.org/CLARIAH/grlc) +# NOTE: This is a highly crippled version of the original grlc server + +It is intended to be used in secure environments. Specifically, the GitHub and YAML file integration has been disabled. Only local queries will be available. +## Original Documentation from https://raw.githubusercontent.com/CLARIAH/grlc/ is below grlc, the git repository linked data API constructor, automatically builds Web APIs using shared SPARQL queries. http://grlc.io/ diff --git a/docker-assets/assets/build/install.sh b/docker-assets/assets/build/install.sh index e0ef183..954b463 100644 --- a/docker-assets/assets/build/install.sh +++ b/docker-assets/assets/build/install.sh @@ -18,11 +18,14 @@ passwd -d ${GRLC_USER} cd ${GRLC_INSTALL_DIR} chown ${GRLC_USER}:${GRLC_USER} ${GRLC_HOME} -R - pip install --upgrade pip +pip install 'setuptools<58' +pip install 'docutils' pip install . -npm install git2prov +#npm install git2prov +#npm audit fix + #move nginx logs to ${GITLAB_LOG_DIR}/nginx sed -i \ @@ -31,7 +34,7 @@ sed -i \ /etc/nginx/nginx.conf # configure gitlab log rotation - cat > /etc/logrotate.d/grlc << EOF + cat > /etc/logrotate.d/grlc << EOF1 ${GRLC_LOG_DIR}/grlc/*.log { weekly missingok @@ -41,10 +44,10 @@ sed -i \ notifempty copytruncate } - EOF +EOF1 # configure gitlab vhost log rotation - cat > /etc/logrotate.d/grlc-nginx << EOF + cat > /etc/logrotate.d/grlc-nginx << EOF2 ${GRLC_LOG_DIR}/nginx/*.log { weekly missingok @@ -54,4 +57,4 @@ sed -i \ notifempty copytruncate } - EOF +EOF2 diff --git a/openapi.json b/openapi.json new file mode 100644 index 0000000..7650e77 --- /dev/null +++ b/openapi.json @@ -0,0 +1,229 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Duchenne Parent Project Shallot", + "description": "The FAIR Data Point Shallot server for the Duchenne Parent Project", + "contact": { + "name": "Mark Wilkinson", + "url": "https://fairdata.systems" + }, + "version": "local" + }, + "servers": [ + { + "url": "//fairdata.services/api-local/" + } + ], + "paths": { + "/count": { + "get": { + "tags": [ + "Patient Count" + ], + "summary": "Returns the number of patients in the registry with the corresponding disease code", + "description": "\n\n```\n#+ summary: Returns the number of patients in the registry with the corresponding disease code\n#+ tags:\n#+ - Patient Count\n#+ defaults:\n#+ - type: http://www.orpha.net/ORDO/Orphanet_98896\n#+ endpoint_in_url: False\n\nPREFIX sio: \nselect (count(?p) as ?count) where { \n ?p sio:SIO_000228 ?role . # person has role role\n ?role sio:SIO_000356 ?process . # is realized in process\n ?process sio:SIO_000229 ?output . #has output output\n ?output sio:SIO_000628 ?attribute . # output refers to attribute\n\t?attribute a ?_type_iri . # attribute is a orphacode\n}\n\n```", + "parameters": [ + { + "name": "type", + "in": "query", + "description": "A value of type string (iri) that will substitute ?_type_iri in the original query", + "required": true, + "schema": { + "type": "string", + "format": "iri", + "default": "http://www.orpha.net/ORDO/Orphanet_98896" + } + } + ], + "responses": { + "200": { + "description": "Query response", + "content": { + "text/csv": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": {} + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": {} + } + } + }, + "text/html": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": {} + } + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "text/csv": { + "schema": { + "$ref": "#/components/schemas/Message" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } + }, + "text/html": { + "schema": { + "$ref": "#/components/schemas/Message" + } + } + } + } + } + } + }, + "/kpi-ttd": { + "get": { + "tags": [ + "KPI diagnosis-delay" + ], + "summary": "Returns the Key Performance Indicator of the delay between symptom onset and diagnosis. This data is aggregated by disease, and by year of diagnosis, and is measured in days.", + "description": "\n\n```\n#+ summary: Returns the Key Performance Indicator of the delay between symptom onset and diagnosis. This data is aggregated by disease, and by year of diagnosis, and is measured in days.\n#+ tags:\n#+ - KPI diagnosis-delay\n#+ defaults:\n#+ \n#+ endpoint_in_url: False\n\n################################################################\n# list diagnosis and time from onset to diagnosis\n################################################################\n\nPREFIX sio: \nPREFIX rdfs: \nPREFIX rdf: \nPREFIX xsd: \nPREFIX ofn: \n\nSELECT DISTINCT ?ORDO ?yearOfDiagnosis (xsd:integer(ROUND(AVG(?timeOnsetToDiagnosis))) as ?avgoffset)\nWHERE {\n BIND(xsd:integer(ofn:asDays(?onsetdate - ?diagnosisdate)) AS ?timeOnsetToDiagnosis)\n# BIND(xsd:integer(ofn:asDays(?diagnosisdate - ?onsetdate)) AS ?timeOnsetToDiagnosis)\n BIND(SUBSTR(str(?diagnosisdate), 1,4) AS ?yearOfDiagnosis)\n {\n SELECT ?ORDO ?diagnosisdate WHERE {\n GRAPH ?g {\n ?person sio:SIO_000228 ?role1 . # person has role role\n ?role1 sio:SIO_000356 ?process1 . # is realized in process\n ?process1 a . # diagnostic process\n ?process1 sio:SIO_000229 ?output1 . #has output output \n ?output1 a . # diagnosis code\n ?output1 sio:SIO_000628 ?diagnosis1 . # output refers to attribute\n ?diagnosis1 a ?ORDO .\n FILTER(!(?ORDO = sio:SIO_000614)) . # not an \"attribute\" diagnosis\n\t \t}\n \t\t?g sio:SIO_000680 ?startdate .\n \t\t?startdate sio:SIO_000300 ?diagnosisdate .\n }\n }\n {\n SELECT ?onsetdate WHERE {\n ?person sio:SIO_000228 ?role2 . # person has role role\n ?role2 sio:SIO_000356 ?process2 . # is realized in process\n ?process2 sio:SIO_000229 ?output2 . #has output output\n ?output2 sio:SIO_000300 ?onsetdate .\n ?output2 sio:SIO_000628 ?attribute2 . # output refers to attribute\n ?attribute2 a . \n }\n }\n} group by ?ORDO ?yearOfDiagnosis order by ?yearOfDiagnosis ?ORDO\n\n\n```", + "responses": { + "200": { + "description": "Query response", + "content": { + "text/csv": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": {} + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": {} + } + } + }, + "text/html": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": {} + } + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "text/csv": { + "schema": { + "$ref": "#/components/schemas/Message" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } + }, + "text/html": { + "schema": { + "$ref": "#/components/schemas/Message" + } + } + } + } + } + } + }, + "/phenotype-frequencies": { + "get": { + "tags": [ + "Phenotype frequency" + ], + "summary": "Returns the number of patients in the registry that have had a phenotype code at any time", + "description": "\n\n```\n#+ summary: Returns the number of patients in the registry that have had a phenotype code at any time\n#+ tags:\n#+ - Phenotype frequency\n#+ defaults:\n#+ \n#+ endpoint_in_url: False\n\nPREFIX sio: \nselect ?type (count(?type) as ?frequency) where {\n select distinct ?p ?type where {\n ?p sio:SIO_000228 ?role . # person has role role\n ?role sio:SIO_000356 ?process . # is realized in process\n ?process sio:SIO_000229 ?output . #has output output\n ?output sio:SIO_000628 ?attribute . # output refers to attribute\n ?attribute a ?type .\n FILTER(!(?type = sio:SIO_000614)) . # not an \"attribute\" type\n }\n} group by ?type\n\n```", + "responses": { + "200": { + "description": "Query response", + "content": { + "text/csv": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": {} + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": {} + } + } + }, + "text/html": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": {} + } + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "text/csv": { + "schema": { + "$ref": "#/components/schemas/Message" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } + }, + "text/html": { + "schema": { + "$ref": "#/components/schemas/Message" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Message": { + "type": "string" + } + } + }, + "x-original-swagger-version": "2.0" +} \ No newline at end of file diff --git a/openapi.yaml b/openapi.yaml new file mode 100644 index 0000000..ed0debe --- /dev/null +++ b/openapi.yaml @@ -0,0 +1,106 @@ +openapi: "3.0.1" +info: + title: SPARQL OpenAPI + version: "10" + description: | + This is a heavily trimmed copy of the RDF4J REST API that includes only the SPARQL endpoint GET and POST definitions + + From this endpoint we serve the BGV Administrative Collection information (collecting group, date, etc.) + +externalDocs: + url: https://rdf4j.org/documentation/reference/rest-api/ + +servers: + - url: https://bgv.cbgp.upm.es + description: SPARQL server for the BANCO DE GERMOPLASMA VEGETAL-UPM + +tags: + - name: SPARQL + description: SPARQL Query execution + +components: + requestBodies: + RdfData: + description: RDF data payload + content: + application/rdf+xml: + schema: + type: object + xml: + name: RDF + namespace: http://www.w3.org/1999/02/22-rdf-syntax-ns# + text/plain: + schema: + type: string + text/turtle: + schema: + type: string + text/rdf+n3: + schema: + type: string + text/x-nquads: + schema: + type: string + application/ld+json: + schema: + type: object + format: json + application/rdf+json: + schema: + type: object + format: json + application/trix: + schema: + type: object + xml: + name: TriX + application/x-trig: + schema: + type: string + application/x-binary-rdf: + schema: + type: string + format: binary + responses: + 200SparqlResult: + description: SPARQL query result + content: + application/sparql-results+json: + examples: + SelectQueryResult: + $ref: "#/components/examples/SparqlJsonBindings" + examples: + SparqlJsonBindings: + value: + head: + vars: [ "s", "p", "o" ] + results: + bindings: + - s: + type: "uri" + value: "http://example.org/s1" + - p: + type: "uri" + value: "http://example.org/p1" + - o: + type: "literal" + value: "foo" +paths: + /repositories/administrative: + get: + tags: + - SPARQL + summary: Execute SPARQL query + description: | + Execute a SPARQL query on the repository. The result format is based on the type of result (boolean, variable bindings, or RDF data) and the negotiated acceptable content-type. Note that RDF4J supports executing SPARQL queries with either a GET or a POST request. POST is supported for queries that are too large to be encoded as a query parameter. + parameters: + - name: query + in: query + description: The query to evaluate + required: true + schema: + type: string + example: SELECT DISTINCT ?type WHERE {?s a ?type} + responses: + '200': + $ref: "#/components/responses/200SparqlResult" \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index d12ef86..b860284 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,10 @@ docopt==0.6.2 -docutils==0.17.1 Flask==1.0.2 -Flask-Cors==3.0.6 -gevent==1.4.0 -greenlet==0.4.15 +Flask-Cors==3.0.9 +urllib3==1.26.5 +itsdangerous==2.0.1 +gevent==21.12.0 +greenlet==1.1.0 html5lib==1.0.1 isodate==0.5.4 keepalive==0.5 @@ -13,10 +14,9 @@ pyparsing==2.0.7 PyYAML==5.4 rdflib==5.0.0 rdflib-jsonld==0.4.0 -requests==2.20.0 +requests six==1.12.0 simplejson==3.16.0 -setuptools>=38.6.0 SPARQLTransformer==2.1.1 SPARQLWrapper==1.8.2 werkzeug>=0.16.0 diff --git a/setup.py b/setup.py index 21f524a..f9190b8 100644 --- a/setup.py +++ b/setup.py @@ -55,5 +55,5 @@ package_data = { 'grlc': grlc_data }, include_package_data=True, data_files=[('citation/grlc', ['CITATION.cff'])], - python_requires='>=3.7, <=3.8', + python_requires='>=3.9, <=3.10', ) diff --git a/src/fileLoaders.py b/src/fileLoaders.py index efde936..0602067 100644 --- a/src/fileLoaders.py +++ b/src/fileLoaders.py @@ -71,13 +71,15 @@ def __init__(self, user, repo, subdir=None, sha=None, prov=None): self.subdir = (subdir + "/") if subdir else "" self.sha = sha if sha else NotSet self.prov = prov - gh = Github(static.ACCESS_TOKEN) + #gh = Github(static.ACCESS_TOKEN) try: - self.gh_repo = gh.get_repo(user + '/' + repo, lazy=False) + #self.gh_repo = gh.get_repo(user + '/' + repo, lazy=False) + raise Exception("GitHub Access is disabled for this grlc server") except BadCredentialsException: raise Exception('BadCredentials: have you set up github_access_token on config.ini ?') except Exception: - raise Exception('Repo not found: ' + user + '/' + repo) + raise Exception('GitHub Access has been disabled for this server' ) +# raise Exception('Repo not found: ' + user + '/' + repo) def fetchFiles(self): """Returns a list of file items contained on the github repo.""" @@ -262,27 +264,32 @@ class URLLoader(BaseLoader): specification from a specification YAML file located on a remote server.""" def __init__(self, spec_url): - """Create a new URLLoader. - - Keyword arguments: - spec_url -- URL where the specification YAML file is located.""" - headers = {'Accept' : 'text/yaml'} - resp = requests.get(spec_url, headers=headers) - if resp.status_code == 200: - self.spec = yaml.load(resp.text) - self.spec['url'] = spec_url - self.spec['files'] = {} - for queryUrl in self.spec['queries']: - queryNameExt = path.basename(queryUrl) - queryName = path.splitext(queryNameExt)[0] # Remove extention - item = { - 'name': queryName, - 'download_url': queryUrl - } - self.spec['files'][queryNameExt] = item - del self.spec['queries'] - else: - raise Exception(resp.text) + try: + raise Exception("YAML file access is disabled for this grlc server") + except Exception: + raise Exception("YAML file access is disabled for this grlc server") + + # """Create a new URLLoader. + + # Keyword arguments: + # spec_url -- URL where the specification YAML file is located.""" + # headers = {'Accept' : 'text/yaml'} + # resp = requests.get(spec_url, headers=headers) + # if resp.status_code == 200: + # self.spec = yaml.load(resp.text) + # self.spec['url'] = spec_url + # self.spec['files'] = {} + # for queryUrl in self.spec['queries']: + # queryNameExt = path.basename(queryUrl) + # queryName = path.splitext(queryNameExt)[0] # Remove extention + # item = { + # 'name': queryName, + # 'download_url': queryUrl + # } + # self.spec['files'][queryNameExt] = item + # del self.spec['queries'] + # else: + # raise Exception(resp.text) def fetchFiles(self): """Returns a list of file items contained on specification.""" diff --git a/swagger.json b/swagger.json new file mode 100644 index 0000000..0e742c0 --- /dev/null +++ b/swagger.json @@ -0,0 +1,133 @@ +{ + "basePath": "/api-local/", + "definitions": { + "Message": { + "type": "string" + } + }, + "host": "fairdata.services", + "info": { + "contact": { + "name": "Mark Wilkinson", + "url": "https://fairdata.systems" + }, + "description": "The FAIR Data Point Shallot server for the Duchenne Parent Project", + "title": "Duchenne Parent Project Shallot", + "version": "local" + }, + "next_commit": null, + "paths": { + "/count": { + "get": { + "description": "\n\n```\n#+ summary: Returns the number of patients in the registry with the corresponding disease code\n#+ tags:\n#+ - Patient Count\n#+ defaults:\n#+ - type: http://www.orpha.net/ORDO/Orphanet_98896\n#+ endpoint_in_url: False\n\nPREFIX sio: \nselect (count(?p) as ?count) where { \n ?p sio:SIO_000228 ?role . # person has role role\n ?role sio:SIO_000356 ?process . # is realized in process\n ?process sio:SIO_000229 ?output . #has output output\n ?output sio:SIO_000628 ?attribute . # output refers to attribute\n\t?attribute a ?_type_iri . # attribute is a orphacode\n}\n\n```", + "parameters": [ + { + "default": "http://www.orpha.net/ORDO/Orphanet_98896", + "description": "A value of type string (iri) that will substitute ?_type_iri in the original query", + "format": "iri", + "in": "query", + "name": "type", + "required": true, + "type": "string" + } + ], + "produces": [ + "text/csv", + "application/json", + "text/html" + ], + "responses": { + "200": { + "description": "Query response", + "schema": { + "items": { + "properties": null, + "type": "object" + }, + "type": "array" + } + }, + "default": { + "description": "Unexpected error", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Returns the number of patients in the registry with the corresponding disease code", + "tags": [ + "Patient Count" + ] + } + }, + "/kpi-ttd": { + "get": { + "description": "\n\n```\n#+ summary: Returns the Key Performance Indicator of the delay between symptom onset and diagnosis. This data is aggregated by disease, and by year of diagnosis, and is measured in days.\n#+ tags:\n#+ - KPI diagnosis-delay\n#+ defaults:\n#+ \n#+ endpoint_in_url: False\n\n################################################################\n# list diagnosis and time from onset to diagnosis\n################################################################\n\nPREFIX sio: \nPREFIX rdfs: \nPREFIX rdf: \nPREFIX xsd: \nPREFIX ofn: \n\nSELECT DISTINCT ?ORDO ?yearOfDiagnosis (xsd:integer(ROUND(AVG(?timeOnsetToDiagnosis))) as ?avgoffset)\nWHERE {\n BIND(xsd:integer(ofn:asDays(?onsetdate - ?diagnosisdate)) AS ?timeOnsetToDiagnosis)\n# BIND(xsd:integer(ofn:asDays(?diagnosisdate - ?onsetdate)) AS ?timeOnsetToDiagnosis)\n BIND(SUBSTR(str(?diagnosisdate), 1,4) AS ?yearOfDiagnosis)\n {\n SELECT ?ORDO ?diagnosisdate WHERE {\n GRAPH ?g {\n ?person sio:SIO_000228 ?role1 . # person has role role\n ?role1 sio:SIO_000356 ?process1 . # is realized in process\n ?process1 a . # diagnostic process\n ?process1 sio:SIO_000229 ?output1 . #has output output \n ?output1 a . # diagnosis code\n ?output1 sio:SIO_000628 ?diagnosis1 . # output refers to attribute\n ?diagnosis1 a ?ORDO .\n FILTER(!(?ORDO = sio:SIO_000614)) . # not an \"attribute\" diagnosis\n\t \t}\n \t\t?g sio:SIO_000680 ?startdate .\n \t\t?startdate sio:SIO_000300 ?diagnosisdate .\n }\n }\n {\n SELECT ?onsetdate WHERE {\n ?person sio:SIO_000228 ?role2 . # person has role role\n ?role2 sio:SIO_000356 ?process2 . # is realized in process\n ?process2 sio:SIO_000229 ?output2 . #has output output\n ?output2 sio:SIO_000300 ?onsetdate .\n ?output2 sio:SIO_000628 ?attribute2 . # output refers to attribute\n ?attribute2 a . \n }\n }\n} group by ?ORDO ?yearOfDiagnosis order by ?yearOfDiagnosis ?ORDO\n\n\n```", + "parameters": [], + "produces": [ + "text/csv", + "application/json", + "text/html" + ], + "responses": { + "200": { + "description": "Query response", + "schema": { + "items": { + "properties": null, + "type": "object" + }, + "type": "array" + } + }, + "default": { + "description": "Unexpected error", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Returns the Key Performance Indicator of the delay between symptom onset and diagnosis. This data is aggregated by disease, and by year of diagnosis, and is measured in days.", + "tags": [ + "KPI diagnosis-delay" + ] + } + }, + "/phenotype-frequencies": { + "get": { + "description": "\n\n```\n#+ summary: Returns the number of patients in the registry that have had a phenotype code at any time\n#+ tags:\n#+ - Phenotype frequency\n#+ defaults:\n#+ \n#+ endpoint_in_url: False\n\nPREFIX sio: \nselect ?type (count(?type) as ?frequency) where {\n select distinct ?p ?type where {\n ?p sio:SIO_000228 ?role . # person has role role\n ?role sio:SIO_000356 ?process . # is realized in process\n ?process sio:SIO_000229 ?output . #has output output\n ?output sio:SIO_000628 ?attribute . # output refers to attribute\n ?attribute a ?type .\n FILTER(!(?type = sio:SIO_000614)) . # not an \"attribute\" type\n }\n} group by ?type\n\n```", + "parameters": [], + "produces": [ + "text/csv", + "application/json", + "text/html" + ], + "responses": { + "200": { + "description": "Query response", + "schema": { + "items": { + "properties": null, + "type": "object" + }, + "type": "array" + } + }, + "default": { + "description": "Unexpected error", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Returns the number of patients in the registry that have had a phenotype code at any time", + "tags": [ + "Phenotype frequency" + ] + } + } + }, + "prev_commit": null, + "schemes": [], + "swagger": "2.0" +} \ No newline at end of file