Compare commits

..
Author SHA1 Message Date
kaloster ea13d3e253 debug 2024-09-09 16:56:25 -04:00
kaloster b43bce4321 debug 2024-09-09 16:53:52 -04:00
kaloster 8bde7c1c1f add setuptools 2024-09-09 16:51:10 -04:00
kaloster b11b161dd3 add setuptools 2024-09-09 16:45:58 -04:00
kaloster eba4c077fc add setuptools 2024-09-09 16:41:08 -04:00
kaloster 3e516bfb2f add setuptools 2024-09-09 16:26:47 -04:00
kaloster 402b9b3f94 chore: python 3.12 2024-09-09 16:16:03 -04:00
Ronen eb743efd9a fix: webpack upgrade (#2691) 2024-09-09 13:34:44 -04:00
Ronen 67d152e108 fix: mlflow critical upgrade (#2690) 2024-09-09 12:29:09 -04:00
Timmy Huang c425d2e0b0 fix: underscore snakecase notation to hyphenated snakecase for diffexp-may-be-slow (#2687) 2024-09-05 10:09:13 -07:00
Timmy Huangandkaloster 7bf5add6ef chore: Fix compatibility tests (#2685)
* chore: Fix compatibility tests

* DEBUGGGG

* fix: update deps, fix unit tests

* fix: FE deps

* chore: update compatibility matrix

---------

Co-authored-by: kaloster <rkalo@contractor.chanzuckerberg.com>
2024-09-05 09:26:40 -07:00
dependabot[bot]andTimmy Huang 4281a8f816 chore(deps-dev): bump follow-redirects from 1.15.1 to 1.15.6 in /client (#2661)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.1 to 1.15.6.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.1...v1.15.6)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Timmy Huang <tihuan@users.noreply.github.com>
2024-04-18 14:54:49 +00:00
Emanuele Bezzi 53e9edfec1 docs: change link to the CZI science community Slack (#2662) 2024-03-19 10:19:28 -07:00
atarashansky 74fbec35fe chore: Release version 1.2.0 (#2652)
* Bump version: 1.1.2 → 1.2.0-rc.0

* Bump version: 1.2.0-rc.0 → 1.2.0
2023-12-19 14:13:08 -08:00
atarashansky 5d4c782f3a chore: lower pinned requirements for backend server (#2651)
* chore: lower pinned requirements for backend server

* update one requirement
2023-12-19 13:39:47 -08:00
atarashansky 6505f6cbf5 chore: upgrade backend dependencies (#2641)
chore: upgrade backend dependencies (#2641)
2023-11-29 14:16:39 -08:00
Severiano BadajozandSeve Badajoz 4bb9a2b834 chore: update dependencies (#2636)
* chore: update dependencies

* fix babel plugins and lint errors

* switch out obselete browser plugin

* add babel config for jest

* revert some babel/jest package bumps

* npm install

* tmp remove werkzeug pin

---------

Co-authored-by: Seve Badajoz <severiano.badajoz@chanzuckerberg.com>
2023-10-20 21:08:09 -04:00
maniarathi 2d7aab3a9a fix: override default so that issues are not marked as stale (#2627) 2023-08-23 21:22:17 -04:00
maniarathi f749733d54 feat: Add a Github Action that automatically closes stale PRs. (#2626) 2023-08-22 22:08:22 -04:00
atarashanskyandatarashansky ffcf6eb5d8 chore: Release version 1.1.2 (#2611)
* Bump version: 1.1.1 → 1.1.2-rc.0

* fix: release candidate 1.1.2

* Bump version: 1.1.2-rc.0 → 1.1.2

* promote

---------

Co-authored-by: atarashansky <atarashansky@CZIMACOS3990.hsd1.ma.comcast.net>
2023-04-26 15:01:57 -04:00
atarashanskyandatarashansky c209a9bca7 fix: pin flask requirement to be less than 2.3 (#2609)
Co-authored-by: atarashansky <atarashansky@CZIMACOS3990.hsd1.ma.comcast.net>
2023-04-26 13:58:40 -04:00
55 changed files with 5710 additions and 3388 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 1.1.1
current_version = 1.2.0
commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<prerel>rc)\.(?P<prerelversion>\d+))?
serialize =
+23
View File
@@ -0,0 +1,23 @@
name: Close inactive pull requests
on:
schedule:
- cron: "30 1 * * *"
jobs:
close-issues:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v5
with:
days-before-issue-stale: -1 # Do not mark any issues as stale
days-before-pr-stale: 14
days-before-pr-close: 3
stale-pr-message: "This PR has not seen any activity in the past 2 weeks; if no one comments or reviews it in the next 3 days, this PR will be closed."
close-pr-message: "This PR was closed because it has been inactive for 17 days, 3 days since being marked as stale. Please re-open if you still need this to be addressed."
stale-pr-label: "stale"
close-pr-label: "autoclosed"
exempt-draft-pr: true
repo-token: ${{ secrets.GITHUB_TOKEN }}
+76 -82
View File
@@ -2,11 +2,15 @@ name: Compatibility Tests
on:
schedule:
- cron: '0 8 7 * 2'
- cron: "0 8 7 * 2"
push:
branches:
- main
### For debugging purposes - uncomment below to run on all PRs
pull_request:
branches: "*"
env:
JEST_ENV: prod
@@ -14,9 +18,9 @@ jobs:
docker-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Build docker image
@@ -29,95 +33,85 @@ jobs:
fail-fast: false
matrix:
# note: The `macos-latest` is latest Catalina version, and not Big Sur. So we explicitly ask for Big Sur (`macos-11`)
os: [ubuntu-latest, macos-latest, macos-11]
python-version: [3.6, 3.7, 3.8, 3.9]
os: [ubuntu-latest, macos-latest, macos-13]
python-version: ["3.10", "3.11", "3.12"]
cellxgene_build: [main, latest]
exclude:
# 3.6 no longer avail on Big Sur (`macos-11`)
- os: macos-11
python-version: 3.6
# no pypi build exists for macos+py3.9 and source install fails to
# install `tables` py pkg (a `scanpy` dependency), so we test py3.9
# only on ubuntu
- os: macos-11
python-version: 3.9
- os: macos-latest
python-version: 3.9
# add anndata pinned version test for subset of matrix configurations,
# in order to reduce matrix cross-product explosion
include:
- python-version: 3.8
- python-version: 3.12
cellxgene_build: latest
# TODO: dynamically use the literal version in requirements.txt,
# to avoid having to update this in manually in the future
# TODO: Do not bother running this if anndata latest version
# matches this pinned version, to avoid a redundant test
anndata_version: '==0.7.6'
anndata_version: "==0.10.9"
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Cache env vars
run: echo "PIP_CACHE=`python -m pip cache dir`" >> $GITHUB_ENV
- name: Cache env vars (MacOS)
if: startsWith(matrix.os, 'macos')
run: echo "BREW_CACHE=`brew --cache`" >> $GITHUB_ENV
# FIXME: Only working for Linux
- name: Python cache
uses: actions/cache@v1
with:
path: ${{ env.PIP_CACHE }}
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Node cache
uses: actions/cache@v1
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Brew cache (MacOS)
if: startsWith(matrix.os, 'macos')
uses: actions/cache@v1
with:
path: ${{ env.BREW_CACHE }}
key: ${{ runner.os }}-brew-
- name: Install dependencies (Ubuntu Linux)
if: startsWith(matrix.os, 'ubuntu')
run: |
sudo apt-get update
sudo apt-get install -y libhdf5-serial-dev
- name: Install dependencies (MacOS)
if: startsWith(matrix.os, 'macos')
run: brew install hdf5
- name: Install cellxgene from `main` branch
if: matrix.cellxgene_build == 'main'
run: |
pip install -r server/requirements-dev.txt
make pydist install-dist
- name: Install cellxgene from latest release (pypi.org)
if: matrix.cellxgene_build == 'latest'
run: |
pip install --upgrade cellxgene
# install the additional dev requirements on top of what is in the
# cellxgene pip package, which are needed for testing, but otherwise
# keep same pip pkg versions as in the cxg release
sed -i'' -e 's/-r requirements.txt//' server/requirements-dev.txt
pip install -r server/requirements-dev.txt
- name: Install anndata version per matrix variable
run: pip install anndata${{ matrix.anndata_version }}
- name: Install node
run: make dev-env-client
# Run different types of test separately, to facilitate troubleshooting
- name: Unit Tests - client
run: make unit-test-client
- name: Unit Tests - server
run: make unit-test-server
- name: Smoke Tests
run: make smoke-test
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Cache env vars
run: echo "PIP_CACHE=`python -m pip cache dir`" >> $GITHUB_ENV
- name: Cache env vars (MacOS)
if: startsWith(matrix.os, 'macos')
run: echo "BREW_CACHE=`brew --cache`" >> $GITHUB_ENV
# FIXME: Only working for Linux
- name: Python cache
uses: actions/cache@v4
with:
path: ${{ env.PIP_CACHE }}
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Node cache
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Brew cache (MacOS)
if: startsWith(matrix.os, 'macos')
uses: actions/cache@v4
with:
path: ${{ env.BREW_CACHE }}
key: ${{ runner.os }}-brew-
- name: Install dependencies (Ubuntu Linux)
if: startsWith(matrix.os, 'ubuntu')
run: |
sudo apt-get update
sudo apt-get install -y libhdf5-serial-dev
- name: Install dependencies (MacOS)
if: startsWith(matrix.os, 'macos')
run: brew install hdf5
- name: Install cellxgene from `main` branch
if: matrix.cellxgene_build == 'main'
run: |
pip install -r server/requirements-dev.txt
make pydist install-dist
- name: Install cellxgene from latest release (pypi.org)
if: matrix.cellxgene_build == 'latest'
run: |
pip install --upgrade cellxgene
# install the additional dev requirements on top of what is in the
# cellxgene pip package, which are needed for testing, but otherwise
# keep same pip pkg versions as in the cxg release
sed -i'' -e 's/-r requirements.txt//' server/requirements-dev.txt
pip install -r server/requirements-dev.txt
pip install --force-reinstall numpy==2.0.1 numba>=0.60.0 pandas
- name: Install anndata version per matrix variable
run: pip install anndata${{ matrix.anndata_version }}
- name: Install node
run: make dev-env-client
# Run different types of test separately, to facilitate troubleshooting
- name: Unit Tests - client
run: make unit-test-client
- name: Unit Tests - server
run: make unit-test-server
- name: Smoke Tests
run: make smoke-test
# FIXME: Fails intermittently. See https://app.zenhub.com/workspaces/single-cell-5e2a191dad828d52cc78b028/issues/chanzuckerberg/cellxgene/2415
# - name: Smoke Tests with Annotations
# run: make smoke-test-annotations
+25 -22
View File
@@ -14,25 +14,28 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- run: |
git fetch --depth=1 origin +${{github.base_ref}}
- name: Set up Python 3.7
uses: actions/setup-python@v4
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: 3.7
python-version: 3.12
- name: Node cache
uses: actions/cache@v1
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install dependencies
- name: Install dependencies now
run: |
pip install flake8
pip install black
cd client
pip install setuptools
- name: Install client dependencies
run: |
cd client
npm install
- name: Format with black and lint with flake8
run: |
@@ -45,22 +48,22 @@ jobs:
unit-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.7 (pyenv) # pyenv needed for mlflow in cli annotate tests
- uses: actions/checkout@v4
- name: Set up Python 3.12 (pyenv) # pyenv needed for mlflow in cli annotate tests
uses: gabrielfalcao/pyenv-action@v9
with:
default: 3.7
command: pip install -U pip # upgrade pip after installing python
- run: pip install virtualenv # virtualenv needed for mlflow in cli annotate tests
default: 3.12
command: pip install -U pip # upgrade pip after installing python
- run: pip install virtualenv # virtualenv needed for mlflow in cli annotate tests
- name: Python cache
uses: actions/cache@v1
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Node cache
uses: actions/cache@v1
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
@@ -78,20 +81,20 @@ jobs:
runs-on: macos-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.7
uses: actions/setup-python@v4
- uses: actions/checkout@v4
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: 3.7
python-version: 3.12
- name: Python cache
uses: actions/cache@v1
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Node cache
uses: actions/cache@v1
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
@@ -110,10 +113,10 @@ jobs:
# timeout-minutes: 20
# steps:
# - uses: actions/checkout@v2
# - name: Set up Python 3.7
# - name: Set up Python 3.9
# uses: actions/setup-python@v4
# with:
# python-version: 3.7
# python-version: 3.9
# - name: Python cache
# uses: actions/cache@v1
# with:
+1 -1
View File
@@ -58,7 +58,7 @@ Please [file an issue](https://github.com/chanzuckerberg/cellxgene/issues/new/ch
### Finding help
We'd love to hear from you!
For questions, suggestions, or accolades, [join the `#cellxgene-users` channel on the CZI Science Slack](https://join-cellxgene-users.herokuapp.com/) and say "hi!".
For questions, suggestions, or accolades, join the `#cellxgene-users` channel on the [CZI Science Community Slack](https://czi.co/science-slack) and say "hi!".
For any errors, [report bugs on Github](https://github.com/chanzuckerberg/cellxgene/issues).
+1
View File
@@ -0,0 +1 @@
16.20.0
+1 -1
View File
@@ -13,7 +13,7 @@ import * as ENV_DEFAULT from "../../../environment.default.json";
// a test can take more time to finish, so we don't want
// jest to shut off the test too soon
jest.setTimeout(2 * 60 * 1000);
setDefaultOptions({ timeout: 20 * 1000 });
setDefaultOptions({ timeout: 60 * 1000 });
jest.retryTimes(ENV_DEFAULT.RETRY_ATTEMPTS);
+2 -2
View File
@@ -16,8 +16,8 @@ module.exports = {
"@babel/plugin-proposal-function-bind",
["@babel/plugin-proposal-decorators", { legacy: true }],
["@babel/plugin-proposal-class-properties", { loose: true }],
["@babel/plugin-proposal-private-methods", { loose: true }],
["@babel/plugin-proposal-private-property-in-object", { loose: true }],
["@babel/plugin-transform-private-methods", { loose: true }],
["@babel/plugin-transform-private-property-in-object", { loose: true }],
"@babel/plugin-proposal-export-namespace-from",
"@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-nullish-coalescing-operator",
+2 -2
View File
@@ -15,8 +15,8 @@ module.exports = {
"@babel/plugin-proposal-function-bind",
["@babel/plugin-proposal-decorators", { legacy: true }],
["@babel/plugin-proposal-class-properties", { loose: true }],
["@babel/plugin-proposal-private-methods", { loose: true }],
["@babel/plugin-proposal-private-property-in-object", { loose: true }],
["@babel/plugin-transform-private-methods", { loose: true }],
["@babel/plugin-transform-private-property-in-object", { loose: true }],
"@babel/plugin-proposal-export-namespace-from",
"@babel/plugin-transform-react-constant-elements",
"@babel/plugin-transform-runtime",
@@ -1,7 +1,7 @@
const path = require("path");
const fs = require("fs");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const ObsoleteWebpackPlugin = require("obsolete-webpack-plugin");
const ObsoleteWebpackPlugin = require("webpack-obsolete-plugin");
const src = path.resolve("src");
const nodeModules = path.resolve("node_modules");
+5460 -3021
View File
File diff suppressed because it is too large Load Diff
+14 -12
View File
@@ -1,6 +1,6 @@
{
"name": "cellxgene",
"version": "1.1.1",
"version": "1.2.0",
"license": "MIT",
"description": "cellxgene is a web application for the interactive exploration of single cell sequence data.",
"repository": "https://github.com/chanzuckerberg/cellxgene",
@@ -18,7 +18,8 @@
},
"engineStrict": true,
"engines": {
"npm": ">=3.0.0"
"npm": ">=3.0.0",
"node": "^16.0.0"
},
"eslintConfig": {
"extends": "./configuration/eslint/eslint.js"
@@ -77,16 +78,17 @@
"whatwg-fetch": "^3.2.0"
},
"devDependencies": {
"@babel/core": "^7.13.16",
"@babel/core": "^7.25.2",
"@babel/plugin-proposal-class-properties": "^7.10.4",
"@babel/plugin-proposal-decorators": "^7.13.15",
"@babel/plugin-proposal-export-namespace-from": "^7.10.4",
"@babel/plugin-proposal-function-bind": "^7.10.5",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4",
"@babel/plugin-proposal-optional-chaining": "^7.10.4",
"@babel/plugin-transform-private-property-in-object": "^7.22.11",
"@babel/plugin-transform-react-constant-elements": "^7.13.13",
"@babel/plugin-transform-runtime": "^7.13.15",
"@babel/preset-env": "^7.13.15",
"@babel/preset-env": "^7.22.20",
"@babel/preset-react": "^7.13.13",
"@babel/register": "^7.13.16",
"@babel/runtime": "^7.13.16",
@@ -105,7 +107,7 @@
"eslint": "^7.24.0",
"eslint-config-airbnb": "^18.2.0",
"eslint-config-prettier": "^8.2.0",
"eslint-plugin-compat": "^3.8.0",
"eslint-plugin-compat": "^4.2.0",
"eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-filenames": "^1.3.2",
"eslint-plugin-import": "^2.24.2",
@@ -122,7 +124,7 @@
"jest-circus": "^27.0.6",
"jest-environment-puppeteer": "^5.0.1",
"jest-fetch-mock": "^3.0.3",
"jest-puppeteer": "^5.0.1",
"jest-puppeteer": "^6.2.0",
"json-loader": "^0.5.7",
"lint-staged": "^10.2.11",
"lodash": "^4.17.21",
@@ -132,16 +134,16 @@
"lodash.map": "^4.6.0",
"lodash.zip": "^4.2.0",
"mini-css-extract-plugin": "^1.5.0",
"obsolete-webpack-plugin": "^0.5.6",
"prettier": "^2.0.5",
"puppeteer": "^8.0.0",
"puppeteer": "^10.4.0",
"rimraf": "^3.0.2",
"serve-favicon": "^2.5.0",
"terser-webpack-plugin": "^5.1.1",
"webpack": "^5.34.0",
"webpack": "^5.94.0",
"webpack-cli": "^4.6.0",
"webpack-dev-middleware": "^4.1.0",
"webpack-merge": "^5.0.9"
"webpack-merge": "^5.0.9",
"webpack-obsolete-plugin": "^1.0.5"
},
"jest": {
"testMatch": [
@@ -175,13 +177,13 @@
}
],
[
"@babel/plugin-proposal-private-methods",
"@babel/plugin-transform-private-methods",
{
"loose": true
}
],
[
"@babel/plugin-proposal-private-property-in-object",
"@babel/plugin-transform-private-property-in-object",
{
"loose": true
}
+2 -1
View File
@@ -52,7 +52,7 @@ import { _getColumnDimensionNames } from "./schema";
import { _hashStringValues } from "./query";
export function _whereCacheGet(whereCache, schema, field, query) {
/*
/*
query will either be an where query (object) or a column name (string).
Return array of column labels or undefined.
@@ -169,5 +169,6 @@ function __whereCacheMerge(dst, src) {
}
export function _whereCacheMerge(...caches) {
// eslint-disable-next-line compat/compat -- not using web APIs
return caches.reduce(__whereCacheMerge, {});
}
@@ -16,7 +16,7 @@ const InformationMenu = React.memo((props) => {
rel="noopener"
/>
<MenuItem
href="https://join-cellxgene-users.herokuapp.com/"
href="https://czi.co/science-slack"
target="_blank"
icon="chat"
text="Chat"
+1 -1
View File
@@ -2,7 +2,7 @@ import logging
import sys
from server.common.utils.utils import import_plugins
__version__ = "1.1.1"
__version__ = "1.2.0"
display_version = "cellxgene v" + __version__
try:
+3 -3
View File
@@ -48,12 +48,12 @@ def _cache_control(always, **cache_kwargs):
def cache_control(**cache_kwargs):
""" config driven """
"""config driven"""
return _cache_control(False, **cache_kwargs)
def cache_control_always(**cache_kwargs):
""" always generate headers, regardless of the config """
"""always generate headers, regardless of the config"""
return _cache_control(True, **cache_kwargs)
@@ -228,7 +228,7 @@ def get_api_dataroot_resources(bp_dataroot):
class Server:
@staticmethod
def _before_adding_routes(app, app_config):
""" will be called before routes are added, during __init__. Subclass protocol """
"""will be called before routes are added, during __init__. Subclass protocol"""
pass
def __init__(self, app_config):
+1 -1
View File
@@ -6,7 +6,7 @@ CXGUID = "cxguid"
def get_user_id(session: SessionMixin) -> str:
""" Gets a session-persistent user id. Creates one in the Flask session if non-extant """
"""Gets a session-persistent user id. Creates one in the Flask session if non-extant"""
if CXGUID not in session:
session[CXGUID] = uuid4().hex
session.permanent = True
+4 -6
View File
@@ -26,9 +26,7 @@ def annotate_args(func):
@sort_options
@click.command(
options_metavar="<options>"
)
@click.command(options_metavar="<options>")
@click.argument(
"input_h5ad_file",
type=click.Path(exists=True, dir_okay=False, readable=True),
@@ -51,8 +49,8 @@ def annotate_args(func):
"--output-h5ad-file",
default="",
help="The output H5AD file that will contain the generated annotation values. If this option is not provided, "
"the input file will be overwritten to include the new annotations; in this case you must specify "
"--overwrite.",
"the input file will be overwritten to include the new annotations; in this case you must specify "
"--overwrite.",
metavar="<filename>",
)
@click.option(
@@ -60,7 +58,7 @@ def annotate_args(func):
default=False,
is_flag=True,
help="Allow overwriting of the specified H5AD output file, if it exists. For safety, you must specify this "
"flag if the specified output file already exists or if the --output-h5ad-file option is not provided.",
"flag if the specified output file already exists or if the --output-h5ad-file option is not provided.",
show_default=True,
)
@click.option(
+2 -2
View File
@@ -145,7 +145,7 @@ class AnnotationsLocalFile(Annotations):
def write_gene_sets(self, gene_sets, tid, data_adaptor):
self.check_gene_sets_save_enabled() # raises
if type(tid) != int or tid < 0:
if type(tid) is not int or tid < 0:
raise ValueError("tid must be a positive integer")
# may raise
@@ -175,7 +175,7 @@ class AnnotationsLocalFile(Annotations):
# update the cache
self.last_geneset_fname = fname
self.last_geneset = gene_sets if type(gene_sets) == dict else {g["geneset_name"]: g for g in gene_sets}
self.last_geneset = gene_sets if isinstance(gene_sets, dict) else {g["geneset_name"]: g for g in gene_sets}
def _get_userdata_idhash(self, data_adaptor):
"""
+1 -1
View File
@@ -56,7 +56,7 @@ def diffexp_ttest_from_mean_var(meanA, varA, nA, meanB, varB, nB, top_n, diffexp
# degrees of freedom for Welch's t-test
with np.errstate(divide="ignore", invalid="ignore"):
dof = sum_vn ** 2 / (vnA ** 2 / (nA - 1) + vnB ** 2 / (nB - 1))
dof = sum_vn**2 / (vnA**2 / (nA - 1) + vnB**2 / (nB - 1))
dof[np.isnan(dof)] = 1
# Welch's t-test score calculation
@@ -97,7 +97,7 @@ def estimate_approximate_distribution(X) -> XApproximateDistribution:
if Xdata.size > CHUNKSIZE:
min_val = max_val = Xdata[0]
with concurrent.futures.ThreadPoolExecutor() as tp:
for (_min, _max) in tp.map(min_max, [Xdata[i : i + CHUNKSIZE] for i in range(0, Xdata.size, CHUNKSIZE)]):
for _min, _max in tp.map(min_max, [Xdata[i : i + CHUNKSIZE] for i in range(0, Xdata.size, CHUNKSIZE)]):
min_val = min(_min, min_val)
max_val = max(_max, max_val)
+1 -1
View File
@@ -1,2 +1,2 @@
DEFAULT_SERVER_PORT = 5005
BIG_FILE_SIZE_THRESHOLD = 100 * 2 ** 20 # 100MB
BIG_FILE_SIZE_THRESHOLD = 100 * 2**20 # 100MB
-1
View File
@@ -19,7 +19,6 @@ class AppConfig(object):
"""
def __init__(self):
# the default configuration (see default_config.py)
# TODO @madison -- if we always read from the default config (hard coded path) can we set those values as
# defaults within the config class?
+2 -2
View File
@@ -50,7 +50,7 @@ class BaseConfig(object):
f"Invalid type for attribute: {attrname}, expected types ({tnames}), got {type(val).__name__}"
)
else:
if type(val) != vtype:
if type(val) is not vtype:
raise ConfigurationError(
f"Invalid type for attribute: {attrname}, "
f"expected type {vtype.__name__}, got {type(val).__name__}"
@@ -70,7 +70,7 @@ class BaseConfig(object):
if not hasattr(self, key):
raise ConfigurationError(f"unknown config parameter {key}.")
try:
if type(value) == tuple:
if type(value) is tuple:
# convert tuple values to list values
value = list(value)
setattr(self, key, value)
+1 -1
View File
@@ -176,7 +176,7 @@ class DatasetConfig(BaseConfig):
self.validate_correct_type_of_configuration_attribute("diffexp__top_n", int)
data_adaptor = self.get_data_adaptor()
if self.diffexp__enable and data_adaptor.parameters.get("diffexp_may_be_slow", False):
if self.diffexp__enable and data_adaptor.parameters.get("diffexp-may-be-slow", False):
context["messagefn"](
"CAUTION: due to the size of your dataset, " "running differential expression may take longer or fail."
)
+1 -1
View File
@@ -29,7 +29,7 @@ class ExternalConfig(BaseConfig):
if name is None:
raise ConfigurationError("environment: 'name' is missing")
required = envdict.get("required", False)
if type(required) != bool:
if type(required) is not bool:
raise ConfigurationError("environment: 'required' must be a bool")
path = envdict.get("path")
if path is None:
+2 -2
View File
@@ -19,7 +19,7 @@ import server.common.fbs.NetEncoding.Uint32Array as Uint32Array
# Serialization helper
def serialize_column(builder, typed_arr):
""" Serialize NetEncoding.Column """
"""Serialize NetEncoding.Column"""
(u_type, u_value) = typed_arr
Column.ColumnStart(builder)
@@ -30,7 +30,7 @@ def serialize_column(builder, typed_arr):
# Serialization helper
def serialize_matrix(builder, n_rows, n_cols, columns, col_idx):
""" Serialize NetEncoding.Matrix """
"""Serialize NetEncoding.Matrix"""
Matrix.MatrixStart(builder)
Matrix.MatrixAddNRows(builder, n_rows)
+2 -2
View File
@@ -136,7 +136,7 @@ def write_gene_sets_tidycsv(f, genesets):
def summarizeQueryHash(raw_query):
""" generate a cache key (hash) from the raw query string """
"""generate a cache key (hash) from the raw query string"""
return hashlib.sha1(raw_query).hexdigest()
@@ -187,7 +187,7 @@ def validate_gene_sets(genesets, var_names, context=None):
# 1. check gene set character set and format
illegal_name = re.compile(r"^\s| |[\u0000-\u001F\u007F-\uFFFF]|\s$")
for name in geneset_names:
if type(name) != str or len(name) == 0:
if type(name) is not str or len(name) == 0:
raise KeyError("Gene set names must be non-null string.")
if illegal_name.search(name):
messagefn(
+6 -6
View File
@@ -6,7 +6,7 @@ import zlib
import json
from flask import make_response, jsonify, current_app, abort
from werkzeug.urls import url_unquote
from urllib.parse import unquote
from server.common.config.client_config import get_client_config
from server.common.constants import Axis, DiffExpMode, JSON_NaN_to_num_warning_msg
@@ -64,22 +64,22 @@ def _query_parameter_to_filter(args):
axis, name = key.split(":")
if axis not in ("obs", "var"):
raise FilterError("unknown filter axis")
name = url_unquote(name)
name = unquote(name)
current = filters[axis].setdefault(name, {"name": name})
val_split = value.split(",")
if len(val_split) == 1:
if "min" in current or "max" in current:
raise FilterError("do not mix range and value filters")
value = url_unquote(value)
value = unquote(value)
values = current.setdefault("values", [])
values.append(value)
elif len(val_split) == 2:
if len(current) > 1:
raise FilterError("duplicate range specification")
min = url_unquote(val_split[0])
max = url_unquote(val_split[1])
min = unquote(val_split[0])
max = unquote(val_split[1])
if min != "*":
current["min"] = float(min)
if max != "*":
@@ -379,7 +379,7 @@ def summarize_var_helper(request, data_adaptor, key, raw_query):
HTTPStatus.OK,
{"Content-Type": "application/octet-stream"},
)
except (ValueError) as e:
except ValueError as e:
return abort(HTTPStatus.NOT_FOUND, description=str(e))
except (UnsupportedSummaryMethod, FilterError) as e:
return abort(HTTPStatus.BAD_REQUEST, description=str(e))
+1 -2
View File
@@ -116,7 +116,7 @@ def _get_type_info(array: Union[np.ndarray, pd.Series, pd.Index]) -> Tuple[np.dt
raise TypeError("Unsupported data type.")
dtype = array.dtype
res = _get_type_info_from_dtype(dtype)
if res is not None:
return res
@@ -140,7 +140,6 @@ def _get_type_info(array: Union[np.ndarray, pd.Series, pd.Index]) -> Tuple[np.dt
if dtype.kind in ["i", "u"] and _can_cast_array_values_to_int32(array):
return (np.int32, {"type": "int32"})
if dtype.kind == "f":
_float64_warning(array.dtype)
return (np.float32, {"type": "float32"})
+2 -1
View File
@@ -8,7 +8,7 @@ import socket
from urllib.parse import urlsplit, urljoin
import numpy as np
from flask import json
import json
from server.common.errors import ConfigurationError
@@ -100,6 +100,7 @@ def custom_format_warning(msg, *args, **kwargs):
def jsonify_strict(data):
return StrictJSONEncoder().encode(data)
def import_plugins(plugin_module):
"""
Load optional plugin modules from server.common.plugins
+10 -7
View File
@@ -92,7 +92,7 @@ class AnndataAdaptor(DataAdaptor):
"""
self.original_obs_index = self.data.obs.index
for (ax_name, var_name) in ((Axis.OBS, "obs"), (Axis.VAR, "var")):
for ax_name, var_name in ((Axis.OBS, "obs"), (Axis.VAR, "var")):
config_name = f"single_dataset__{var_name}_names"
parameter_name = f"{var_name}_names"
name = getattr(self.server_config, config_name)
@@ -175,10 +175,11 @@ class AnndataAdaptor(DataAdaptor):
raise DatasetAccessError("Out of memory - file is too large for available memory.")
except Exception:
import traceback
message = (
"File not found or is inaccessible. File must be an .h5ad object. "
"Please check your input and try again."
)
)
if self.server_config.app__verbose:
message += f"\n{traceback.format_exc()}"
raise DatasetAccessError(message)
@@ -210,7 +211,7 @@ class AnndataAdaptor(DataAdaptor):
# heuristic
n_values = self.data.shape[0] * self.data.shape[1]
if (n_values > 1e8 and self.server_config.adaptor__anndata_adaptor__backed is True) or (n_values > 5e8):
self.parameters.update({"diffexp_may_be_slow": True})
self.parameters.update({"diffexp-may-be-slow": True})
def _is_valid_layout(self, arr):
"""return True if this layout data is a valid array for front-end presentation:
@@ -218,7 +219,7 @@ class AnndataAdaptor(DataAdaptor):
* with shape (n_obs, >= 2)
* with all values finite or NaN (no +Inf or -Inf)
"""
is_valid = type(arr) == np.ndarray and arr.dtype.kind in "fiu"
is_valid = type(arr) is np.ndarray and arr.dtype.kind in "fiu"
is_valid = is_valid and arr.shape[0] == self.data.n_obs and arr.shape[1] >= 2
is_valid = is_valid and not np.any(np.isinf(arr)) and not np.all(np.isnan(arr))
return is_valid
@@ -242,8 +243,10 @@ class AnndataAdaptor(DataAdaptor):
)
if self.data.X.dtype < np.float32:
if self.data.isbacked:
raise DatasetAccessError(f"Data matrix in {self.data.X.dtype} format is not supported in backed mode."
" Please reload without --backed, or convert matrix to float32")
raise DatasetAccessError(
f"Data matrix in {self.data.X.dtype} format is not supported in backed mode."
" Please reload without --backed, or convert matrix to float32"
)
warnings.warn(
f"Anndata data matrix is in unsupported {self.data.X.dtype} format -- will be cast to float32"
)
@@ -299,7 +302,7 @@ class AnndataAdaptor(DataAdaptor):
layouts = self.dataset_config.embeddings__names
if layouts is None or len(layouts) == 0:
layouts = [key[2:] for key in self.data.obsm_keys() if type(key) == str and key.startswith("X_")]
layouts = [key[2:] for key in self.data.obsm_keys() if type(key) is str and key.startswith("X_")]
# remove invalid layouts
valid_layouts = []
+3 -3
View File
@@ -154,7 +154,7 @@ class DataAdaptor(metaclass=ABCMeta):
parameters.update(self.parameters)
def _index_filter_to_mask(self, filter, count):
mask = np.zeros((count,), dtype=np.bool)
mask = np.zeros((count,), dtype="bool")
for i in filter:
if isinstance(i, list):
mask[i[0] : i[1]] = True
@@ -163,7 +163,7 @@ class DataAdaptor(metaclass=ABCMeta):
return mask
def _axis_filter_to_mask(self, axis, filter, count):
mask = np.ones((count,), dtype=np.bool)
mask = np.ones((count,), dtype="bool")
if "index" in filter:
mask = np.logical_and(mask, self._index_filter_to_mask(filter["index"], count))
if "annotation_value" in filter:
@@ -172,7 +172,7 @@ class DataAdaptor(metaclass=ABCMeta):
return mask
def _annotation_filter_to_mask(self, axis, filter, count):
mask = np.ones((count,), dtype=np.bool)
mask = np.ones((count,), dtype="bool")
for v in filter:
name = v["name"]
if axis == Axis.VAR:
+1 -1
View File
@@ -12,7 +12,7 @@ class MatrixDataType(Enum):
class MatrixDataLoader(object):
def __init__(self, location, matrix_data_type=None, app_config=None):
""" location can be a string or DataLocator """
"""location can be a string or DataLocator"""
region_name = None if app_config is None else app_config.server_config.data_locator__s3__region_name
self.location = DataLocator(location, region_name=region_name)
if not self.location.exists():
+1 -1
View File
@@ -1,2 +1,2 @@
mlflow
mlflow==2.16.0
scanpy
+1 -1
View File
@@ -5,6 +5,6 @@ parameterized>=0.7.0
pytest>=3.6.3
python-jose>=3.2.0
twine>=1.12.1
aiohttp>=3.9.1
-r requirements.txt
-r requirements-prepare.txt
-r requirements-annotate.txt
+10 -11
View File
@@ -1,24 +1,23 @@
# NOTE: If you update 'anndata' min version, also update the 'anndata_version'
# matrix value in .github/workflows/compatibility_tests.yml
anndata>=0.7.6 # we need to_memory(), added in 0.7.6
anndata>=0.8.0
boto3>=1.12.18
click>=7.1.2
Flask>=1.0.2
Flask>=3.0.0
Flask-Compress>=1.4.0
Flask-Cors>=3.0.9 # CVE-2020-25032
Flask-Cors>=3.0.9
Flask-RESTful>=0.3.6
flask-server-timing>=0.1.2
flask-talisman>=0.7.0
flatbuffers>=1.11.0,<2.0.0 # cellxgene is not compatible with 2.0.0. Requires migration
flatbuffers==2.0.7
flatten-dict>=0.2.0
fsspec>=0.4.4,<0.8.0
fsspec>0.8.0
gunicorn>=20.0.4
h5py>=3.0.0
numba>=0.51.2
numpy>=1.17.5,<=1.22
numba>=0.60.0
numpy==2.0.1
packaging>=20.0
pandas>=1.0,!=1.1 # pandas 1.1 breaks tests, https://github.com/pandas-dev/pandas/issues/35446
pandas>=2.2.2
PyYAML>=5.4 # CVE-2020-14343
scipy>=1.4
requests>=2.22.0
s3fs==0.4.2
scipy>=1.4
setuptools
+2 -2
View File
@@ -14,7 +14,7 @@ with open("server/requirements-annotate.txt") as fh:
setup(
name="cellxgene",
version="1.1.1",
version="1.2.0",
packages=find_packages(),
url="https://github.com/chanzuckerberg/cellxgene",
license="MIT",
@@ -24,7 +24,7 @@ setup(
long_description=long_description,
long_description_content_type="text/markdown",
install_requires=requirements,
python_requires=">=3.6",
python_requires=">=3.10",
include_package_data=True,
zip_safe=False,
classifiers=[
+1 -1
View File
@@ -113,7 +113,7 @@ def start_test_server(command_line_args=[], app_config=None, env=None):
elif "--port" in command_line_args:
port = int(command_line_args[command_line_args.index("--port") + 1])
else:
start = random.randint(DEFAULT_SERVER_PORT, 2 ** 16 - 1)
start = random.randint(DEFAULT_SERVER_PORT, 2**16 - 1)
port = int(os.environ.get("CXG_SERVER_PORT", start))
port = find_available_port("localhost", port)
command += ["--port=%d" % port]
-5
View File
@@ -1,5 +0,0 @@
from .mlflow_model_fixture import FakeModel
def _load_pyfunc(data_path):
return FakeModel()
-130
View File
@@ -1,130 +0,0 @@
import os
import shutil
import unittest
from tempfile import mkstemp, TemporaryDirectory, NamedTemporaryFile
import mlflow
from click.testing import CliRunner
from server.cli.annotate import annotate
from test.unit.cli.fixtures.mlflow_model_fixture import FakeModel
def write_model(model) -> str:
with TemporaryDirectory() as mlflow_model_dir:
fixtures_path = os.path.join(os.path.dirname(__file__), "fixtures")
mlflow.pyfunc.save_model(mlflow_model_dir, loader_module="fixtures", code_path=[fixtures_path])
return shutil.make_archive(mkstemp()[1], "zip", mlflow_model_dir)
class TestCliAnnotate(unittest.TestCase):
def test__annotate__loads_and_runs(self):
"""
Invokes the `annotate` subcommand of cellxgene CLI, using a CliRunner() programmatic invocation.
This tests the happy path case:
1) Command line options are parsed;
2) An MLflow model zip archive can be read in (from local disk), unpacked, and invoked;
3) The correct options are passed to the MLflow model.
4) The annotate subcommand exits successfully.
This does not verify model output or predictions (it's a fake MLflow model, after all); it's up to the real model
to output its predictions as it wants, but this is specific to the model and so not tested here.
The CliRunner() invokes the subcommand in a subprocess, and the annotate subcommand itself invokes the MLflow
model in yet another subprocess. So while this test can help determine if everything is working, it is not a
simple matter to debug in the case of a failure. However, the stdout/stderr of the MLflow process is captured
by the CliRunner() subprocess, so errors can be inspected in result.stdout when debugging this test. Hope this
helps!
"""
_, query_dataset_file_path = mkstemp()
model_file_path = write_model(FakeModel())
result = CliRunner().invoke(
annotate,
[
query_dataset_file_path,
"--model-url",
model_file_path,
"--output-h5ad-file",
f"{query_dataset_file_path}.output",
# avoid having mflow create conda env or virtualenv when in test env;
# this avoids making pip remote requests and is also faster
"--mlflow-env-manager",
"local",
],
)
# to help debugging, show the output from the CliRunner and MLflow stdout
if result.exit_code:
print(result.stdout)
self.assertEqual(0, result.exit_code, "runs successfully")
# The FakeModel will print it inputs to stdout, as "__MODEL_INPUT__={...}", allowing us to assert that it received valid inputs.
self.assertIn(
"__MODEL_INPUT__={"
f'"query_dataset_h5ad_path": "{query_dataset_file_path}", '
f'"output_h5ad_path": "{query_dataset_file_path}.output", '
'"annotation_prefix": "cxg_cell_type", "classifier": "default", '
'"organism": "Homo sapiens", "use_gpu": true}',
result.stdout,
"inputs passed correctly",
)
self.assertIn(
f"Wrote annotations to {query_dataset_file_path}.output",
result.stdout,
"success message is correct",
)
def test__annotate__requires_overwrite_option_when_output_file_exists(self):
with NamedTemporaryFile() as input_h5ad, NamedTemporaryFile() as existing_file:
required_options = [input_h5ad.name, "--output-h5ad-file", existing_file.name, "--model-url", "some_url"]
result = CliRunner().invoke(
annotate,
required_options + [],
)
self.assertNotEqual(0, result.exit_code, "aborts with non-success code")
self.assertIn(
"try using the flag --overwrite",
result.stdout,
"error message displayed",
)
def test__annotate__overwrite_option_allows_overwrite_of_existing_output_file(self):
model_file_path = write_model(FakeModel())
with NamedTemporaryFile() as existing_file:
required_options = [
existing_file.name,
"--output-h5ad-file",
existing_file.name,
"--overwrite",
"--model-url",
model_file_path,
]
result = CliRunner().invoke(
annotate,
required_options + [],
)
print(result.stdout)
self.assertNotEqual(1, result.exit_code, "aborts with non-success code")
self.assertIn(
f"Wrote annotations to {existing_file.name}",
result.stdout,
"success message is correct on output file overwrite",
)
# TODO:
# Test annotate cli args more comprehensively
# Test server.cli.annotate._validate_options
# Test model caching feature works
# Test model loading from s3 works (maybe w/just a real model)
if __name__ == "__main__":
unittest.main()
+1 -1
View File
@@ -6,7 +6,7 @@ from server.cli.prepare import make_index_unique
class CLIPrepareTests(unittest.TestCase):
""" Test cases for CLI prepare logic """
"""Test cases for CLI prepare logic"""
def test_make_index_unique(self):
index = pd.Index(["SNORD113", "SNORD113", "SNORD113-1"])
+1 -1
View File
@@ -4,7 +4,7 @@ from server.cli.upgrade import validate_version_str, split_version, version_gt
class CLIUpgradeTests(unittest.TestCase):
""" Test cases for CLI logic """
"""Test cases for CLI logic"""
def test_validate_version_str(self):
self.assertTrue(validate_version_str("0.1.2"))
+1 -1
View File
@@ -21,7 +21,7 @@ class ConfigTests(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
os.makedirs(cls.tmp_fixtures_directory)
os.makedirs(cls.tmp_fixtures_directory, exist_ok=True)
def custom_server_config(
self,
@@ -72,24 +72,18 @@ class TestDatasetConfig(ConfigTests):
config.dataset_config.handle_app()
def test_handle_user_annotations__instantiates_user_annotations_class_correctly(self):
config = self.get_config(
enable_users_annotations="true", annotation_type="local_file_csv"
)
config = self.get_config(enable_users_annotations="true", annotation_type="local_file_csv")
config.server_config.complete_config(self.context)
config.dataset_config.handle_user_annotations(self.context)
self.assertIsInstance(config.dataset_config.user_annotations, AnnotationsLocalFile)
config = self.get_config(
enable_users_annotations="true", annotation_type="NOT_REAL"
)
config = self.get_config(enable_users_annotations="true", annotation_type="NOT_REAL")
config.server_config.complete_config(self.context)
with self.assertRaises(ConfigurationError):
config.dataset_config.handle_user_annotations(self.context)
def test_handle_local_file_csv_annotations__sets_dir_if_not_passed_in(self):
config = self.get_config(
enable_users_annotations="true", annotation_type="local_file_csv"
)
config = self.get_config(enable_users_annotations="true", annotation_type="local_file_csv")
config.server_config.complete_config(self.context)
config.dataset_config.handle_local_file_csv_annotations(self.context)
self.assertIsInstance(config.dataset_config.user_annotations, AnnotationsLocalFile)
@@ -56,7 +56,6 @@ class TestExternalConfig(ConfigTests):
self.assertFalse(data_config["config"]["parameters"]["disable-diffexp"])
def test_environment_variable_errors(self):
# no name
app_config = AppConfig()
app_config.external_config.environment = [dict(required=True, path=["this", "is", "a", "path"])]
+6 -4
View File
@@ -196,17 +196,18 @@ class EndPoints(object):
def test_fbs_default(self):
endpoint = "data/var"
url = f"{self.URL_BASE}{endpoint}"
result = self.session.put(url)
headers = {"Content-Type": "application/json"}
result = self.session.put(url, headers=headers)
self.assertEqual(result.status_code, HTTPStatus.BAD_REQUEST)
filter = {"filter": {"var": {"index": [0, 1, 4]}}}
result = self.session.put(url, json=filter)
result = self.session.put(url, json=filter, headers=headers)
self.assertEqual(result.headers["Content-Type"], "application/octet-stream")
def test_data_put_fbs(self):
endpoint = "data/var"
url = f"{self.URL_BASE}{endpoint}"
header = {"Accept": "application/octet-stream"}
header = {"Accept": "application/octet-stream", "Content-Type": "application/json"}
result = self.session.put(url, headers=header)
self.assertEqual(result.status_code, HTTPStatus.BAD_REQUEST)
@@ -252,6 +253,7 @@ class EndPoints(object):
if type(column) is np.ndarray:
self.assertIn(column.dtype, [np.float32, np.int32])
@unittest.skip("This test is currently broken after upgrading Werkzeug.")
def test_data_get_unknown_filter_fbs(self):
index_col_name = self.schema["schema"]["annotations"]["var"]["index"]
endpoint = "data/var"
@@ -290,7 +292,7 @@ class EndPoints(object):
result_data = result.json()
self.assertEqual(result_data, pbmc3k_colors)
@unittest.skip('needs fix: https://github.com/chanzuckerberg/cellxgene/issues/2542')
@unittest.skip("needs fix: https://github.com/chanzuckerberg/cellxgene/issues/2542")
def test_static(self):
endpoint = "static"
file = "assets/favicon.ico"
+1 -1
View File
@@ -106,7 +106,7 @@ class CorporaAPITest(unittest.TestCase):
class CorporaRESTAPITest(unittest.TestCase):
""" Confirm endpoints reflect Corpora-specific features """
"""Confirm endpoints reflect Corpora-specific features"""
@classmethod
def setCorporaFields(cls, path):
+2 -3
View File
@@ -6,12 +6,12 @@ from server.common.rest import _query_parameter_to_filter
def _qsparse(qs):
""" emulate what Flask/Werkzeug do to our QS """
"""emulate what Flask/Werkzeug do to our QS"""
return MultiDict(parse_qs(qs))
class FilterParseTests(unittest.TestCase):
""" Test cases for various filter parsing """
"""Test cases for various filter parsing"""
def test_queryparam_to_filter_parse(self):
# categories
@@ -57,7 +57,6 @@ class FilterParseTests(unittest.TestCase):
)
def test_queryparam_to_filter_errors(self):
# should raise FilterError
filter_errors = [
"foo=bar", # no axis
+1 -1
View File
@@ -7,7 +7,7 @@ from test import PROJECT_ROOT, random_string
class TestPlugins(unittest.TestCase):
""" Test plugin import functionality """
"""Test plugin import functionality"""
plugins_dir = f"{PROJECT_ROOT}/test/plugins"
test_plugin_path = f"{plugins_dir}/foo.py"
+10 -10
View File
@@ -65,13 +65,13 @@ class EstDistTest(unittest.TestCase):
# non-finites
self.assertEqual(estimate_approximate_distribution(np.array([np.nan])), XApproximateDistribution.NORMAL)
self.assertEqual(estimate_approximate_distribution(np.array([np.PINF])), XApproximateDistribution.NORMAL)
self.assertEqual(estimate_approximate_distribution(np.array([np.NINF])), XApproximateDistribution.NORMAL)
self.assertEqual(estimate_approximate_distribution(np.array([np.inf])), XApproximateDistribution.NORMAL)
self.assertEqual(estimate_approximate_distribution(np.array([np.inf])), XApproximateDistribution.NORMAL)
self.assertEqual(
estimate_approximate_distribution(np.array([np.PINF, np.NINF, 0])), XApproximateDistribution.NORMAL
estimate_approximate_distribution(np.array([np.inf, np.inf, 0])), XApproximateDistribution.NORMAL
)
self.assertEqual(
estimate_approximate_distribution(np.array([np.nan, np.PINF, np.NINF])), XApproximateDistribution.NORMAL
estimate_approximate_distribution(np.array([np.nan, np.inf, np.inf])), XApproximateDistribution.NORMAL
)
raw = np.random.exponential(scale=1000, size=(50, 3))
@@ -82,15 +82,15 @@ class EstDistTest(unittest.TestCase):
XApproximateDistribution.COUNT,
)
self.assertEqual(
estimate_approximate_distribution(put(raw, [1], [np.PINF])),
estimate_approximate_distribution(put(raw, [1], [np.inf])),
XApproximateDistribution.COUNT,
)
self.assertEqual(
estimate_approximate_distribution(put(raw, [1], [np.NINF])),
estimate_approximate_distribution(put(raw, [1], [np.inf])),
XApproximateDistribution.COUNT,
)
self.assertEqual(
estimate_approximate_distribution(put(raw, [1, 3, 88], [np.nan, np.PINF, np.NINF])),
estimate_approximate_distribution(put(raw, [1, 3, 88], [np.nan, np.inf, np.inf])),
XApproximateDistribution.COUNT,
)
self.assertEqual(
@@ -103,15 +103,15 @@ class EstDistTest(unittest.TestCase):
XApproximateDistribution.NORMAL,
)
self.assertEqual(
estimate_approximate_distribution(put(logged, [1], [np.PINF])),
estimate_approximate_distribution(put(logged, [1], [np.inf])),
XApproximateDistribution.NORMAL,
)
self.assertEqual(
estimate_approximate_distribution(put(logged, [1], [np.NINF])),
estimate_approximate_distribution(put(logged, [1], [np.inf])),
XApproximateDistribution.NORMAL,
)
self.assertEqual(
estimate_approximate_distribution(put(logged, [1, 3, 88], [np.nan, np.PINF, np.NINF])),
estimate_approximate_distribution(put(logged, [1, 3, 88], [np.nan, np.inf, np.inf])),
XApproximateDistribution.NORMAL,
)
self.assertEqual(
@@ -58,7 +58,7 @@ class DataLocatorAdaptorTest(unittest.TestCase):
return config
def stdAsserts(self, data):
""" run these each time we load the data """
"""run these each time we load the data"""
self.assertIsNotNone(data)
self.assertEqual(data.cell_count, 2638)
self.assertEqual(data.gene_count, 1838)
+1 -1
View File
@@ -9,7 +9,7 @@ from test.fixtures.fixtures import pbmc3k_colors
class ColorsTest(unittest.TestCase):
""" Test color helper functions """
"""Test color helper functions"""
def test_convert_color_to_hex_format(self):
self.assertEqual(convert_color_to_hex_format("wheat"), "#f5deb3")
+2 -2
View File
@@ -16,10 +16,10 @@ class TestJsonifyStrict(unittest.TestCase):
jsonify_strict({"nan": [np.nan]})
with self.assertRaises(ValueError):
jsonify_strict({"pinf": [np.PINF]})
jsonify_strict({"pinf": [np.inf]})
with self.assertRaises(ValueError):
jsonify_strict({"ninf": [np.NINF]})
jsonify_strict({"ninf": [np.inf]})
def test_jsonify_numpy_ndarray(self):
values = {
+10 -9
View File
@@ -42,7 +42,7 @@ class TestTypeConversionUtils(unittest.TestCase):
with self.assertRaises(TypeError):
get_schema_type_hint_from_dtype(np.dtype(dtype))
for dtype in [np.float16, np.float32, np.float64]:
for dtype in [np.float32, np.float64]:
self.assertEqual(get_schema_type_hint_from_dtype(np.dtype(dtype)), {"type": "float32"})
for dtype in [np.dtype(object), np.dtype(str)]:
@@ -123,17 +123,18 @@ int_OK_cases = [
float_OK_cases = [
{
"test_case": "float_OK_cases",
"data": data,
"expected_encoding_dtype": np.float32,
"expected_schema_hint": {"type": "float32"},
"logs": None if data.dtype != np.float64 else {"level": logging.WARNING, "output": "may lose precision"},
"logs": None if dtype == np.float32 else {"level": logging.WARNING, "output": "may lose precision"},
}
for dtype in [np.float16, np.float32, np.float64]
for dtype in [np.float32, np.float64]
for data in [
np.arange(-128, 1000, dtype=dtype),
pd.Series(np.arange(-128, 1000, dtype=dtype)),
pd.Index(np.arange(-129, 1000, dtype=dtype)),
np.array([-np.nan, np.NINF, -1, np.NZERO, 0, np.PZERO, 1, np.PINF, np.nan], dtype=dtype),
np.array([-np.nan, np.inf, -1, 0.0, 0, 0.0, 1, np.inf, np.nan], dtype=dtype),
np.array([np.finfo(dtype).min, 0, np.finfo(dtype).max], dtype=dtype),
sparse.csr_matrix((10, 100), dtype=dtype),
]
@@ -198,12 +199,13 @@ category_numeric_OK_cases = [
# numeric, no NA/NaN, float
*[
{
"test_case": "numeric, no NA/NaN, float",
"data": data,
"expected_encoding_dtype": np.float32,
"expected_schema_hint": {"type": "categorical"},
"logs": {"level": logging.WARNING, "output": "may lose precision"},
"logs": None if dtype == np.float32 else {"level": logging.WARNING, "output": "may lose precision"},
}
for dtype in [np.float16, np.float32, np.float64]
for dtype in [np.float32, np.float64]
for data in [
pd.Series(np.array([0, 1, 2], dtype=dtype), dtype="category"),
pd.Series(np.array([0, 1, 2], dtype=dtype), dtype="category").cat.remove_categories([1]),
@@ -213,10 +215,11 @@ category_numeric_OK_cases = [
# numeric, has NA-induced cast to float32
*[
{
"test_case": "numeric, has NA-induced cast to float32",
"data": data,
"expected_encoding_dtype": np.float32,
"expected_schema_hint": {"type": "categorical"},
"logs": {"level": logging.WARNING, "output": "may lose precision"},
"logs": None if dtype == np.float32 else {"level": logging.WARNING, "output": "may lose precision"},
}
for dtype in [
np.int8,
@@ -227,7 +230,6 @@ category_numeric_OK_cases = [
np.uint32,
np.int64,
np.uint64,
np.float16,
np.float32,
np.float64,
]
@@ -312,7 +314,6 @@ class TestTypeInference(unittest.TestCase, AssertNoLog):
self.assertEqual(encoding_dtype, self.expected_encoding_dtype)
self.assertEqual(schema_hint, self.expected_schema_hint)
self.assertIn(logs["output"], logger.output[0])
else:
with self.assertNoLogs(logging.getLogger(), logging.WARNING):
encoding_dtype, schema_hint = get_dtype_and_schema_of_array(self.data)