From 25bb0394e480bf40adfdcfad24e45c3e161db078 Mon Sep 17 00:00:00 2001 From: tef Date: Wed, 3 Oct 2012 20:37:32 +0100 Subject: [PATCH] moving py to submodule --- .gitmodules | 3 + Makefile | 2 +- py | 1 + py/hyperglyph/__init__.py | 43 --- py/hyperglyph/data.py | 363 --------------------- py/hyperglyph/encoding.py | 407 ------------------------ py/hyperglyph/resource/__init__.py | 0 py/hyperglyph/resource/base.py | 176 ----------- py/hyperglyph/resource/handler.py | 159 ---------- py/hyperglyph/resource/persistent.py | 34 -- py/hyperglyph/resource/router.py | 112 ------- py/hyperglyph/resource/transient.py | 42 --- py/hyperglyph/server.py | 40 --- py/requirements.txt | 5 - py/setup.py | 21 -- py/tests/__init__.py | 450 --------------------------- 16 files changed, 5 insertions(+), 1853 deletions(-) create mode 100644 .gitmodules create mode 160000 py delete mode 100644 py/hyperglyph/__init__.py delete mode 100644 py/hyperglyph/data.py delete mode 100644 py/hyperglyph/encoding.py delete mode 100644 py/hyperglyph/resource/__init__.py delete mode 100644 py/hyperglyph/resource/base.py delete mode 100644 py/hyperglyph/resource/handler.py delete mode 100644 py/hyperglyph/resource/persistent.py delete mode 100644 py/hyperglyph/resource/router.py delete mode 100644 py/hyperglyph/resource/transient.py delete mode 100644 py/hyperglyph/server.py delete mode 100644 py/requirements.txt delete mode 100644 py/setup.py delete mode 100644 py/tests/__init__.py diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..6e1b644 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "py"] + path = py + url = git@github.com:hyperglyph/hyperglyph-python.git diff --git a/Makefile b/Makefile index f7d74ac..6b36914 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ RUBY?=ruby test: python_test ruby_test python_test: py/ - python py/setup.py test + cd py && python setup.py test ruby_test: rb/ rm -f rb/hyperglyph*.gem diff --git a/py b/py new file mode 160000 index 0000000..ab5f5a8 --- /dev/null +++ b/py @@ -0,0 +1 @@ +Subproject commit ab5f5a86140198c8b383be1beb11dd16e8f352c8 diff --git a/py/hyperglyph/__init__.py b/py/hyperglyph/__init__.py deleted file mode 100644 index b931faf..0000000 --- a/py/hyperglyph/__init__.py +++ /dev/null @@ -1,43 +0,0 @@ -""" hyperglyph - duck typed ipc """ -""" -Copyright (c) 2012 Thomas Figg (tef) -Copyright (c) 2011-2012 Hanzo Archives Ltd - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -""" - - -from .data import CONTENT_TYPE, dump, dump_iter, parse, get, form, link, utcnow, embedlink, error -from .encoding import blob -from .resource.handler import safe, embed, redirect, hidden -from .resource.transient import TransientResource -from .resource.persistent import PersistentResource -from .resource.base import ClassMapper -from .resource.router import Router -from .server import Server - -__all__ = [ - 'CONTENT_TYPE','Server','redirect','hidden', - 'get', 'Router','Resource','r', 'safe', 'embed', 'utcnow', - 'parse','dump','form','link','embedlink', 'blob', 'error', - 'TransientResource', 'PersistentResource', - 'ClassMapper', -] - -r = Resource = TransientResource - diff --git a/py/hyperglyph/data.py b/py/hyperglyph/data.py deleted file mode 100644 index 947e694..0000000 --- a/py/hyperglyph/data.py +++ /dev/null @@ -1,363 +0,0 @@ -from urlparse import urljoin -import datetime -import io -import requests -import requests.exceptions -import collections -import socket -from urlparse import urljoin - -from pytz import utc - -from .encoding import Encoder, CONTENT_TYPE, Blob - -HEADERS={'Accept': CONTENT_TYPE, 'Content-Type': CONTENT_TYPE} -CHUNKED = False # wsgiref is terrible - enable by default when the default wsgi server works - -session = requests.session() - -# Fix for earlier versions of requests. -# Todo: Replace requests exceptions with specific glyph exceptions - -if not issubclass(requests.exceptions.RequestException, RuntimeError): - requests.exceptions.RequestException.__bases__ = (RuntimeError,) - - -def utcnow(): - return datetime.datetime.utcnow().replace(tzinfo=utc) - - -def robj(obj, contents): - return Extension.__make__(u'resource', {u'name':unicode(obj.__class__.__name__), u'url': obj}, contents) - -def form(url, method=u'POST',values=None): - if values is None: - if ismethod(url): - values = methodargs(url) - elif isinstance(url, type): - values = methodargs(url.__init__) - elif callable(url): - values = funcargs(url) - - if values is not None: - values = [form_input(v) for v in values] - - return Extension.__make__(u'form', {u'method':method, u'url':url, u'values':values}, None) - -def link(url, method='GET'): - return Extension.__make__(u'link', {u'method':method, u'url':url}, None) - -def embedlink(url, content, method=u'GET'): - return Extension.__make__(u'link', {u'method':method, u'url':url, u'inline':True}, content) - -def error(reference, message): - return Extension.__make__(u'error', {u'logref':unicode(reference), u'message':message}, {}) - -def form_input(name): - #return unicode(name) - return Extension.__make__(u'input', {u'name':unicode(name)}, None) - - -# move to inspect ? - -def ismethod(m, cls=None): - return callable(m) and hasattr(m,'im_self') and (cls is None or isinstance(m.im_self, cls)) - -def methodargs(m): - if ismethod(m): - return m.func_code.co_varnames[1:m.func_code.co_argcount] - -def funcargs(m): - return m.func_code.co_varnames[:m.func_code.co_argcount] - -def get(url, args=None, headers=None): - if hasattr(url, u'url'): - url = url.url() - return fetch('GET', url, args, None, headers) - - -# wow, transfer-chunked encoding is ugly. - -class chunk_fh(object): - def __init__(self, data): - self.g = data - self.state = 0 - self.buf = io.BytesIO() - - def read(self,size=-1): - """ we need to conform to the defintion of read, - don't return pieces bigger than size, and return all when size =< 0 - """ - if size > 0: - while self.buf.tell() < size: - if not self.read_chunk(size): - break - else: - while self.read_chunk(size): - pass - - self.buf.seek(0) - ret = self.buf.read(size) - tail = self.buf.read() - self.buf.seek(0) - self.buf.truncate(0) - self.buf.write(tail) - - return ret - - def read_chunk(self, size): - chunk_size = max(1, size-4-len("%s"%size)) if size > 0 else size - - try: - if self.state > 0: - chunk = self.g.send(chunk_size) - elif self.state == 0: - self.g = dump_iter(self.g, chunk_size=chunk_size) - chunk = self.g.next() - self.state = 1 - elif self.state < 0: - return False - - self.buf.write("%X\r\n%s\r\n"%(len(chunk), chunk)) - except (GeneratorExit, StopIteration): - self.state = -1 - self.buf.write("0\r\n\r\n") - - return self.state > 0 - -def fetch(method, url, args=None, data=None, headers=None): - h = dict(HEADERS) - if headers: - h.update(headers) - headers = h - - if args is None: - args = {} - if data is not None: - if CHUNKED: - headers["Transfer-Encoding"] = "chunked" - data = chunk_fh(data) - else: - data = dump(data) - result = session.request(method, url, params=args, data=data, headers=headers, allow_redirects=False, timeout=socket.getdefaulttimeout()) - def join(u): - return urljoin(result.url, u) - if result.status_code == 303: # See Other - return get(join(result.headers['Location'])) - elif result.status_code == 204: # No Content - return None - elif result.status_code == 201: # - # never called - return link(join(result.headers['Location'])) - - result.raise_for_status() - data = result.content - if result.headers['Content-Type'].startswith(CONTENT_TYPE): - data = parse(data, base_url=result.url) - else: - attr = dict(((unicode(k).lower(), v) for k,v in result.headers.iteritems())) - data = Blob(data, attr) - - return data - - -class BaseNode(object): - def __init__(self, name, attributes, content): - self._name = name - self._attributes = attributes - self._content = content - def __getstate__(self): - return self._name, self._attributes, self._content - - def __setstate__(self, state): - self._name = state[0] - self._attributes = state[1] - self._content = state[2] - - def __eq__(self, other): - return self._name == other._name and self._attributes == other._attributes and self._content == other._content - - @classmethod - def __make__(cls, name, attributes, content): - return cls(name,attributes, content) - - def __resolve__(self, resolver): - pass - - @staticmethod - def __rebase__(name, attr, base_url): - return attr, base_url - -class Extension(BaseNode): - _exts = {} - @classmethod - def __make__(cls, name, attributes, content): - ext = cls._exts[name] - return ext(name,attributes, content) - - @classmethod - def register(cls, name): - def _decorator(fn): - cls._exts[name] = fn - return fn - return _decorator - - def __eq__(self, other): - return isinstance(other, Extension) and BaseNode.__eq__(self, other) - - def __repr__(self): - return ''%(self._name, repr(self._attributes), repr(self._content)) - - @staticmethod - def __rebase__(name, attr, base_url): - if u'url' in attr: #and name in (u"form", u"link", u"resource", u"error"): - attr[u'url'] = urljoin(base_url, attr[u'url']) - return attr, attr[u'url'] - else: - return attr, base_url - -@Extension.register('form') -class Form(Extension): - HEADERS = set([ 'If-None-Match', 'Accept', 'If-Match', 'If-Unmodified-Since', 'If-Modified-Since']) - def __call__(self, *args, **kwargs): - headers = self._attributes.get(u'headers', {}) - headers = dict(((unicode(k),unicode(v)) for k,v in headers.iteritems() if k in self.HEADERS)) - - - url = self._attributes[u'url'] - - parameters = collections.OrderedDict() - - # convert all inputs to a ord-dict of form inputs - if self._attributes[u'values']: - for n in self._attributes[u'values']: - if isinstance(n, Input): - parameters[n.name] = n - else: - parameters[n] = form_input(n) - - - # build the form arguments - data = collections.OrderedDict() - - if parameters: - args = list(args) - kwargs = dict(kwargs) - - for p,i in parameters.iteritems(): - if args: - data[p] = i.convert(args.pop(0)) - elif p in kwargs: - data[p] = i.convert(kwargs.pop(p)) - elif i.has_default(): - data[p] = i.default() - else: - raise TypeError('argument %s missing'%p) - if args: - raise TypeError('function passed %d extra parameter(s)'%len(args)) - elif kwargs: - raise TypeError('function passed %d extra named parameter(s)'%len(kwargs)) - - elif args or kwargs: - raise TypeError('function takes 0 arguments') - - headers = {} - - #data = [(k,v) for k,v in data.iteritems()] - - method = self._attributes.get(u'envelope', u'POST') - default_envelope = { - u'GET': u'query', - u'POST': u'form', - u'PUT': u'blob', - u'PATCH': u'blob', - u'DELETE': u'none', - }[method] - - envelope = self._attributes.get(u'envelope', default_envelope) - - if envelope == u'form' and method != u'GET': - return fetch(method, url, data=data) - elif envelope == u'none' and method not in [u'PUT', u'PATCH']: - return fetch(method, url) - elif envelope == u'query' and method not in [u'POST', u'PUT', u'PATCH']: - return fetch(method, url, args=data) - elif envelope == u'blob' and method not in [u'GET'] and len(data) == 1: - data = list(data.values())[0] - return fetch(method, url, data=data) - else: - raise StandardError('unsupported method/envelope/input combination') - - def __resolve__(self, resolver): - self._attributes[u'url'] = unicode(resolver(self._attributes[u'url'])) - -@Extension.register('link') -class Link(Extension): - HEADERS = set(['Accept']) - def __call__(self, *args, **kwargs): - headers = self._attributes.get(u'headers', {}) - headers = dict(((unicode(k),unicode(v)) for k,v in headers.iteritems() if k in self.HEADERS)) - - if self._attributes.get(u'inline', False): - return self._content - else: - url = self._attributes[u'url'] - return fetch(self._attributes.get(u'method',u'GET'),url, headers=headers) - - def url(self): - return self._attributes[u'url'] - - def __resolve__(self, resolver): - self._attributes[u'url'] = unicode(resolver(self._attributes[u'url'])) - - - -@Extension.register('resource') -class Resource(Extension): - - def __resolve__(self, resolver): - self._attributes[u'url'] = unicode(resolver(self._attributes[u'url'])) - - def __getattr__(self, name): - try: - return self._content[name] - except KeyError: - raise AttributeError(name) - - -@Extension.register('error') -class Error(Extension): - @property - def message(self): - return self._attributes[u'message'] - - @property - def logref(self): - return self._attributes[u'logref'] - - -@Extension.register('input') -class Input(Extension): - @property - def name(self): - return self._attributes[u'name'] - - def convert(self, value): - return value - - def has_default(self): - return u'value' in self._attributes - - def default(self): - return self._attributes[u'value'] - -@Extension.register('collection') -class Collection(Extension): - pass -_encoder = Encoder(extension=Extension) - -dump = _encoder.dump -dump_iter = _encoder.dump_iter -dump_buf = _encoder.dump_buf -parse = _encoder.parse -read = _encoder.read diff --git a/py/hyperglyph/encoding.py b/py/hyperglyph/encoding.py deleted file mode 100644 index 5d56ca0..0000000 --- a/py/hyperglyph/encoding.py +++ /dev/null @@ -1,407 +0,0 @@ -from urlparse import urljoin -from datetime import datetime, timedelta -import collections -import io -import os -import itertools -import operator -import tempfile - -from pytz import utc - -try: - # Code for older deployments that do not have isodate - from isodate import duration_isoformat, parse_duration -except ImportError: - # todo? consider replacing isodate - def duration_isoformat(dt): - raise NotImplementedError() - def parse_duration(string): - raise NotImplementedError() - - -CONTENT_TYPE='application/vnd.hyperglyph' - -UNICODE_CHARSET="utf-8" - -BSTR='b' -UNI='u' -LEN_SEP=':' -END_ITEM=';' -END_NODE = END_EXT = END_DICT = END_LIST = END_SET = END_ITEM - -FLT='f' -NUM='i' -DTM='d' -PER='p' - -DICT='D' -ODICT='O' -LIST='L' -SET='S' - -TRUE='T' -FALSE='F' -NONE='N' - -EXT='X' -BLOB = 'B' -CHUNK = 'c' - -def blob(content, content_type=u"application/octet-stream"): - if isinstance(content, unicode): - content = io.StringIO(content) - content_type = "text/plain; charset=utf-8" - elif isinstance(content, str): - content = io.BytesIO(content) - content_type = "text/plain" - return Blob(content, {u'content-type':content_type,}) - -class Blob(object): - def __init__(self, content, attributes): - self._attributes = attributes - self.fh = content - - @property - def content_type(self): - return self._attributes[u'content-type'] - - def __getattr__(self, attr): - return getattr(self.__dict__["fh"], attr) - - -identity = lambda x:x - -def fail(): - raise StandardError() - - -def _read_until(fh, term, parse=identity, skip=None): - c = fh.read(1) - buf = io.BytesIO() - while c not in term and c != skip: - buf.write(c) - c = fh.read(1) - if c in term: - d = parse(buf.getvalue()) - return d, c - else: - return None, c - - -def read_first(fh): - c = fh.read(1) - while c in ('\r','\v','\n',' ','\t'): - c = fh.read(1) - return c - - -class Encoder(object): - def __init__(self, extension, **kwargs): - self.extension = extension - self.max_blob_mem_size = kwargs.get("max_blob_mem_size", 1024*1024*2) - - def temp_file(self): - return tempfile.SpooledTemporaryFile(max_size=self.max_blob_mem_size) - - def dump(self, obj, resolver=identity, inline=fail): - return self.dump_buf(obj, resolver, inline).read() - - def dump_buf(self, obj, resolver=identity, inline=fail): - buf = io.BytesIO() - for chunk in self._dump(obj, resolver, inline): - buf.write(chunk) - buf.seek(0) - return buf - - def dump_iter(self, obj, chunk_size=-1, resolver=identity, inline=fail): - buf = io.BytesIO() - for chunk in self._dump(obj, resolver, inline): - buf.write(chunk) - - if chunk_size > 0 and buf.tell() > chunk_size: - buf.seek(0) - new_chunk_size = yield buf.read(chunk_size) - if new_chunk_size: - chunk_size = new_chunk_size - tail = buf.read() - buf.seek(0) - buf.truncate(0) - buf.write(tail) - - if buf.tell(): - yield buf.getvalue() - - - - def _dump(self, obj, resolver, inline): - blobs = [] - for o in self._dump_one(obj, resolver, inline, blobs): - yield o - for o in self._dump_blobs(blobs): - yield o - - def _dump_one(self, obj, resolver, inline, blobs): - if obj is True: - yield TRUE - yield END_ITEM - - elif obj is False: - yield FALSE - yield END_ITEM - - elif obj is None: - yield NONE - yield END_ITEM - - elif isinstance(obj, (self.extension,)): - yield EXT - name, attributes, content = obj.__getstate__() - obj.__resolve__(resolver) - for r in self._dump_one(name, resolver, inline, blobs): - yield r - for r in self._dump_one(attributes, resolver, inline, blobs): - yield r - for r in self._dump_one(content, resolver, inline, blobs): - yield r - yield END_EXT - - elif isinstance(obj, (str, buffer)): - yield BSTR - if len(obj) > 0: - yield "%d" % len(obj) - yield LEN_SEP - yield str(obj) - yield END_ITEM - - elif isinstance(obj, unicode): - yield UNI - obj = obj.encode(UNICODE_CHARSET) - if len(obj) > 0: - yield "%d" % len(obj) - yield LEN_SEP - yield obj - yield END_ITEM - - elif isinstance(obj, set): - yield SET - for x in sorted(obj): - for r in self._dump_one(x, resolver, inline, blobs): yield r - yield END_SET - elif hasattr(obj, 'iteritems'): - if isinstance(obj, collections.OrderedDict): - yield ODICT - else: - yield DICT - for k in sorted(obj.keys()): # always sorted, so can compare serialized - v=obj[k] - for r in self._dump_one(k, resolver, inline, blobs): yield r - for r in self._dump_one(v, resolver, inline, blobs): yield r - yield END_DICT - elif isinstance(obj, Blob): - blob_id = len(blobs) - blobs.append(obj.fh) - yield BLOB - yield str(blob_id) - yield LEN_SEP - for r in self._dump_one(obj._attributes, resolver, inline, blobs): - yield r - yield END_ITEM - elif hasattr(obj, '__iter__'): - yield LIST - for x in obj: - for r in self._dump_one(x, resolver, inline, blobs): yield r - yield END_LIST - elif isinstance(obj, (int, long)): - yield NUM - yield str(obj) - yield END_ITEM - elif isinstance(obj, float): - yield FLT - obj = float.hex(obj) - yield obj - yield END_ITEM - elif isinstance(obj, datetime): - yield DTM - obj = obj.astimezone(utc) - yield obj.strftime("%Y-%m-%dT%H:%M:%S.%fZ") - yield END_ITEM - elif isinstance(obj, timedelta): - yield PER - yield duration_isoformat(obj) - yield END_ITEM - - else: - for r in self._dump_one(inline(obj), resolver, inline, blobs): yield r - - def _dump_blobs(self, blobs): - for idx, b in enumerate(blobs): - while True: - data = b.read(8192) - if not data: - break - yield CHUNK - yield str(idx) - yield LEN_SEP - yield str(len(data)) - yield LEN_SEP - yield data - yield END_ITEM - yield CHUNK - yield str(idx) - yield END_ITEM - - - def parse(self, stream, base_url=None): - if not hasattr(stream, "read"): - stream = io.BytesIO(stream) - return self.read(stream, base_url) - - def read(self, fh, base_url=None): - blobs = {} - first = read_first(fh) - if first == '': - raise EOFError() - result = self._read_one(fh, first, blobs, base_url) - self._read_blobs(fh, blobs) - return result - - def _read_one(self, fh, c, blobs, base_url=None): - if c == NONE: - _read_until(fh, END_ITEM) - return None - elif c == TRUE: - _read_until(fh, END_ITEM) - return True - elif c == FALSE: - _read_until(fh, END_ITEM) - return False - if c == BSTR or c == UNI: - size, first = _read_until(fh, LEN_SEP, parse=int, skip=END_ITEM) - if first == LEN_SEP: - buf= fh.read(size) - first = read_first(fh) - else: - buf = b'' - - if c == UNI: - buf=buf.decode(UNICODE_CHARSET) - if first == END_ITEM: - return buf - else: - raise StandardError('error') - - elif c == NUM: - return _read_until(fh, END_ITEM, parse=int)[0] - - elif c == FLT: - f = _read_until(fh, END_ITEM)[0] - if 'x' in f: - return float.fromhex(f) - else: - return float(f) - - elif c == SET: - first = read_first(fh) - out = set() - while first != END_SET: - item = self._read_one(fh, first, blobs, base_url) - if item not in out: - out.add(item) - else: - raise StandardError('duplicate key') - first = read_first(fh) - return out - - elif c == LIST: - first = read_first(fh) - out = [] - while first != END_LIST: - out.append(self._read_one(fh, first, blobs, base_url)) - first = read_first(fh) - return out - - elif c == DICT or c == ODICT: - first = read_first(fh) - if c == ODICT: - out = collections.OrderedDict() - else: - out = {} - while first != END_DICT: - f = self._read_one(fh, first, blobs, base_url) - second = read_first(fh) - g = self._read_one(fh, second, blobs, base_url) - new = out.setdefault(f,g) - if new is not g: - raise StandardError('duplicate key') - first = read_first(fh) - return out - elif c == EXT: - first = read_first(fh) - name = self._read_one(fh, first, blobs, base_url) - first = read_first(fh) - attr = self._read_one(fh, first, blobs, base_url) - - attr, new_base = self.extension.__rebase__(name, attr, base_url ) - - first = read_first(fh) - content = self._read_one(fh, first, blobs, new_base) - - first = read_first(fh) - if first != END_EXT: - raise StandardError('ext') - - ext= self.extension.__make__(name, attr, content) - return ext - elif c == PER: - period = _read_until(fh, END_ITEM)[0] - return parse_duration(period) - elif c == DTM: - datestring = _read_until(fh, END_ITEM)[0] - if datestring[-1].lower() == 'z': - if '.' in datestring: - datestring, sec = datestring[:-1].split('.') - date = datetime.strptime(datestring, "%Y-%m-%dT%H:%M:%S").replace(tzinfo=utc) - sec = float("0."+sec) - return date + timedelta(seconds=sec) - else: - return datetime.strptime(datestring, "%Y-%m-%dT%H:%M:%S").replace(tzinfo=utc) - - raise StandardError('decoding date err', datestring) - elif c == BLOB: - blob_id, first = _read_until(fh, LEN_SEP, parse=int) - first = read_first(fh) - attr = self._read_one(fh, first, blobs, base_url) - blob = Blob(self.temp_file(), attr) - first = read_first(fh) - if first != END_ITEM: - raise StandardError('blob') - blobs[blob_id] = blob - return blob - - elif c not in ('', ): - raise StandardError('decoding err', c) - raise EOFError() - - def _read_blobs(self, fh, blobs): - byte_count = collections.defaultdict(int) - while blobs: - first = read_first(fh) - if first == CHUNK: - blob_id, first = _read_until(fh, (LEN_SEP,END_ITEM), parse=int) - if first == END_ITEM: # 0 length block - blobs.pop(blob_id).seek(0) - else: - size, first = _read_until(fh, LEN_SEP, parse=int) - blob = blobs[blob_id] - buf = fh.read(size) - blob.write(buf) - byte_count[blob_id] += len(buf) - first = read_first(fh) - - if first != END_ITEM: - raise StandardError('blob') - - else: - raise StandardError('chunk') - diff --git a/py/hyperglyph/resource/__init__.py b/py/hyperglyph/resource/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/py/hyperglyph/resource/base.py b/py/hyperglyph/resource/base.py deleted file mode 100644 index e07848f..0000000 --- a/py/hyperglyph/resource/base.py +++ /dev/null @@ -1,176 +0,0 @@ -import types -import traceback - -from urllib import quote_plus, unquote_plus - - -from werkzeug.wrappers import Request, Response -from werkzeug.exceptions import HTTPException, NotFound, BadRequest, NotImplemented, MethodNotAllowed -from werkzeug.utils import redirect as Redirect - -from ..data import CONTENT_TYPE, dump, parse, get, form, link, robj, embedlink, ismethod, methodargs - -VERBS = set(("GET", "POST", "PUT","DELETE","PATCH",)) -from .handler import Handler - -def make_controls(resource): - forms = {} - for m in dir(resource.__class__): - cls_attr = getattr(resource.__class__ ,m) - ins_attr = getattr(resource,m) - if cls_attr == ins_attr: - # schenanigans - # classmethods are going to be weird. staticmethods weirder - # ignore them for now - continue - - - m = unicode(m) - if m not in VERBS and Handler.is_visible(ins_attr, name=m): - if isinstance(cls_attr, property): - # just inline the property - forms[m] = ins_attr - elif callable(cls_attr): - # but if it is a method or similar, make a link/form - if hasattr(ins_attr, 'func_code'): - forms[m] = Handler.make_link(ins_attr) - - return forms - - -class BaseResource(object): - """ An abstract resource to be served. Contains a mapper attribute __glyph__, - which contains a mapper class to use for this resource, - """ - __glyph__ = None - - def GET(self): - return self -def get_mapper(obj, name): - """ return the mapper for this object, with a given name""" - if hasattr(obj, '__glyph__') and issubclass(obj.__glyph__, BaseMapper): - return obj.__glyph__(name, obj) - elif isinstance(obj, types.FunctionType): - return BaseMapper(name, obj) - raise StandardError('no mapper for object') - -class BaseMapper(object): - """ The base mapper - bound to a class and a prefix, handles requests - from the router, and finds/creates a resource to handle it. - - This handles the verb mapping part of http, as well as the serializing/ - deserializing content. It also maps methods to urls, as well as object - state representation. - """ - - def __init__(self, prefix, res): - self.prefix = prefix - self.res = res - - """ How query strings are handled. """ - @staticmethod - def dump_query(d): - """ transform a dict into a query string """ - return quote_plus(dump(d)) if d else '' - - @staticmethod - def parse_query(query): - """ turn a query string into a dict """ - return parse(unquote_plus(query)) if query else {} - - def handle(self,request, router): - return Handler.call(self.res, request, router) - - def inline(self,resource): - return Handler.make_link(resource) - - def url(self, r): - """ return a url string that reflects this resource: - can be a resource class, instance or method - """ - if isinstance(r, basestring): - return r - elif r is self.res: - return '/%s'%self.prefix - - raise LookupError() - -class ClassMapper(BaseMapper): - def handle(self,request, router): - path = request.path.split(self.prefix)[1] - verb = request.method.upper() - if 'Method' in request.headers: - verb = request.headers['Method'] - - try: - if path: - # if we are mapping to an instance - attr_name = path[1:] if path [1:] else verb - args = self.parse_query(request.query_string) - obj = self.get_instance(args) - - # find the method to call - attr = getattr(obj, attr_name) - - else: - # no instance found, use default handler - attr = self.default_method(verb) - - except StandardError as e: - router.log_error(e, traceback.format_exc()) - raise BadRequest() - - return Handler.call(attr, request, router) - - def inline(self,resource): - if isinstance(resource, BaseResource): - return self.inline_resource(resource) - else: - return Handler.make_link(resource) - - """ Generate a glyph-node that contains - the object attributes and methods - """ - def inline_resource(self, resource): - page = dict() - page.update(make_controls(resource)) - page.update(self.index(resource)) - return robj(resource, page) - - def index(self, resource): - return dict((unicode(k),v) for k,v in resource.__dict__.items() if not k.startswith('_')) - - - def default_method(self, verb): - if verb not in VERBS: - raise MethodNotAllowed(verb) - try: - return getattr(self, verb) - except: - raise MethodNotAllowed() - - def url(self, r): - """ return a url string that reflects this resource: - can be a resource class, instance or method - """ - if isinstance(r, self.res): - return u"/%s/?%s"%(self.prefix, self.dump_query(self.get_repr(r))) - elif isinstance(r, type) and issubclass(r, self.res): - return u'/%s'%self.prefix - elif ismethod(r, self.res): - return u"/%s/%s?%s"%(self.prefix, r.im_func.__name__, self.dump_query(self.get_repr(r.im_self))) - else: - return BaseMapper.url(self, r) - - """ Abstract methods for creating, finding a resource, and getting the representation - of a resource, to embed in the url """ - - def get_instance(self, representation): - raise NotImplemented() - - def get_repr(self, resource): - """ return the representation of the state of the resource as a dict """ - raise NotImplemented() - - - diff --git a/py/hyperglyph/resource/handler.py b/py/hyperglyph/resource/handler.py deleted file mode 100644 index ec873c6..0000000 --- a/py/hyperglyph/resource/handler.py +++ /dev/null @@ -1,159 +0,0 @@ -import types -import sys -import collections - -from werkzeug.wrappers import Response -from werkzeug.exceptions import HTTPException, NotFound, BadRequest, NotImplemented, MethodNotAllowed -from werkzeug.utils import redirect as Redirect -from werkzeug.datastructures import ResponseCacheControl - -from ..data import CONTENT_TYPE, dump, parse, get, form, link, embedlink, ismethod, methodargs - - -def get_stream(request): - if request.headers.get('Transfer-Encoding') == 'chunked': - raise StandardError('middleware doesnt unwrap transfer-encoding') - else: - return request.stream - -class Handler(object): - """ Represents the capabilities of methods on resources, used by the mapper - to determine how to handle requests - """ - SAFE = False - EMBED = False - EXPIRES = False - REDIRECT = None - VISIBLE = True - CACHE = ResponseCacheControl() - - def __init__(self, handler=None): - if handler: - self.safe = handler.safe - self.embed = handler.embed - self.expires = handler.expires - self.cache = handler.cache - self.visible = handler.visible - else: - self.safe = self.SAFE - self.embed = self.EMBED - self.redirect = self.REDIRECT - self.cache = self.CACHE - self.visible = self.VISIBLE - - @staticmethod - def parse(resource, data): - return parse(data) - - @staticmethod - def dump(resource, data, resolver, inline): - return CONTENT_TYPE, dump(data, resolver,inline) - - @classmethod - def is_safe(cls, m): - try: - return m.__glyph_method__.safe - except StandardError: - if m.__name__ == 'GET': - return True - return cls.SAFE - - @classmethod - def is_embed(cls, m): - try: - return m.__glyph_method__.embed - except StandardError: - return cls.EMBED - - @classmethod - def is_redirect(cls, m): - try: - return m.__glyph_method__.redirect is not None - except StandardError: - return cls.REDIRECT - - @classmethod - def is_visible(cls, m, name): - try: - return m.__glyph_method__.visible - except StandardError: - return not name.startswith('_') - - @staticmethod - def redirect_code(m): - return m.__glyph_method__.redirect - - @classmethod - def make_link(cls, m): - if cls.is_safe(m): - if cls.is_embed(m): - return embedlink(m,content=m()) - else: - return link(m) - else: - return form(m) - - - @classmethod - def call(cls, attr, request, router): - verb = request.method - if verb == 'GET' and cls.is_safe(attr): - result = attr() - elif verb == 'POST' and not cls.is_safe(attr): - try: - data = collections.OrderedDict(cls.parse(attr, get_stream(request))) - except StandardError: - raise - raise BadRequest() - result =attr(**data) - else: - raise MethodNotAllowed() - - if result is None: - return Response('', status='204 None') - - else: - if cls.is_redirect(attr): - try: - url = router.url(result) - except LookupError: - pass - else: - return Redirect(url, code=cls.redirect_code(attr)) - - content_type, result = cls.dump(attr, result, router.url, router.inline) - return Response(result, content_type=content_type) - - - -def make_handler(fn): - if not hasattr(fn, '__glyph_method__'): - fn.__glyph_method__ = Handler() - return fn.__glyph_method__ - - -def redirect(code=303): - def _decorate(fn): - m = make_handler(fn) - m.redirect=code - return fn - return _decorate - -def safe(embed=False): - def _decorate(fn): - m = make_handler(fn) - m.safe=True - m.embed=embed - return fn - return _decorate - -def embed(): - return safe(embed=True) - -def hidden(): - def _decorate(fn): - m = make_handler(fn) - m.visible = False - return fn - return _decorate - diff --git a/py/hyperglyph/resource/persistent.py b/py/hyperglyph/resource/persistent.py deleted file mode 100644 index 1d42fe5..0000000 --- a/py/hyperglyph/resource/persistent.py +++ /dev/null @@ -1,34 +0,0 @@ -from uuid import uuid4 - -from .base import ClassMapper, BaseResource -from .handler import redirect - -class PersistentMapper(ClassMapper): - def __init__(self, prefix, res): - ClassMapper.__init__(self, prefix, res) - self.instances = {} - self.identifiers = {} - - @redirect() - def POST(self, **args): - instance = self.res(**args) - uuid = str(uuid4()) - self.instances[uuid] = instance - self.identifiers[instance] = uuid - return instance - - def get_instance(self, uuid): - return self.instances[uuid] - - def get_repr(self, instance): - if instance not in self.identifiers: - uuid = str(uuid4()) - self.instances[uuid] = instance - self.identifiers[instance] = uuid - else: - uuid = self.identifiers[instance] - return uuid - -class PersistentResource(BaseResource): - __glyph__ = PersistentMapper - diff --git a/py/hyperglyph/resource/router.py b/py/hyperglyph/resource/router.py deleted file mode 100644 index 8d652fd..0000000 --- a/py/hyperglyph/resource/router.py +++ /dev/null @@ -1,112 +0,0 @@ -import weakref -import sys -import traceback - -from werkzeug.utils import redirect as Redirect -from werkzeug.wrappers import Request, Response -from werkzeug.exceptions import HTTPException, NotFound, BadRequest, NotImplemented, MethodNotAllowed - - -from .base import BaseResource, BaseMapper, get_mapper -from .handler import Handler, safe -from .transient import TransientResource, TransientMapper -from ..data import ismethod - - - - -def make_default(router): - class _mapper(TransientMapper): - def index(self,resource): - return dict((unicode(r.__name__), Handler.make_link(r)) for r in router.mappers if r is not resource.__class__) - - class __default__(TransientResource): - __glyph__ = _mapper - - return __default__ -class Router(object): - def __init__(self): - self.mappers = {} - self.routes = {} - default = make_default(weakref.proxy(self)) - - self.register(default) - self.default_resource = default() - - def __call__(self, environ, start_response): - request = Request(environ) - try: - if request.path == '/': - response = Redirect(self.url(self.default_resource), code=303) - else: - response = self.url_mapper(request.path).handle(request, self) - - except (StopIteration, GeneratorExit, SystemExit, KeyboardInterrupt): - raise - except HTTPException as r: - response = r - self.log_error(r, traceback.format_exc()) - except Exception as e: - trace = traceback.format_exc() - self.log_error(e, trace) - response = self.error_response(e, trace) - return response(environ, start_response) - - def log_error(self, exception, trace): - print >> sys.stderr, trace - - def error_response(self, exception, trace): - return Response(trace, status='500 not ok (%s)'%exception) - - - def url_mapper(self, path): - try: - path= path[1:].split('/') - return self.routes[path[0]] - except StandardError: - raise NotFound() - - def register(self, obj): - path = obj.__name__ - mapper = get_mapper(obj, path) - self.routes[path] = mapper - self.mappers[obj] = mapper - return obj - - def resource_mapper(self, r): - if isinstance(r, BaseResource): - return self.mappers[r.__class__] - elif ismethod(r, BaseResource): - return self.mappers[r.im_class] - elif r in self.mappers: - return self.mappers[r] - - def inline(self, r): - m = self.resource_mapper(r) - if m: - return m.inline(r) - - raise StandardError("Don't know how to inline %s"%repr(r)) - - - def url(self, r): - if isinstance(r, basestring): - return unicode(r) - - mapper = self.resource_mapper(r) - if mapper: - return unicode(mapper.url(r)) - - raise LookupError('no url for',r ) - - def add(self): - return self.register - - def default(self, **args): - def _register(obj): - self.register(obj) - self.default_resource=obj(**args) - return obj - return _register - - diff --git a/py/hyperglyph/resource/transient.py b/py/hyperglyph/resource/transient.py deleted file mode 100644 index d9a4383..0000000 --- a/py/hyperglyph/resource/transient.py +++ /dev/null @@ -1,42 +0,0 @@ -""" Transient Resources - -When a request arrives, the mapper constructs a new instance of the resource, -using the query string to construct it. - -when you link to a resource instance, the link contains the state of the resource -encoded in the query string - -""" -from .base import ClassMapper, BaseResource -from .handler import redirect -from ..data import methodargs - -class TransientMapper(ClassMapper): - def get_instance(self, args): - return self.res(**args) - - def get_repr(self, resource): - repr = {} - args = methodargs(resource.__init__) - for k,v in resource.__dict__.items(): - if k in args: - repr[k] = v - - return repr - - @redirect() - def POST(self, **args): - """ Posting to a class, i.e from form(Resource), creates a new instance - and redirects to it """ - return self.get_instance(args) - - -class TransientResource(BaseResource): - __glyph__ = TransientMapper - -""" Persistent Resources """ - - - - - diff --git a/py/hyperglyph/server.py b/py/hyperglyph/server.py deleted file mode 100644 index 816bef0..0000000 --- a/py/hyperglyph/server.py +++ /dev/null @@ -1,40 +0,0 @@ -from werkzeug.serving import make_server, WSGIRequestHandler -import threading -import socket - - -class RequestHandler(WSGIRequestHandler): - def log_request(self, code='-', size='-'): - pass - -class Server(threading.Thread): - def __init__(self, app, host="", port=0, threaded=True, processes=1, - request_handler=RequestHandler, passthrough_errors=False, ssl_context=None): - """ Use ssl_context='adhoc' for an ad-hoc cert, a tuple for a (cerk, pkey) files - - - """ - threading.Thread.__init__(self) - self.daemon=True - self.server = make_server(host, port, app, threaded=threaded, processes=processes, - request_handler=request_handler, passthrough_errors=passthrough_errors, ssl_context=ssl_context) - - @property - def url(self): - return u'http%s://%s:%d/'%(('s' if self.server.ssl_context else ''), self.server.server_name, self.server.server_port) - - def run(self): - self.server.serve_forever() - - def stop(self): - self.server.shutdown_signal = True - if self.server and self.is_alive(): - try: - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.connect(self.server.socket.getsockname()[:2]) - s.send('\r\n') - s.close() - except IOError: - import traceback - traceback.print_exc() - self.join(5) diff --git a/py/requirements.txt b/py/requirements.txt deleted file mode 100644 index 47d8af8..0000000 --- a/py/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -unittest2 -werkzeug -requests -pytz -isodate diff --git a/py/setup.py b/py/setup.py deleted file mode 100644 index 5800d47..0000000 --- a/py/setup.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python - -import glob - -from setuptools import setup - -import os.path, os - -os.chdir(os.path.dirname(os.path.abspath(__file__))) - -setup(name='hyperglyph', - version='0.9-20120825', - license="MIT License", - description='hyperglyph is ducked typed ipc over http', - author='tef', - author_email='tef@twentygototen.org', - packages=['hyperglyph', 'hyperglyph.resource'], - #scripts=glob.glob('*.py'), - test_suite = "tests", - ) - diff --git a/py/tests/__init__.py b/py/tests/__init__.py deleted file mode 100644 index 720aad1..0000000 --- a/py/tests/__init__.py +++ /dev/null @@ -1,450 +0,0 @@ -import unittest2 -import hyperglyph as glyph -import datetime -import collections - -from cStringIO import StringIO -from io import BytesIO - - - -class EncodingTest(unittest2.TestCase): - def testCase(self): - cases = [ - 1, - 1.0, - "foo", - u"bar", - [], - ['a',1,[2]], - collections.OrderedDict([('a', 1), ('b',2)]), - None, - True, - False, - {'a':1}, - set([1,2,3]), - glyph.utcnow(), - datetime.timedelta(days=5, hours=4, minutes=3, seconds=2), - ] - for c in cases: - self.assertEqual(c, glyph.parse(glyph.dump(c))) - -class BlobEncodingTest(unittest2.TestCase): - def testCase(self): - s = "Hello, World" - a = glyph.blob(s) - b = glyph.parse(glyph.dump(a)) - - self.assertEqual(s, b.fh.read()) - - s = "Hello, World" - a = glyph.blob(BytesIO(s)) - b = glyph.parse(glyph.dump(a)) - - self.assertEqual(s, b.fh.read()) - - -class ExtTest(unittest2.TestCase): - def testCase(self): - cases = [ - glyph.form("http://example.org",values=['a','b']), - glyph.link("http://toot.org") - ] - for c in cases: - self.assertEqual(c, glyph.parse(glyph.dump(c))) - - - -class ServerTest(unittest2.TestCase): - def setUp(self): - self.endpoint=glyph.Server(self.router()) - self.endpoint.start() - - def tearDown(self): - self.endpoint.stop() - -class GetTest(ServerTest): - def setUp(self): - self.value = 0 - ServerTest.setUp(self) - - def router(self): - m = glyph.Router() - @m.default() - class Test(glyph.r): - def get(self_): - return self.value - return m - - def testCase(self): - result = glyph.get(self.endpoint.url) - - self.assertEqual(result.get(), self.value) -class NoneTest(ServerTest): - - def router(self): - m = glyph.Router() - @m.default() - class Test(glyph.r): - def get(self): - return None - return m - - def testCase(self): - result = glyph.get(self.endpoint.url) - - self.assertEqual(result.get(), None) - -class IndexTest(ServerTest): - def setUp(self): - ServerTest.setUp(self) - - def router(self): - m = glyph.Router() - @m.add() - class Test(glyph.r): - def get(self): - return 1 - @m.add() - class Other(glyph.r): - def __init__(self, v=0): - self.v = v - return m - - def testCase(self): - index = glyph.get(self.endpoint.url) - - self.assertEqual(index.Test().get(), 1) - self.assertEqual(index.Other(4).v,4) -class LinkTest(ServerTest): - def setUp(self): - self.value = 0 - ServerTest.setUp(self) - - def router(self): - m = glyph.Router() - @m.add() - class Test(glyph.r): - def __init__(self, value): - self.value = int(value) - def double(self): - return self.value*2 - @m.default() - class Root(glyph.r): - @glyph.redirect() - def get(self_): - return Test(self.value) - return m - - def testCase(self): - result = glyph.get(self.endpoint.url) - result = result.get() - self.assertEqual(result.double(), self.value) - - -class MethodTest(ServerTest): - def setUp(self): - ServerTest.setUp(self) - - def router(self): - m = glyph.Router() - @m.default() - class Test(glyph.r): - def __init__(self, value=0): - self.value = int(value) - @glyph.redirect() - def inc(self, n): - return Test(self.value+n) - return m - - def testCase(self): - result = glyph.get(self.endpoint.url) - self.assertEqual(result.value, 0) - result = result.inc(n=5) - - self.assertEqual(result.value, 5) - - -class LinkEmbedTest(ServerTest): - - def router(self): - m = glyph.Router() - @m.default() - class Test(glyph.r): - def __init__(self, _value=0): - self._value = int(_value) - @glyph.embed() - def value(self): - return self._value - - @glyph.safe() - def add2(self): - return self._value + 2 - return m - - def testCase(self): - result = glyph.get(self.endpoint.url) - self.assertEqual(result.value(), 0) - self.assertEqual(result.add2(), 2) - - - - -class FormObjectTest(ServerTest): - - def router(self): - m = glyph.Router() - - @m.add() - class Test(glyph.r): - def __init__(self, _value=0): - self._value = int(_value) - - @glyph.embed() - def value(self): - return self._value - - return m - - def testCase(self): - root = glyph.get(self.endpoint.url) - result = root.Test(0) - self.assertEqual(result.value(), 0) - result = root.Test(2) - self.assertEqual(result.value(), 2) - -class PersistentObjectTest(ServerTest): - - def router(self): - m = glyph.Router() - - @m.add() - class Test(glyph.PersistentResource): - def __init__(self, _value=0): - self._value = int(_value) - - @property - def self(self): - return glyph.link(self) - - @property - def value(self): - return self._value - - - return m - - def testCase(self): - root = glyph.get(self.endpoint.url) - result_a = root.Test(0) - self.assertEqual(result_a.value, 0) - result_b = root.Test(0) - self.assertEqual(result_b.value, 0) - self.assertNotEqual(result_a.self.url(), result_b.self.url()) - -class DefaultPersistentObjectTest(ServerTest): - - def router(self): - m = glyph.Router() - @m.default() - class Test(glyph.PersistentResource): - def __init__(self, _value=0): - self._value = int(_value) - - @glyph.embed() - def value(self): - return self._value - - @glyph.redirect() - def make(self, value): - return Test(value) - - @property - def self(self): - return glyph.link(self) - - - return m - - def testCase(self): - result_a = glyph.get(self.endpoint.url) - self.assertEqual(result_a.value(), 0) - result_b = result_a.make(0) - self.assertEqual(result_b.value(), 0) - self.assertNotEqual(result_a.self.url(), result_b.self.url()) - -class HiddenMethodTest(ServerTest): - def router(self): - m = glyph.Router() - @m.default() - class Test(glyph.r): - - @glyph.hidden() - def nope(self): - return "Nope" - return m - - def testCase(self): - result = glyph.get(self.endpoint.url) - with self.assertRaises(StandardError): - self.result.nope() - -class SpecialMethodTest(ServerTest): - def router(self): - m = glyph.Router() - @m.default() - class Test(glyph.r): - @classmethod - def clsm(cls): - return "Hello" - - @staticmethod - def static(self): - return "World" - return m - - def testCase(self): - result = glyph.get(self.endpoint.url) - with self.assertRaises(AttributeError): - self.result.clsm() - with self.assertRaises(AttributeError): - self.result.static() - -class VerbTest(ServerTest): - - def router(self): - m = glyph.Router() - @m.default() - class Test(glyph.r): - def POST(self): - return 0 - - def GET(self): - return {'make':glyph.form(Test), 'zero': glyph.form(self)} - return m - - def testCase(self): - result = glyph.get(self.endpoint.url) - self.assertEqual(result['zero'](), 0) - result = result['make']() - -class FunctionTest(ServerTest): - - def router(self): - m = glyph.Router() - @m.add() - def foo(a,b): - return a+b - - @m.add() - def bar(a,b): - return a*b - - @m.add() - def baz(a,b,c): - return a,b,c - - - - return m - - def testCase(self): - root = glyph.get(self.endpoint.url) - self.assertEqual(root.foo(1,1), 2) - self.assertEqual(root.bar(1,1), 1) - self.assertEqual(root.baz(1,2,3), [1,2,3]) - self.assertEqual(root.baz(c=3,b=2,a=1), [1,2,3]) - self.assertEqual(root.baz(1,c=3,b=2), [1,2,3]) - -class FunctionRedirectTest(ServerTest): - - def router(self): - m = glyph.Router() - - @m.add() - @glyph.safe() - def bar(): - return 123 - - @m.add() - @glyph.redirect() - def foo(): - return bar - - - return m - - def testCase(self): - root = glyph.get(self.endpoint.url) - self.assertEqual(root.foo(), 123) - -class PropertyAsInstanceValue(ServerTest): - - def router(self): - m = glyph.Router() - - @m.default() - class R(glyph.Resource): - @property - def foo(self): - return 123 - - - return m - - def testCase(self): - root = glyph.get(self.endpoint.url) - self.assertEqual(root.foo, 123) - -class Inline(ServerTest): - def setUp(self): - ServerTest.setUp(self) - - def router(self): - m = glyph.Router() - @m.add() - def pair(): - return Other(0), Other(1) - - @m.add() - def unit(): - return pair - - @m.add() - class Other(glyph.r): - def __init__(self, v=0): - self.v = v - return m - - def testCase(self): - index = glyph.get(self.endpoint.url) - - - a,b = index.unit()() - self.assertEqual(a.v, 0) - self.assertEqual(b.v, 1) - -if __name__ == '__main__': - unittest2.main() - -class BlobReturnAndCall(ServerTest): - - def router(self): - m = glyph.Router() - - @m.default() - class R(glyph.Resource): - def do_blob(self, b): - s = b.fh.read() - c = b.content_type - return glyph.blob(BytesIO(s.lower()), content_type=c) - - - return m - - def testCase(self): - root = glyph.get(self.endpoint.url) - b = glyph.blob(BytesIO("TEST"), content_type="Test") - b2 = root.do_blob(b) - self.assertEqual(b2.fh.read(), "test") - self.assertEqual(b2.content_type, "Test")