diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..4cd0416f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +ipython +nose diff --git a/solvebio/__init__.py b/solvebio/__init__.py index bd841f20..f37ab07c 100644 --- a/solvebio/__init__.py +++ b/solvebio/__init__.py @@ -68,9 +68,7 @@ def emit(self, record): from . import version from .errors import SolveError -from .resource import (User, Depository, DepositoryVersion, Dataset, # noqa - DatasetField, Annotation, Sample) -from .query import Query, PagingQuery, BatchQuery, Filter, RangeFilter # noqa +from .query import Query, PagingQuery, Filter, RangeFilter __all__ = ['version', 'Annotation' diff --git a/solvebio/resource/__init__.py b/solvebio/resource/__init__.py index 2ac43d12..a8db045f 100644 --- a/solvebio/resource/__init__.py +++ b/solvebio/resource/__init__.py @@ -1,13 +1,14 @@ """Glues together all SolveObjects""" -# For the future... -# from Dataset import Dataset -# from DatasetField import DatasetField -from depository import Depository -from depositoryversion import DepositoryVersion -from resource import Dataset, User, \ - ListObject, DatasetField +# __package__ = 'solvebio.resource' + +from .depository import Depository +from .depositoryversion import DepositoryVersion +from .resource import ListObject from .annotation import Annotation -from .sample import Sample +from .sample import Sample +from .user import User +from .dataset import Dataset +from .datasetfield import DatasetField types = { diff --git a/solvebio/resource/dataset.py b/solvebio/resource/dataset.py new file mode 100644 index 00000000..29d218ee --- /dev/null +++ b/solvebio/resource/dataset.py @@ -0,0 +1,77 @@ +"""Dataset Class""" +import re + +# __package__ = 'solvebio.resource' +from ..client import client +from ..help import open_help +from ..query import Query, PagingQuery + +from .solveobject import convert_to_solve_object +from .resource import CreateableAPIResource, ListableAPIResource, \ + UpdateableAPIResource +from .datasetfield import DatasetField + + +class Dataset(CreateableAPIResource, ListableAPIResource, + UpdateableAPIResource): + """ + Datasets are access points to data. Dataset names are unique + within versions of a depository. + """ + ALLOW_FULL_NAME_ID = True + FULL_NAME_REGEX = r'^([\w\d\-\.]+/){2}[\w\d\-\.]+$' + + @classmethod + def retrieve(cls, id, **params): + """Supports lookup by full name""" + if isinstance(id, unicode) or isinstance(id, str): + _id = unicode(id).strip() + id = None + if re.match(cls.FULL_NAME_REGEX, _id): + params.update({'full_name': _id}) + else: + raise Exception('Unrecognized full name.') + + return super(Dataset, cls).retrieve(id, **params) + + def depository_version(self): + from .depositoryversion import DepositoryVersion + return DepositoryVersion.retrieve(self['depository_version']) + + def depository(self): + from .depository import Depository + return Depository.retrieve(self['depository']) + + def fields(self, name=None, **params): + if 'fields_url' not in self: + raise Exception( + 'Please use Dataset.retrieve({ID}) before doing looking ' + 'up fields') + + if name: + # construct the field's full_name if a field name is provided + return DatasetField.retrieve( + '/'.join([self['full_name'], name])) + + response = client.request('get', self.fields_url, params) + return convert_to_solve_object(response) + + def _data_url(self): + if 'data_url' not in self: + if 'id' not in self or not self['id']: + raise Exception( + 'No Dataset ID was provided. ' + 'Please instantiate the Dataset ' + 'object with an ID or full_name.') + # automatically construct the data_url from the ID + return self.instance_url() + u'/data' + return self['data_url'] + + def query(self, paging=False, **params): + self._data_url() # raises an exception if there's no ID + query_klass = PagingQuery if paging else Query + q = query_klass(self['id'], **params) + return q.filter(params.get('filters')) if params.get('filters') else q + + def help(self): + open_help(self['full_name']) diff --git a/solvebio/resource/datasetfield.py b/solvebio/resource/datasetfield.py new file mode 100644 index 00000000..c1a00521 --- /dev/null +++ b/solvebio/resource/datasetfield.py @@ -0,0 +1,39 @@ +"""DatasetField Class""" +import re + +from ..client import client + +from .solveobject import convert_to_solve_object +from .resource import CreateableAPIResource, ListableAPIResource, \ + UpdateableAPIResource + + +class DatasetField(CreateableAPIResource, ListableAPIResource, + UpdateableAPIResource): + """ + Each SolveBio dataset has a different set of fields, some of + which can be used as filters. Dataset field resources provide + users with documentation about each field. + """ + ALLOW_FULL_NAME_ID = True + FULL_NAME_REGEX = r'^([\w\d\-\.]+/){3}[\w\d\-\.]+$' + + @classmethod + def retrieve(cls, id, **params): + """Supports lookup by ID or full name""" + if isinstance(id, unicode) or isinstance(id, str): + _id = unicode(id).strip() + id = None + if re.match(cls.FULL_NAME_REGEX, _id): + params.update({'full_name': _id}) + else: + raise Exception('Unrecognized full name.') + + return super(DatasetField, cls).retrieve(id, **params) + + def facets(self, **params): + response = client.request('get', self.facets_url, params) + return convert_to_solve_object(response) + + def help(self): + return self.facets() diff --git a/solvebio/resource/depository.py b/solvebio/resource/depository.py index 72ce113a..713d7c54 100644 --- a/solvebio/resource/depository.py +++ b/solvebio/resource/depository.py @@ -1,10 +1,16 @@ """Depository Class""" - -from resource import CreateableAPIResource, ListableAPIResource, \ - SearchableAPIResource, UpdateableAPIResource -from solveobject import convert_to_solve_object +# __package__ = 'solvebio.resource' import re +from ..client import client +from ..help import open_help + +from .solveobject import convert_to_solve_object +from .resource import CreateableAPIResource, ListableAPIResource, \ + SearchableAPIResource, UpdateableAPIResource +from .depositoryversion import DepositoryVersion + + class Depository(CreateableAPIResource, ListableAPIResource, SearchableAPIResource, UpdateableAPIResource): """ diff --git a/solvebio/resource/depositoryversion.py b/solvebio/resource/depositoryversion.py index 6787e7b3..21ca3c6f 100644 --- a/solvebio/resource/depositoryversion.py +++ b/solvebio/resource/depositoryversion.py @@ -1,10 +1,15 @@ """DepositoryVersion Class""" - -from resource import CreateableAPIResource, ListableAPIResource, \ - UpdateableAPIResource -from solveobject import convert_to_solve_object import re +from ..client import client +from ..help import open_help + +from .solveobject import convert_to_solve_object +from .resource import CreateableAPIResource, ListableAPIResource, \ + UpdateableAPIResource +from .dataset import Dataset + + class DepositoryVersion(CreateableAPIResource, ListableAPIResource, UpdateableAPIResource): diff --git a/solvebio/resource/resource.py b/solvebio/resource/resource.py index c9c58103..3e7a6f16 100644 --- a/solvebio/resource/resource.py +++ b/solvebio/resource/resource.py @@ -1,18 +1,13 @@ # -*- coding: utf-8 -*- import urllib -import re from ..client import client -from ..query import Query, PagingQuery -from ..help import open_help from .util import class_to_api_name from .solveobject import SolveObject, convert_to_solve_object class APIResource(SolveObject): - # from pydbgr.api import debug; debug() - # types[self.__class__.__name__] = self @classmethod def retrieve(cls, id, **params): @@ -159,93 +154,3 @@ class DeletableAPIResource(APIResource): def delete(self, **params): self.refresh_from(self.request('delete', self.instance_url(), params)) return self - - -# API resources - -class User(SingletonAPIResource): - pass - -class Dataset(CreateableAPIResource, ListableAPIResource, - UpdateableAPIResource): - ALLOW_FULL_NAME_ID = True - FULL_NAME_REGEX = r'^([\w\d\-\.]+/){2}[\w\d\-\.]+$' - - @classmethod - def retrieve(cls, id, **params): - """Supports lookup by full name""" - if isinstance(id, unicode) or isinstance(id, str): - _id = unicode(id).strip() - id = None - if re.match(cls.FULL_NAME_REGEX, _id): - params.update({'full_name': _id}) - else: - raise Exception('Unrecognized full name.') - - return super(Dataset, cls).retrieve(id, **params) - - def depository_version(self): - return DepositoryVersion.retrieve(self['depository_version']) - - def depository(self): - return Depository.retrieve(self['depository']) - - def fields(self, name=None, **params): - if 'fields_url' not in self: - raise Exception( - 'Please use Dataset.retrieve({ID}) before doing looking ' - 'up fields') - - if name: - # construct the field's full_name if a field name is provided - return DatasetField.retrieve( - '/'.join([self['full_name'], name])) - - response = client.request('get', self.fields_url, params) - return convert_to_solve_object(response) - - def _data_url(self): - if 'data_url' not in self: - if 'id' not in self or not self['id']: - raise Exception( - 'No Dataset ID was provided. ' - 'Please instantiate the Dataset ' - 'object with an ID or full_name.') - # automatically construct the data_url from the ID - return self.instance_url() + u'/data' - return self['data_url'] - - def query(self, paging=False, **params): - self._data_url() # raises an exception if there's no ID - query_klass = PagingQuery if paging else Query - q = query_klass(self['id'], **params) - return q.filter(params.get('filters')) if params.get('filters') else q - - def help(self): - open_help(self['full_name']) - - -class DatasetField(CreateableAPIResource, ListableAPIResource, - UpdateableAPIResource): - ALLOW_FULL_NAME_ID = True - FULL_NAME_REGEX = r'^([\w\d\-\.]+/){3}[\w\d\-\.]+$' - - @classmethod - def retrieve(cls, id, **params): - """Supports lookup by ID or full name""" - if isinstance(id, unicode) or isinstance(id, str): - _id = unicode(id).strip() - id = None - if re.match(cls.FULL_NAME_REGEX, _id): - params.update({'full_name': _id}) - else: - raise Exception('Unrecognized full name.') - - return super(DatasetField, cls).retrieve(id, **params) - - def facets(self, **params): - response = client.request('get', self.facets_url, params) - return convert_to_solve_object(response) - - def help(self): - return self.facets() diff --git a/solvebio/resource/sample.py b/solvebio/resource/sample.py index fc96d2a9..5a9fbc04 100644 --- a/solvebio/resource/sample.py +++ b/solvebio/resource/sample.py @@ -38,7 +38,8 @@ def create(cls, genome_build, **params): """ if 'vcf_url' in params: if 'vcf_file' in params: - raise TypeError('Specified both vcf_url and vcf_file; use only one') + raise TypeError('Specified both vcf_url and vcf_file; ' + + 'use only one') return Sample.create_from_url(genome_build, params['vcf_url']) elif 'vcf_file' in params: return Sample.create_from_file(genome_build, params['vcf_file']) diff --git a/solvebio/resource/solveobject.py b/solvebio/resource/solveobject.py index a11a4679..a130aca7 100644 --- a/solvebio/resource/solveobject.py +++ b/solvebio/resource/solveobject.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- from ..client import client + from .util import json diff --git a/solvebio/resource/user.py b/solvebio/resource/user.py new file mode 100644 index 00000000..ba984f2f --- /dev/null +++ b/solvebio/resource/user.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +from .resource import SingletonAPIResource + + +class User(SingletonAPIResource): + pass diff --git a/solvebio/test/__init__.py b/solvebio/test/__init__.py index 0e682da5..072fdbe3 100644 --- a/solvebio/test/__init__.py +++ b/solvebio/test/__init__.py @@ -5,19 +5,27 @@ def all_names(): - for _, modname, _ in pkgutil.iter_modules(__path__): + for i, modname, j in pkgutil.iter_modules(__path__): + print i, j + print modname # FIXME: figure out why test loader can't resolve # solvebio.test.test_conversion - if modname == 'test_conversion': - continue - if modname.startswith('test_modify_'): - continue + # if modname in ('test_conversion', + # # 'test_annotation_access', + # # 'test_query', + # # 'test_query_batch', + # # 'test_query_paging', + # # 'test_sample_access', + # # 'test_sample', + # 'test_annotation'): + # continue if modname.startswith('test_'): yield 'solvebio.test.' + modname def all(): - return unittest.defaultTestLoader.loadTestsFromNames(all_names()) + names = [name for name in all_names()] + return unittest.defaultTestLoader.loadTestsFromNames(names) # def unit(): # unit_names = [name for name in all_names() if 'integration' not in name] diff --git a/solvebio/test/query_helper.py b/solvebio/test/query_helper.py index ba645f48..bac1f826 100644 --- a/solvebio/test/query_helper.py +++ b/solvebio/test/query_helper.py @@ -2,6 +2,7 @@ import os import re import unittest + import solvebio TEST_DATASET_NAME = 'ClinVar/2.0.0-1/Variants' diff --git a/solvebio/test/test_annotation.py b/solvebio/test/test_annotation.py index 1a4ea763..2fc823bb 100644 --- a/solvebio/test/test_annotation.py +++ b/solvebio/test/test_annotation.py @@ -1,7 +1,8 @@ import unittest import os -from solvebio import Annotation, SolveError +from solvebio.errors import SolveError +from solvebio.resource import Annotation class AnnotationTest(unittest.TestCase): diff --git a/solvebio/test/test_annotation_access.py b/solvebio/test/test_annotation_access.py index 523ad1a8..62f80c56 100644 --- a/solvebio/test/test_annotation_access.py +++ b/solvebio/test/test_annotation_access.py @@ -1,7 +1,7 @@ import unittest import os -from solvebio import Annotation +from solvebio.resource import Annotation @unittest.skipUnless('TEST_SOLVEBIO_API_UPDATE' in os.environ, diff --git a/solvebio/test/test_conversion.py b/solvebio/test/test_conversion.py index ba21e356..29987342 100644 --- a/solvebio/test/test_conversion.py +++ b/solvebio/test/test_conversion.py @@ -1,17 +1,17 @@ import unittest -import sys +# import sys -sys.path.insert(0, '..') -sys.path.insert(0, '.') -from conversion import class_to_api_name +# sys.path.insert(0, '..') +# sys.path.insert(0, '.') +from solvebio.resource.util import class_to_api_name class ConversionTest(unittest.TestCase): def test_class_to_api_name(self): for class_name, expect in [ - ('Annotation', 'annotations'), - ('DataField', 'data_fields'), - ('Depository', 'depositories')]: + ('Annotation', 'annotations'), + ('DataField', 'data_fields'), + ('Depository', 'depositories')]: self.assertEqual(class_to_api_name(class_name), expect) diff --git a/solvebio/test/test_query.py b/solvebio/test/test_query.py index 9fd6ba75..02929fb3 100644 --- a/solvebio/test/test_query.py +++ b/solvebio/test/test_query.py @@ -1,10 +1,10 @@ """Test Non-Paging Queries""" import unittest -import sys import os -sys.path.insert(0, '.') + from query_helper import SolveBioTestCase, TEST_DATASET_NAME -from solvebio import Dataset, Filter +from solvebio.resource import Dataset +from solvebio.query import Filter @unittest.skipIf('SOLVEBIO_API_HOST' in os.environ and diff --git a/solvebio/test/test_query_batch.py b/solvebio/test/test_query_batch.py index 10460b8b..b610a594 100644 --- a/solvebio/test/test_query_batch.py +++ b/solvebio/test/test_query_batch.py @@ -1,9 +1,9 @@ import unittest -import sys import os -sys.path.insert(0, '.') from query_helper import SolveBioTestCase, TEST_DATASET_NAME -from solvebio import Dataset, BatchQuery, SolveError +from solvebio.errors import SolveError +from solvebio.query import BatchQuery +from solvebio.resource import Dataset @unittest.skipIf('SOLVEBIO_API_HOST' in os.environ and diff --git a/solvebio/test/test_query_paging.py b/solvebio/test/test_query_paging.py index f2da31d3..8cfced5d 100644 --- a/solvebio/test/test_query_paging.py +++ b/solvebio/test/test_query_paging.py @@ -1,10 +1,8 @@ """Test Paging Queries""" import unittest -import sys import os -sys.path.insert(0, '.') from query_helper import SolveBioTestCase, TEST_DATASET_NAME -from solvebio import Dataset +from solvebio.resource import Dataset @unittest.skipIf('SOLVEBIO_API_HOST' in os.environ and diff --git a/solvebio/test/test_sample.py b/solvebio/test/test_sample.py index 9f12b969..269a7bb4 100644 --- a/solvebio/test/test_sample.py +++ b/solvebio/test/test_sample.py @@ -1,7 +1,8 @@ import unittest import os -from solvebio import Sample, SolveError +from solvebio.errors import SolveError +from solvebio.resource import Sample class SampleTest(unittest.TestCase): diff --git a/solvebio/test/test_sample_access.py b/solvebio/test/test_sample_access.py index 09d44ba5..00ee5aa7 100644 --- a/solvebio/test/test_sample_access.py +++ b/solvebio/test/test_sample_access.py @@ -1,7 +1,7 @@ import unittest import os -from solvebio import Sample +from solvebio.resource import Sample @unittest.skipUnless('TEST_SOLVEBIO_API_UPDATE' in os.environ,