Skip to content

Commit

Permalink
Merge 61c8855 into 58d60f4
Browse files Browse the repository at this point in the history
  • Loading branch information
scottgigante committed Apr 1, 2019
2 parents 58d60f4 + 61c8855 commit 9f0a0ce
Show file tree
Hide file tree
Showing 12 changed files with 558 additions and 45 deletions.
14 changes: 14 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,28 @@
- libhdf5-dev
- ffmpeg
- pandoc
- gfortran
- libblas-dev
- liblapack-dev

cache:
- pip
- apt
- directories:
- $HOME/R/Library

install:
- python setup.py install

before_script:
- sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E298A3A825C0D65DFD57CBB651716619E084DAB9
- echo "deb http://cran.rstudio.com/bin/linux/ubuntu xenial-cran35/" | sudo tee -a /etc/apt/sources.list
- sudo apt-get update -qq
- sudo apt-get install r-base r-base-core -y
- export R_LIBS_USER="$HOME/R/Library"
- echo ".libPaths(c('$R_LIBS_USER', .libPaths()))" >> $HOME/.Rprofile
- Rscript travis_setup.R

script:
- pip install -U .[test]
- python setup.py test
Expand Down
4 changes: 2 additions & 2 deletions doc/source/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ scikit-learn>=0.19.1
pandas>=0.19.0,<0.24
decorator
matplotlib
sphinx
sphinx<=1.8.5
sphinxcontrib-napoleon
autodocsumm
ipykernel
nbsphinx
.
.
1 change: 1 addition & 0 deletions scprep/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
import scprep.sanitize
import scprep.stats
import scprep.reduce
import scprep.run
45 changes: 29 additions & 16 deletions scprep/_lazyload.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
'axes', 'lines', 'ticker', 'transforms'],
'mpl_toolkits': ['mplot3d'],
'fcsparser': ['api'],
'rpy2': [{'robjects': ['numpy2ri', 'packages', 'vectors', 'conversion']},
'rinterface',
{'rinterface_lib': ['callbacks']}],
'h5py': [],
'tables': []
}
Expand All @@ -18,29 +21,36 @@
class AliasModule(object):

def __init__(self, name, members=None):
# easy access to AliasModule members to avoid recursionerror
super_setattr = super().__setattr__
if members is None:
members = []
# easy access to AliasModule members to avoid recursionerror
self.__module_name__ = name
self.__module_members__ = members
# always import these members if they exist
self.__implicit_members__ = [
'__version__', '__warning_registry__', '__file__',
'__loader__', '__path__', '__doc__', '__package__']
self.__loaded__ = False
super_setattr('__module_name__', name)
super_setattr('__module_members__', members)
super_setattr('__loaded__', False)
# create submodules
submodules = []
for member in members:
if isinstance(member, dict):
for submodule, submembers in member.items():
setattr(self, submodule, AliasModule(
super_setattr(submodule, AliasModule(
"{}.{}".format(name, submodule), submembers))
submodules.append(submodule)
else:
setattr(self, member, AliasModule(
"{}.{}".format(name, member)))
super_setattr(member, AliasModule(
"{}.{}".format(name, member)))
submodules.append(member)
setattr(self, "__submodules__", submodules)
super_setattr("__submodules__", submodules)

@property
def __loaded_module__(self):
# easy access to AliasModule members to avoid recursionerror
super_getattr = super().__getattribute__
if not super_getattr("__loaded__"):
# module hasn't been imported yet
importlib.import_module(super_getattr("__module_name__"))
# access lazy loaded member from loaded module
return sys.modules[super_getattr("__module_name__")]

def __getattribute__(self, attr):
# easy access to AliasModule members to avoid recursionerror
Expand All @@ -50,11 +60,14 @@ def __getattribute__(self, attr):
return super_getattr(attr)
else:
# accessing an unknown member
if not super_getattr("__loaded__"):
# module hasn't been imported yet
importlib.import_module(super_getattr("__module_name__"))
# access lazy loaded member from loaded module
return getattr(sys.modules[super_getattr("__module_name__")], attr)
return getattr(super_getattr("__loaded_module__"), attr)

def __setattr__(self, name, value):
# allows monkey-patching
# easy access to AliasModule members to avoid recursionerror
super_getattr = super().__getattribute__
return setattr(super_getattr("__loaded_module__"), name, value)


# load required aliases into global namespace
Expand Down
2 changes: 2 additions & 0 deletions scprep/run/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .r_function import RFunction
from .splatter import SplatSimulate
85 changes: 85 additions & 0 deletions scprep/run/r_function.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import numpy as np

from .. import utils
from .._lazyload import rpy2


loud_console_warning = rpy2.rinterface_lib.callbacks.consolewrite_warnerror


def quiet_console_warning(s: str) -> None:
rpy2.rinterface_lib.callbacks.logger.warning(
rpy2.rinterface_lib.callbacks._WRITECONSOLE_EXCEPTION_LOG, s.strip())


class RFunction(object):
"""Run an R function from Python
"""

def __init__(self, name, args, setup, body, quiet_setup=True):
self.name = name
self.args = args
self.setup = setup
self.body = body
if quiet_setup:
self.setup = """
suppressPackageStartupMessages(suppressMessages(
suppressWarnings({{
{setup}
}})))""".format(setup=self.setup)

@utils._with_pkg(pkg="rpy2", min_version="2-3")
def _build(self):
function_text = """
{setup}
{name} <- function({args}) {{
{body}
}}
""".format(setup=self.setup, name=self.name,
args=self.args, body=self.body)
fun = getattr(rpy2.robjects.packages.STAP(
function_text, self.name), self.name)
rpy2.robjects.numpy2ri.activate()
return fun

@property
def function(self):
try:
return self._function
except AttributeError:
self._function = self._build()
return self._function

def is_r_object(self, obj):
return "rpy2.robjects" in str(type(obj)) or obj is rpy2.rinterface.NULL

@utils._with_pkg(pkg="rpy2", min_version="2-3")
def convert(self, robject):
if self.is_r_object(robject):
if isinstance(robject, rpy2.robjects.vectors.ListVector):
names = self.convert(robject.names)
if names is rpy2.rinterface.NULL or \
len(names) > len(np.unique(names)):
# list
robject = [self.convert(obj) for obj in robject]
else:
# dictionary
robject = {name: self.convert(
obj) for name, obj in zip(robject.names, robject)}
else:
# try numpy first
robject = rpy2.robjects.numpy2ri.rpy2py(robject)
if self.is_r_object(robject):
# try regular conversion
robject = rpy2.robjects.conversion.rpy2py(robject)
if robject is rpy2.rinterface.NULL:
robject = None
return robject

def __call__(self, *args, **kwargs):
# monkey patch warnings
rpy2.rinterface_lib.callbacks.consolewrite_warnerror = quiet_console_warning
robject = self.function(*args, **kwargs)
robject = self.convert(robject)
rpy2.rinterface_lib.callbacks.consolewrite_warnerror = loud_console_warning
return robject
Loading

0 comments on commit 9f0a0ce

Please sign in to comment.