Skip to content
This repository has been archived by the owner on Oct 28, 2022. It is now read-only.

Commit

Permalink
Add protocol.py
Browse files Browse the repository at this point in the history
  • Loading branch information
dcolligan committed Feb 7, 2017
1 parent 9714415 commit 8b89125
Show file tree
Hide file tree
Showing 4 changed files with 288 additions and 0 deletions.
Empty file.
31 changes: 31 additions & 0 deletions python/ga4gh/schemas/hacks/googhack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""
Hack for getting a handle to the top-level google module, etc.
"""
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

import google.protobuf.json_format as json_format
import google.protobuf.message as message
import google.protobuf.struct_pb2 as struct_pb2


def getJsonFormat():
"""
Returns the module google.protobuf.json_format
"""
return json_format


def getMessage():
"""
Returns the module google.protobuf.message
"""
return message


def getStructPb2():
"""
Returns the module google.protobuf.struct_pb2
"""
return struct_pb2
184 changes: 184 additions & 0 deletions python/ga4gh/schemas/protocol.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
"""
Definitions of the GA4GH protocol types.
"""
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

import datetime
import json
import inspect
import sys

from _protocol_version import version # noqa
from ga4gh.common_pb2 import * # noqa
from ga4gh.assay_metadata_pb2 import * # noqa
from ga4gh.metadata_pb2 import * # noqa
from ga4gh.metadata_service_pb2 import * # noqa
from ga4gh.read_service_pb2 import * # noqa
from ga4gh.reads_pb2 import * # noqa
from ga4gh.reference_service_pb2 import * # noqa
from ga4gh.references_pb2 import * # noqa
from ga4gh.variant_service_pb2 import * # noqa
from ga4gh.variants_pb2 import * # noqa
from ga4gh.allele_annotations_pb2 import * # noqa
from ga4gh.allele_annotation_service_pb2 import * # noqa
from ga4gh.sequence_annotations_pb2 import * # noqa
from ga4gh.sequence_annotation_service_pb2 import * # noqa
from ga4gh.bio_metadata_pb2 import * # noqa
from ga4gh.bio_metadata_service_pb2 import * # noqa
from ga4gh.genotype_phenotype_pb2 import * # noqa
from ga4gh.genotype_phenotype_service_pb2 import * # noqa
from ga4gh.rna_quantification_pb2 import * # noqa
from ga4gh.rna_quantification_service_pb2 import * # noqa

import hacks.googhack as googhack


# This is necessary because we have a package in the same directory as this
# file named 'google', so an 'import google' attempts to import that package
# instead of the top-level google package.
json_format = googhack.getJsonFormat()
message = googhack.getMessage()
struct_pb2 = googhack.getStructPb2()


def getValueListName(protocolResponseClass):
"""
Returns the name of the attribute in the specified protocol class
that is used to hold the values in a search response.
"""
return protocolResponseClass.DESCRIPTOR.fields_by_number[1].name


def convertDatetime(t):
"""
Converts the specified datetime object into its appropriate protocol
value. This is the number of milliseconds from the epoch.
"""
epoch = datetime.datetime.utcfromtimestamp(0)
delta = t - epoch
millis = delta.total_seconds() * 1000
return int(millis)


def getValueFromValue(value):
"""
Extract the currently set field from a Value structure
"""
if type(value) != struct_pb2.Value:
raise TypeError("Expected a Value, but got {}".format(type(value)))
if value.WhichOneof("kind") is None:
raise AttributeError("Nothing set for {}".format(value))
return getattr(value, value.WhichOneof("kind"))


def toJson(protoObject, indent=None):
"""
Serialises a protobuf object as json
"""
# Using the internal method because this way we can reformat the JSON
js = json_format.MessageToDict(protoObject, False)
return json.dumps(js, indent=indent)


def toJsonDict(protoObject):
"""
Converts a protobuf object to the raw attributes
i.e. a key/value dictionary
"""
return json.loads(toJson(protoObject))


def fromJson(json, protoClass):
"""
Deserialise json into an instance of protobuf class
"""
return json_format.Parse(json, protoClass())


def validate(json, protoClass):
"""
Check that json represents data that could be used to make
a given protobuf class
"""
try:
fromJson(json, protoClass)
# The json conversion automatically validates
return True
except Exception:
return False


def getProtocolClasses(superclass=message.Message):
"""
Returns all the protocol classes that are subclasses of the
specified superclass. Only 'leaf' classes are returned,
corresponding directly to the classes defined in the protocol.
"""
# We keep a manual list of the superclasses that we define here
# so we can filter them out when we're getting the protocol
# classes.
superclasses = set([message.Message])
thisModule = sys.modules[__name__]
subclasses = []
for name, class_ in inspect.getmembers(thisModule):
if ((inspect.isclass(class_) and
issubclass(class_, superclass) and
class_ not in superclasses)):
subclasses.append(class_)
return subclasses


postMethods = \
[('/callsets/search',
SearchCallSetsRequest, # noqa
SearchCallSetsResponse), # noqa
('/datasets/search',
SearchDatasetsRequest, # noqa
SearchDatasetsResponse), # noqa
('/readgroupsets/search',
SearchReadGroupSetsRequest, # noqa
SearchReadGroupSetsResponse), # noqa
('/reads/search',
SearchReadsRequest, # noqa
SearchReadsResponse), # noqa
('/references/search',
SearchReferencesRequest, # noqa
SearchReferencesResponse), # noqa
('/referencesets/search',
SearchReferenceSetsRequest, # noqa
SearchReferenceSetsResponse), # noqa
('/variants/search',
SearchVariantsRequest, # noqa
SearchVariantsResponse), # noqa
('/datasets/search',
SearchDatasetsRequest, # noqa
SearchDatasetsResponse), # noqa
('/callsets/search',
SearchCallSetsRequest, # noqa
SearchCallSetsResponse), # noqa
('/featuresets/search',
SearchFeatureSetsRequest, # noqa
SearchFeatureSetsResponse), # noqa
('/features/search',
SearchFeaturesRequest, # noqa
SearchFeaturesResponse), # noqa
('/variantsets/search',
SearchVariantSetsRequest, # noqa
SearchVariantSetsResponse), # noqa
('/variantannotations/search',
SearchVariantAnnotationsRequest, # noqa
SearchVariantAnnotationSetsResponse), # noqa
('/variantannotationsets/search',
SearchVariantAnnotationSetsRequest, # noqa
SearchVariantAnnotationSetsResponse), # noqa
('/rnaquantificationsets/search',
SearchRnaQuantificationSetsRequest, # noqa
SearchRnaQuantificationSetsResponse), # noqa
('/rnaquantifications/search',
SearchRnaQuantificationsRequest, # noqa
SearchRnaQuantificationsResponse), # noqa
('/expressionlevels/search',
SearchExpressionLevelsRequest, # noqa
SearchExpressionLevelsResponse)] # noqa
73 changes: 73 additions & 0 deletions tests/test_protocol.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""
Tests for protocol.py
"""
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

import datetime
import unittest

import google.protobuf.message as message
import google.protobuf.struct_pb2 as struct_pb2

import ga4gh.schemas.protocol as protocol


class TestProtocol(unittest.TestCase):

# TODO this suite is not very sophisticated right now due to
# using empty instead of instantiated protobuf classes to test various
# aspects of the protocol module

def testGetValueListName(self):
for clazz in protocol.getProtocolClasses():
self.assertIsInstance(protocol.getValueListName(clazz), str)

def testConvertDatetime(self):
datetime_ = datetime.datetime.fromordinal(1234567)
converted = protocol.convertDatetime(datetime_)
self.assertEqual(converted, 44530905600000)

def testGetValueFromValue(self):
with self.assertRaises(TypeError):
protocol.getValueFromValue(5)
val = struct_pb2.Value()
with self.assertRaises(AttributeError):
protocol.getValueFromValue(val)
# TODO add positive test

def testToJsonAndFromJson(self):
classes = protocol.getProtocolClasses()
for clazz in classes:
obj = clazz()
jsonStr = protocol.toJson(obj)
obj2 = protocol.fromJson(jsonStr, clazz)
self.assertTrue(obj, obj2)

def testToJsonDict(self):
classes = protocol.getProtocolClasses()
for clazz in classes:
obj = clazz()
jsonDict = protocol.toJsonDict(obj)
self.assertIsInstance(jsonDict, dict)

def testValidate(self):
classes = protocol.getProtocolClasses()
for clazz in classes:
obj = clazz()
jsonStr = protocol.toJson(obj)
protocol.validate(jsonStr, clazz)

def testGetProtocolClasses(self):
classes = protocol.getProtocolClasses()
self.assertGreater(len(classes), 0)
for clazz in classes:
self.assertTrue(issubclass(clazz, message.Message))

def testPostMethods(self):
for postMethod in protocol.postMethods:
self.assertEqual(len(postMethod), 3)
self.assertIsInstance(postMethod[0], unicode)
self.assertTrue(issubclass(postMethod[1], message.Message))
self.assertTrue(issubclass(postMethod[2], message.Message))

0 comments on commit 8b89125

Please sign in to comment.