Skip to content

Commit e6a6b1c

Browse files
committed
Merge branch 'master' of github.com:PavlidisLab/gemmapy
2 parents 0489896 + aa902e6 commit e6a6b1c

11 files changed

Lines changed: 190 additions & 74 deletions

File tree

.github/workflows/python-package.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
strategy:
1111
fail-fast: false
1212
matrix:
13-
python-version: ["3.10", "3.11", "3.12"]
13+
python-version: [ "3.10", "3.11", "3.12", "3.13" ]
1414
steps:
1515
- uses: actions/checkout@v3
1616
- name: Set up Python ${{ matrix.python-version }}

README.rst

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,20 @@ public studies, referencing thousands of published papers.
1212
Installation instructions
1313
-------------------------
1414
.. This is a content of docs/install.rst. Update it whenever install.rst changes.
15-
16-
The package requires Python3.10+.
1715
18-
#. Install it from a local copy
16+
The package requires Python 3.10+.
17+
18+
#. Install it from PyPI
1919

2020
.. code-block:: bash
2121
22-
git clone git@github.com:PavlidisLab/gemmapy.git
23-
cd gemmapy
24-
pip install .
22+
pip install gemmapy
2523
26-
#. Install it from PyPI
24+
#. Install it from a local copy
2725

2826
.. code-block:: bash
2927
30-
pip install gemmapy
28+
pip install git+https://github.com/PavlidisLab/gemmapy.git
3129
3230
3331
Usage

gemmapy/gemmapy_api.py

Lines changed: 78 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,105 @@
22
"""
33
Gemma python API (https://gemma.msl.ubc.ca/rest/v2/)
44
"""
5-
6-
from gemmapy import sdk
7-
from gemmapy import _processors as ps
8-
from gemmapy import _validators as vs
9-
from gemmapy import _subprocessors as sub
5+
import enum
6+
import json
7+
import logging
8+
import os
9+
import subprocess
10+
import warnings
11+
from getpass import getpass
12+
from io import StringIO
1013
from typing import Optional, List, Callable
11-
from pandas import DataFrame
12-
import pandas as pd
13-
import numpy as np
14+
1415
import anndata as ad
16+
import numpy as np
17+
import pandas as pd
1518
from anndata import AnnData
16-
from io import StringIO
17-
import warnings
18-
import json
19+
from pandas import DataFrame
20+
21+
from gemmapy import _processors as ps
22+
from gemmapy import _subprocessors as sub
23+
from gemmapy import _validators as vs
24+
from gemmapy import sdk
25+
26+
logger = logging.getLogger(__name__)
1927

28+
class GemmaPath(enum.Enum):
29+
PROD = "prod"
30+
DEV = "dev"
31+
STAGING = "staging"
2032

2133
class GemmaPy(object):
2234
"""
2335
Main API class
2436
"""
2537

26-
def __init__(self, auth:list|tuple=None, path="prod"):
38+
def __init__(self, auth: Optional[list | tuple] = None,
39+
path: Optional[GemmaPath | str] = None):
2740
"""
2841
:param list auth: (optional) A list or tuple of credential strings, e.g.
29-
(your_username, your_password)
30-
:param bool devel: (optional) If True development version of Gemma API will be
31-
used. Default is False.
42+
(your_username, your_password). Note that you may also define your Gemma
43+
credentials using `GEMMA_USERNAME` and `GEMMA_PASSWORD` environment
44+
variables. For a more secure approach, you can also provide a
45+
`GEMMA_PASSWORD_CMD` variable that produces your password
46+
(e.g. `pass gemma` using https://www.passwordstore.org/). If only a
47+
username is supplied, a password prompt will be used.
48+
:param str path: (optional) Override the path to use for the REST API.
49+
You may use one of the enumerated values in GemmaPath or a string.
50+
Three special values are recognized: "prod", "staging" and "dev",
51+
although only "prod" is publicly accessible. The default is the value
52+
from the OpenAPI specification used to generate the SDK, which is
53+
usually equivalent to PROD.
3254
"""
3355

3456
configuration = sdk.Configuration()
35-
if path == "prod":
36-
pass
37-
# configuration.host = 'https://gemma.msl.ubc.ca/rest/v2'
38-
elif path == 'dev':
57+
if path == GemmaPath.PROD or path == 'prod':
58+
logger.debug("Using production endpoint.")
59+
configuration.host = 'https://gemma.msl.ubc.ca/rest/v2'
60+
elif path == GemmaPath.DEV or path == 'dev':
3961
configuration.host = 'https://dev.gemma.msl.ubc.ca/rest/v2'
40-
elif path == 'staging':
62+
elif path == GemmaPath.STAGING or path == 'staging':
4163
configuration.host = "https://staging-gemma.msl.ubc.ca/rest/v2"
42-
else:
64+
elif path is not None:
4365
configuration.host = path
44-
66+
else:
67+
# use the default configuration in the openapi.json file
68+
pass
4569

4670
if auth is not None:
71+
if len(auth) != 1 and len(auth) != 2:
72+
raise ValueError(
73+
'There must be exactly one or two values in the auth parameter.')
4774
configuration.username = auth[0]
48-
configuration.password = auth[1]
75+
if len(auth) == 2:
76+
configuration.password = auth[1]
77+
else:
78+
configuration.password = getpass(
79+
f'Supply your password for {configuration.username}@{configuration.host}: ')
80+
elif os.environ.get('GEMMA_USERNAME'):
81+
logger.debug(
82+
'Reading username for %s from $GEMMA_USERNAME.',
83+
configuration.host)
84+
configuration.username = os.getenv('GEMMA_USERNAME')
85+
if os.getenv('GEMMA_PASSWORD'):
86+
logger.debug("Reading password for %s@%s from $GEMMA_PASSWORD.",
87+
configuration.username, configuration.host)
88+
configuration.password = os.getenv('GEMMA_PASSWORD')
89+
elif os.getenv('GEMMA_PASSWORD_CMD'):
90+
logger.debug(
91+
"Reading password for %s@%s from $GEMMA_PASSWORD_CMD (%s).",
92+
configuration.username, configuration.host,
93+
os.getenv('GEMMA_PASSWORD_CMD'))
94+
password = subprocess.run(os.getenv('GEMMA_PASSWORD_CMD'),
95+
shell=True, check=True,
96+
stdout=subprocess.PIPE,
97+
text=True).stdout
98+
configuration.password = password.splitlines()[0]
99+
else:
100+
logger.debug(
101+
'Could not read GEMMA_PASSWORD nor GEMMA_PASSWORD_CMD from environment, the password will be prompted.')
102+
configuration.password = getpass(
103+
f'Supply your password for {configuration.username}@{configuration.host}: ')
49104

50105
# create an instance of the API class
51106
self.raw = sdk.DefaultApi(sdk.ApiClient(configuration))

gemmapy/sdk/api_client.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,24 @@ def deserialize(self, response, response_type):
226226
if response_type == "file":
227227
return self.__deserialize_file(response)
228228

229+
content_type = response.urllib3_response.headers['Content-Type']
230+
231+
if content_type.startswith('text/'):
232+
return self.__deserialize_text(response)
233+
234+
if content_type.startswith('application/json'):
235+
return self.__deserialize_json(response, response_type)
236+
237+
# treat response as bytes
238+
return response.data
239+
240+
def __deserialize_text(self, response):
241+
"""Deserialize a text response."""
242+
# TODO: extract the charset parameter instead of assuming UTF-8
243+
return str(response.data, encoding='utf-8')
244+
245+
def __deserialize_json(self, response, response_type):
246+
"""Deserialize a JSON response."""
229247
# fetch data from response object
230248
try:
231249
data = json.loads(response.data)
@@ -545,11 +563,6 @@ def __deserialize_primitive(self, data, klass):
545563
546564
:return: int, long, float, str, bool.
547565
"""
548-
549-
# dv special case
550-
if klass == str and type(data) == bytes:
551-
return str(data, encoding = 'utf-8')
552-
553566
try:
554567
return klass(data)
555568
except UnicodeEncodeError:

sdk-gen/README.rst

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,9 @@ Code generation sources
1111

1212
**To produce the code in gemmapy/sdk/:**
1313

14-
(1) Get `Swagger Codegen 3.0.35 <https://github.com/swagger-api/swagger-codegen/tree/3.0.35>`_ jar, e.g:
14+
Run the generate-sdk.sh script which will create a temporary directory and synchronize the newly generated code with the
15+
content of `../gemmapy/sdk`.
1516

16-
.. code-block:: bash
17+
.. code-block:: bash
1718
18-
wget https://repo1.maven.org/maven2/io/swagger/codegen/v3/swagger-codegen-cli/3.0.35/swagger-codegen-cli-3.0.35.jar -O swagger-codegen-cli.jar
19-
20-
(2) Run the generate-sdk.sh script which will create a temporary directory and
21-
synchronize the newly generated code with the content of `../gemmapy/sdk`.
22-
23-
.. code-block:: bash
24-
25-
./generate-sdk.sh
26-
27-
The current `gemmapy/sdk` was generated with Swagger Codegen 3.0.35 and
28-
openapi.yaml (of 2022-10-14, REST API v.2.5.1), templ/ presented in here.
19+
./generate-sdk.sh

sdk-gen/generate-sdk.sh

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
#!/bin/bash
22
set -e
3-
swagger_version=3.0.55
3+
swagger_version=3.0.73
44
temp_sdk_dir=$(mktemp -d)
55
script_dir=$(dirname "${BASH_SOURCE[0]}")
66
gemmapy_version=$(grep 'version' "$script_dir/../setup.cfg" | sed 's/version = //')
77
echo "Updating openapi.yaml..."
88
curl https://gemma.msl.ubc.ca/rest/v2/openapi.yaml -o "$script_dir/openapi.yaml" --compressed
9-
echo "Updating swagger-codegen-cli.jar..."
10-
curl "https://repo1.maven.org/maven2/io/swagger/codegen/v3/swagger-codegen-cli/$swagger_version/swagger-codegen-cli-$swagger_version.jar" -o swagger-codegen-cli.jar
9+
echo "Downloading swagger-codegen-cli.jar..."
10+
curl "https://repo1.maven.org/maven2/io/swagger/codegen/v3/swagger-codegen-cli/$swagger_version/swagger-codegen-cli-$swagger_version.jar" -o "$temp_sdk_dir/swagger-codegen-cli.jar"
1111

1212
echo "SDK will be generated in $temp_sdk_dir..."
13-
java -jar swagger-codegen-cli.jar generate -i "$script_dir/openapi.yaml" -l python -t "$script_dir/templ" -DpackageName=gemmapy.sdk -o "$temp_sdk_dir" --http-user-agent "gemmapy/$gemmapy_version"
13+
java -jar "$temp_sdk_dir/swagger-codegen-cli.jar" generate -i "$script_dir/openapi.yaml" -l python -t "$script_dir/templ" -DpackageName=gemmapy.sdk -o "$temp_sdk_dir" --http-user-agent "gemmapy/$gemmapy_version"
1414
rsync -av --delete "$temp_sdk_dir/gemmapy/sdk/" "$script_dir/../gemmapy/sdk/"
15-
15+
rm -rf "$temp_sdk_dir"

sdk-gen/openapi.yaml

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,6 @@ paths:
102102
text/tab-separated-values; charset=UTF-8; q=0.9:
103103
schema:
104104
type: string
105-
format: binary
106105
example: "# If you use this file for your research, please cite:\r\n\
107106
# Lim et al. (2021) Curation of over 10 000 transcriptomic studies\
108107
\ to enable data reuse.\r\n# Database, baab006 (doi:10.1093/database/baab006).\r\
@@ -823,7 +822,6 @@ paths:
823822
text/tab-separated-values; charset=UTF-8:
824823
schema:
825824
type: string
826-
format: binary
827825
example: "# Expression design file generated by Gemma on 12/14/2023\n\
828826
# shortName=GSE2018\n# name=Human Lung Transplant - BAL\n# Experiment\
829827
\ details: https://gemma.msl.ubc.ca/expressionExperiment/showExpressionExperiment.html?id=1\n\
@@ -956,7 +954,6 @@ paths:
956954
text/tab-separated-values; charset=UTF-8:
957955
schema:
958956
type: string
959-
format: binary
960957
example: "# Expression data file generated by Gemma on 10/29/2024\n\
961958
# shortName=GSE2018\n# name=Human Lung Transplant - BAL\n# Experiment\
962959
\ details: https://gemma.msl.ubc.ca/expressionExperiment/showExpressionExperiment.html?id=1\n\
@@ -1120,7 +1117,6 @@ paths:
11201117
text/tab-separated-values; charset=UTF-8:
11211118
schema:
11221119
type: string
1123-
format: binary
11241120
example: "# Expression data file generated by Gemma on 10/29/2024\n\
11251121
# shortName=GSE2018\n# name=Human Lung Transplant - BAL\n# Experiment\
11261122
\ details: https://gemma.msl.ubc.ca/expressionExperiment/showExpressionExperiment.html?id=1\n\
@@ -1285,7 +1281,6 @@ paths:
12851281
text/tab-separated-values; charset=UTF-8:
12861282
schema:
12871283
type: string
1288-
format: binary
12891284
example: "# Expression data file generated by Gemma on 08/12/2024\n\
12901285
# shortName=GSE2018\n# name=Human Lung Transplant - BAL\n# Experiment\
12911286
\ details: https://gemma.msl.ubc.ca/expressionExperiment/showExpressionExperiment.html?id=1\n\
@@ -1631,7 +1626,6 @@ paths:
16311626
text/tab-separated-values; charset=UTF-8; q=0.9:
16321627
schema:
16331628
type: string
1634-
format: binary
16351629
example: |
16361630
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
16371631
<html><head>
@@ -2171,7 +2165,6 @@ paths:
21712165
text/tab-separated-values; charset=UTF-8; q=0.9:
21722166
schema:
21732167
type: string
2174-
format: binary
21752168
/datasets/analyses/differential/results/taxa/{taxon}/genes/{gene}:
21762169
get:
21772170
summary: Retrieve the differential expression results for a given gene and taxa
@@ -2264,7 +2257,6 @@ paths:
22642257
text/tab-separated-values; charset=UTF-8:
22652258
schema:
22662259
type: string
2267-
format: binary
22682260
/datasets/expressions/genes/{gene}:
22692261
get:
22702262
summary: Retrieve the expression levels of a gene among datasets matching the
@@ -3094,7 +3086,6 @@ paths:
30943086
text/tab-separated-values; charset=UTF-8:
30953087
schema:
30963088
type: string
3097-
format: binary
30983089
example: "# Annotation file generated by Gemma\n# Generated 10/27/2023\n\
30993090
# If you use this file for your research, please cite: \n# Lim et\
31003091
\ al. (2021) Curation of over 10 000 transcriptomic studies to enable\

sdk-gen/templ/api_client.mustache

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,24 @@ class ApiClient(object):
233233
if response_type == "file":
234234
return self.__deserialize_file(response)
235235

236+
content_type = response.urllib3_response.headers['Content-Type']
237+
238+
if content_type.startswith('text/'):
239+
return self.__deserialize_text(response)
240+
241+
if content_type.startswith('application/json'):
242+
return self.__deserialize_json(response, response_type)
243+
244+
# treat response as bytes
245+
return response.data
246+
247+
def __deserialize_text(self, response):
248+
"""Deserialize a text response."""
249+
# TODO: extract the charset parameter instead of assuming UTF-8
250+
return str(response.data, encoding='utf-8')
251+
252+
def __deserialize_json(self, response, response_type):
253+
"""Deserialize a JSON response."""
236254
# fetch data from response object
237255
try:
238256
data = json.loads(response.data)
@@ -552,11 +570,6 @@ class ApiClient(object):
552570

553571
:return: int, long, float, str, bool.
554572
"""
555-
556-
# dv special case
557-
if klass == str and type(data) == bytes:
558-
return str(data, encoding = 'utf-8')
559-
560573
try:
561574
return klass(data)
562575
except UnicodeEncodeError:

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ keywords = gemma, bioinformatics
77

88
[options]
99
packages = find:
10-
python_requires = >3.10
10+
python_requires = >=3.10
1111
install_requires =
1212
certifi >= 14.05.14
1313
six >= 1.10

0 commit comments

Comments
 (0)