Commits

Lynn Rees committed edadea9 Merge
  • Participants
  • Parent commits bfcbc59, ceffc0d
  • Branches pu

Comments (0)

Files changed (106)

-'''setup for req'''
+'''setup for wire'''
+
+import sys
 
 try:
     from setuptools import setup
 except ImportError:
-    from distutils.core import setup
+    from distutils.core import setup
+
+install_requires = ['httplib2', 'stuf']
+if sys.version_info <= (2, 7):
+    install_requires.append('importlib')
 
 setup(
     name='wire',
     version='0.1',
-    description='''Web client framework''',
-    long_description='''wire is a Web client framework featuring:
+    description='Web client application framework',
+    long_description='''
+    wire is a Web client application framework featuring:
 
-    * extensive client request customization
+    * client request customization
 
     * automatic client request header validation
 
 
     * persistent REST client
 
-    * support for choosing different HTTP client libraries:
+    * support for different HTTP client backends:
 
+      - httplib2
+      - pycurl
       - urllib2
-      - httplib2
-      - pycurl (planned)
-      - curl (planned)
-      - wget (planned)
 
     * built-in serialization/deserialization support for:
 
-       * binary JSON (BSON)
-       * comma-separated values (CSV)
-       * Microsoft Excel
-       * HTML
-       * JSON
-       * Python marshal
-       * form multipart encoding
-       * Python pickle
-       * Protocol Buffers
-       * Thrift
-       * URL encoding
-       * XML
-       * XML-RPC
-       * YaML
+       - binary JSON (BSON)
+       - comma-separated values (CSV)
+       - Microsoft Excel
+       - HTML
+       - JSON
+       - Python marshal
+       - form multipart encoding
+       - Python pickle
+       - Protocol Buffers
+       - Thrift
+       - URL encoding
+       - XML
+       - XML-RPC
+       - YaML
     ''',
     author='L. C. Rees',
     author_email='lcrees@gmail.com',
     url='https://bitbucket.org/lcrees/wire',
     license='BSD',
-    packages = ['wire', 'wire.talk', 'wire.http'],
+    packages=['wire', 'wire.talk', 'wire.http'],
     test_suite='wire.test',
-    zip_safe = False,
+    zip_safe=False,
     keywords='http client https Web REST',
-    install_requires=['httplib2', 'stuf'],
+    install_requires=install_requires,
     classifiers=[
         'Development Status :: 2 - Alpha',
         'Intended Audience :: Developers',
         'Programming Language :: Python :: 2.7',
         'Topic :: Internet :: WWW/HTTP',
         'Topic :: Software Development :: Libraries :: Application Frameworks',
-    ]
-)
+    ],
+)

File unfinished/test_avro.py

     def test_talk(self):
         dumped = self.talker.dumps(self.obj, avro_schema=self.schema)
         self.assertIsInstance(dumped, basestring)
-        newobj = self.talker.loads(dumped)
+        newobj = self.talker._loads(dumped)
         self.assertEqual(self.obj, newobj, newobj)
 
 
     def test_talk(self):
         dumped = self.talker.dumps(self.obj, avro_schema=self.schema)
         self.assertIsInstance(dumped, basestring)
-        newobj = self.talker.loads(dumped)
+        newobj = self.talker._loads(dumped)
         self.assertEqual(self.obj, newobj, newobj)
 
 

File wire/http/__init__.py

-from wire.http.core import (
-    Client, RESTClient, get, mget, fast_get, post, mpost, fast_post, put, mput,
-    fast_put, delete, mdelete, fast_delete, head, mhead, fast_head, options,
-    moptions, fast_options,
-)
+# -*- coding: utf-8 -*-
+'''wire http'''

File wire/http/apps.py

+
+
+# HTTP clients
+HTTPLIST = dict(
+    urllib2='ul2_client.Request',
+    httplib2='hl2_client.Request',
+    pycurl='pycurl_client.Request',
+)
+#HTTP = Loader(HTTPLIST, package='wire.http.client')
+
+# REST clients
+RESTLIST = dict(
+    urllib2='ul2_client.REST',
+    httplib2='hl2_client.REST',
+    pycurl='pycurl_client.REST',
+)
+#REST = Loader(RESTLIST, package='wire.http.client')

File wire/http/chains.py

+# -*- coding: utf-8 -*-
+'''wire http call chain'''
+
+from callchain.root.chain import callchain
+from wire.util import missing
+
+
+class http(callchain):
+
+    '''HTTP client call chain'''
+
+    def __init__(self, pattern=None, required=None, defaults=None, **kw):
+        '''
+        init
+
+        @param pattern: pattern configuration or appspace label (default: None)
+        @param required: required settings (default: None)
+        @param defaults: default settings (default: None)
+        '''
+        super(http, self).__init__(pattern, required, defaults, **kw)
+        # set data to something
+        self._data = ''
+        # wire protocol
+        self._format = ''
+        # files something
+        self._files = kw.pop('files', {})
+        # custom headers
+        self._headers = kw.pop('headers', {})
+        # custom cookie
+        self._cookie = kw.pop('cookie', {})
+        # default timeout
+        self._timeout = kw.pop('timeout', 100)
+        # use random user agent
+        self._randomua = kw.pop('randomua', False)
+        # preserve any original data
+        self._original = kw.pop('data', '')
+        # url, if any
+        self._url = kw.pop('url', '')
+        # files demand multipart as default otherwise go with urlencode
+        self._originalfmt = kw.pop(
+            'format', 'multipart' if self._files else 'url'
+        )
+        # authentication
+        self._auth = kw.pop('auth', {})
+        if self._auth:
+            # verify
+            if missing(self._auth, ('username', 'password')):
+                raise TypeError(
+                    u'authentication missing both "username" and "password"',
+                )
+        # proxy
+        self._proxy = kw.pop('proxy', {})
+        if self._proxy:
+            if missing(self._proxy, ('server', 'host')):
+                raise TypeError(u'proxy missing both "server" and "host"')
+        # misc. options, if any
+        self._options = kw
+        self._changed = False
+
+    def __call__(self, *urls):
+        '''
+        load urls
+
+        @param urls: Uniform Resource Locations (URLs)
+        '''
+        self._urls = urls
+        return self

File wire/http/client/__init__.py

Empty file removed.

File wire/http/client/app.py

-from wire.util import Load
-
-# HTTP clients
-HTTPLIST = dict(
-    urllib2='ul2_client.Request',
-    httplib2='hl2_client.Request',
-)
-HTTP = Load(HTTPLIST, module='wire.http.client')
-
-# REST clients
-RESTLIST = dict(
-    urllib2='ul2_client.REST',
-    httplib2='hl2_client.REST',
-)
-REST = Load(RESTLIST, module='wire.http.client')

File wire/http/client/hl2_client.py

-'''httplib2 wire HTTP client wrapper'''
-
-try:
-    import eventlet
-    httplib2 = eventlet.import_patched('httplib2')
-except ImportError:
-    import httplib2
-
-from wire.util import lazy, lazycls
-from wire.http.response import CoreResponse
-from wire.http.request import CoreRequest, CoreREST
-
-
-class Request(CoreRequest):
-
-    @lazy
-    def _factory(self):
-        auth = self._auth
-        proxy = self._proxy
-        if proxy:
-            try:
-                import socks
-                h = httplib2.Http(
-                    '.cache',
-                    proxy_info=httplib2.ProxyInfo(
-                        socks.PROXY_TYPE_HTTP, proxy['server'], proxy['host'],
-                    ),
-                )
-            except ImportError:
-                raise ImportWarning(
-                    u'httplib2 proxy support requires "socks" library'
-                )
-        else:
-            h = httplib2.Http('.cache')
-        if auth: h.add_credentials(*(auth['username'], auth['password']))
-        return h
-
-    @lazycls
-    def _response(self):
-        return Response
-
-    def _request(self, **kw):
-        try:
-            factory = self._factory
-            # temporary auth
-            auth = kw.pop('auth', self._auth)
-            if auth: factory.add_credentials(
-                *(auth['username'], auth['password'])
-            )
-            # temporary timeout
-            timeout = kw.pop('timout', self._timeout)
-            if timeout: factory.timeout = timeout
-            headers = kw.pop('headers', dict(self.headers))
-            return factory.request(
-                kw.pop('url'),
-                method=kw.pop('method', 'GET'),
-                body=kw.pop('data', ''),
-                headers=headers,
-            )
-        except httplib2.RelativeURIError, e:
-            raise ValueError(e)
-        finally:
-            # clear temporary auth
-            if auth: factory.clear_credentials()
-            # clear temporary timeout
-            if timeout: factory.timeout = None
-
-
-class RESTClient(Request, CoreREST):
-
-    '''httplib2 REST client'''
-
-
-class Response(CoreResponse):
-
-    '''httplib2 response object'''
-
-    def __init__(self, resp, **kw):
-        response, content = resp
-        self._original = content
-        self._headers = h = dict(response)
-        self._code = h.pop('status')
-        self.url = h.pop('content-location', None)
-        super(Response, self).__init__(**kw)

File wire/http/client/pycurl_client.py

-'''p wire HTTP client wrapper'''
-
-from StringIO import StringIO
-from httplib import HTTPMessage
-# what pycurl needs...
-try:
-    import signal
-    from signal import SIGPIPE, SIG_IGN
-    signal.signal(SIGPIPE, SIG_IGN)
-except ImportError:
-    pass
-
-try:
-    import pycurl as p
-except ImportError:
-    raise ImportError('pycurl_client requires the "p" library')
-
-from wire.util import lazy, lazycls
-from wire.http.response import CoreResponse
-from wire.http.request import CoreRequest, CoreREST
-
-
-class Request(CoreRequest):
-
-    @lazy
-    def _factory(self):
-        factory = p.Curl()
-        proxy = self._proxy
-# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY
-        if proxy:
-            factory.setopt(p.PROXY, ':'.join([proxy['server'], proxy['host']]))
-# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTFOLLOWLOCATION
-        factory.setopt(p.FOLLOWLOCATION, 1)
-# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTMAXREDIRS
-        factory.setopt(p.MAXREDIRS, 5)
-# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTCONNECTTIMEOUT
-        factory.setopt(p.CONNECTTIMEOUT, 15)
-# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTNOSIGNAL
-        factory.setopt(p.NOSIGNAL, 1)
-        # deprecated?
-# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTTRANSFERTEXT
-        factory.setopt(p.TRANSFERTEXT, 1)
-        auth = self._auth
-        if auth:
-# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTHTTPAUTH
-            factory.setopt(p.HTTPAUTH, p.HTTPAUTH_BASIC)
-# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTUSERPWD
-            factory.setopt(
-                p.USERPWD, ':'.join(auth['username'], auth['password'])
-            )
-        return factory
-
-#    @lazy
-#    def multiple(self):
-#        def func(urls, **kw):
-#            multi = p.CurlMulti()
-#            makehandler = self._makehandler
-#            num_urls = len(urls)
-#            handlers = list(makehandler() for i in xrange(urls))
-#            return func
-
-    @lazycls
-    def _response(self):
-        return Response
-
-#    def _makehandler(self, **kw):
-#        factory = p.Curl()
-#        getinfo = factory.getinfo
-## http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTFOLLOWLOCATION
-#        factory.setopt(p.FOLLOWLOCATION, 1)
-## http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTMAXREDIRS
-#        factory.setopt(p.MAXREDIRS, 5)
-## http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTCONNECTTIMEOUT
-#        factory.setopt(p.CONNECTTIMEOUT, 15)
-## http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTNOSIGNAL
-#        factory.setopt(p.NOSIGNAL, 1)
-#        # deprecated?
-## http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTTRANSFERTEXT
-#        factory.setopt(p.TRANSFERTEXT, 1)
-#        # handle proxy
-#        proxy = kw.get('proxy', self._proxy)
-#        if proxy:
-#            factory.setopt(
-#                p.PROXY, ':'.join([proxy['server'], proxy['host']]),
-#            )
-#            # handle proxy auth
-#            factory.setopt(p.PROXYAUTH, p.HTTPAUTH_BASIC)
-#        # handle auth
-#        auth = kw.get('auth', self._auth)
-## http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTUSERPWD
-#        if auth:
-#            factory.setopt(p.HTTPAUTH, p.HTTPAUTH_BASIC)
-#            factory.setopt(
-#                p.USERPWD, ':'.join(auth['username'], auth['password'])
-#            )
-#        # handle timeout
-#        timeout = kw.get('timeout', self._timeout)
-#        if timeout: factory.setopt(p.TIMEOUT, timeout)
-#        data = kw.get('data', '')
-#        # handle method
-#        method = kw.get('method', 'get').lower()
-#        headers = list(
-#            ': '.join(i) for i in kw.get('headers', list(self.h))
-#        )
-#        if method == 'post':
-## http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPOST
-#            factory.setopt(p.POST, 1)
-## http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPOSTFIELDS
-#            factory.setopt(p.POSTFIELDS, data)
-#        elif method == 'put':
-#            # deprecated in favor of UPLOAD?
-## http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPUT
-#            factory.setopt(p.PUT, 1)
-## http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTUPLOAD
-#            factory.setopt(p.UPLOAD, 1)
-## http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTINFILESIZE
-#            factory.setopt(p.INFILESIZE, len(data))
-## http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTREADDATA
-#            factory.setopt(p.READDATA, data)
-## http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTNOBODY
-#        elif method == 'head':
-#            factory.setopt(p.NOBODY, 1)
-## http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTCUSTOMREQUEST
-#        elif method == 'delete':
-#            factory.setopt(p.CUSTOMREQUEST, 'DELETE')
-#        elif method == 'options':
-#            factory.setopt(p.CUSTOMREQUEST, 'OPTIONS')
-#        return factory
-
-    def _request(self, **kw):
-        try:
-            factory = self._factory
-            getinfo = factory.getinfo
-            # handle proxy
-            proxy = kw.pop('proxy', self._proxy)
-            if proxy:
-                factory.setopt(
-                    p.PROXY, ':'.join([proxy['server'], proxy['host']]),
-                )
-                # handle proxy auth
-                factory.setopt(p.PROXYAUTH, p.HTTPAUTH_BASIC)
-            # handle auth
-            auth = kw.pop('auth', self._auth)
-# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTUSERPWD
-            if auth:
-                factory.setopt(
-                    p.USERPWD, ':'.join(auth['username'], auth['password'])
-                )
-            # handle timeout
-            timeout = kw.pop('timeout', self._timeout)
-            if timeout: factory.setopt(p.TIMEOUT, timeout)
-            data = kw.pop('data', '')
-            # handle method
-            method = kw.pop('method', 'get').lower()
-            headers = list(
-                ': '.join(i) for i in kw.pop('headers', list(self.h))
-            )
-            if method == 'post':
-# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPOST
-                factory.setopt(p.POST, 1)
-# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPOSTFIELDS
-                factory.setopt(p.POSTFIELDS, data)
-            elif method == 'put':
-                # deprecated in favor of UPLOAD?
-# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPUT
-                factory.setopt(p.PUT, 1)
-# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTUPLOAD
-                factory.setopt(p.UPLOAD, 1)
-# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTINFILESIZE
-                factory.setopt(p.INFILESIZE, len(data))
-# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTREADDATA
-                factory.setopt(p.READDATA, data)
-# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTNOBODY
-            elif method == 'head':
-                factory.setopt(p.NOBODY, 1)
-# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTCUSTOMREQUEST
-            elif method == 'delete':
-                factory.setopt(p.CUSTOMREQUEST, 'DELETE')
-            elif method == 'options':
-                factory.setopt(p.CUSTOMREQUEST, 'OPTIONS')
-            # handle headers
-            header = StringIO()
-# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTUSERAGENT
-            factory.setopt(p.USERAGENT, self.h.useragent)
-# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTHTTPHEADER
-            factory.setopt(p.HTTPHEADER, list(headers))
-# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTHEADERFUNCTION
-            factory.setopt(p.HEADERFUNCTION, header.write)
-            # handle data
-            body = StringIO()
-# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTWRITEFUNCTION
-            factory.setopt(p.WRITEFUNCTION, body.write)
-            # handle url
-            factory.setopt(p.URL, kw.pop('url'))
-            factory.perform()
-# INFO: http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html
-            result = dict(
-                headers=header,
-                data=data,
-                status=getinfo(p.HTTP_CODE),
-                content_location=getinfo(p.EFFECTIVE_URL),
-            )
-        except p.error, e:
-# INFO: http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html
-            result = dict(
-                headers=header,
-                data=data,
-                status=getinfo(p.HTTP_CODE),
-                url=getinfo(p.EFFECTIVE_URL),
-                redirect_url=getinfo(p.REDIRECT_URL),
-                errors='{0} {1}'.format(e[0], e[1]),
-            )
-        finally:
-            factory.close()
-        return result
-
-
-class RESTClient(Request, CoreREST):
-
-    '''pycurl REST client'''
-
-
-class Response(CoreResponse):
-
-    '''pycurl response object'''
-
-    def __init__(self, resp, **kw):
-        # data
-        data = resp.pop('data', StringIO())
-        data.seek(0)
-        # preserve original data
-        self._original = data.read()
-        # headers...
-        headers = resp.pop('headers', StringIO())
-        headers.seek(0)
-        # clear first status line
-        headers.readline()
-        # yum...headers
-        self._headers = h = dict(i for i in HTTPMessage(headers).items())
-        # it's an int
-        self._code = str(h.pop('status', 500))
-        # final url
-        self.url = h.pop('url', None)
-        super(Response, self).__init__(**kw)
-
-
-#m = pycurl.CurlMulti()
-#m.handles = []
-#for i in range(num_conn):
-#    c = pycurl.Curl()
-#    c.fp = None
-#    c.setopt(pycurl.FOLLOWLOCATION, 1)
-#    c.setopt(pycurl.MAXREDIRS, 5)
-#    c.setopt(pycurl.CONNECTTIMEOUT, 30)
-#    c.setopt(pycurl.TIMEOUT, 300)
-#    c.setopt(pycurl.NOSIGNAL, 1)
-#    m.handles.append(c)
-#
-#
-## Main loop
-#freelist = m.handles[:]
-#num_processed = 0
-#while num_processed < num_urls:
-#    # If there is an url to process and a free curl object, add to multi stack
-#    while queue and freelist:
-#        url, filename = queue.pop(0)
-#        c = freelist.pop()
-#        c.fp = open(filename, "wb")
-#        c.setopt(pycurl.URL, url)
-#        c.setopt(pycurl.WRITEDATA, c.fp)
-#        m.add_handle(c)
-#        # store some info
-#        c.filename = filename
-#        c.url = url
-#    # Run the internal curl state machine for the multi stack
-#    while 1:
-#        ret, num_handles = m.perform()
-#        if ret != pycurl.E_CALL_MULTI_PERFORM:
-#            break
-#    # Check for curl objects which have terminated, and add them to the freelist
-#    while 1:
-#        num_q, ok_list, err_list = m.info_read()
-#        for c in ok_list:
-#            c.fp.close()
-#            c.fp = None
-#            m.remove_handle(c)
-#            print "Success:", c.filename, c.url, c.getinfo(pycurl.EFFECTIVE_URL)
-#            freelist.append(c)
-#        for c, errno, errmsg in err_list:
-#            c.fp.close()
-#            c.fp = None
-#            m.remove_handle(c)
-#            print "Failed: ", c.filename, c.url, errno, errmsg
-#            freelist.append(c)
-#        num_processed = num_processed + len(ok_list) + len(err_list)
-#        if num_q == 0:
-#            break
-#    # Currently no more I/O is pending, could do something in the meantime
-#    # (display a progress bar, etc.).
-#    # We just call select() to sleep until some more data is available.
-#    m.select(1.0)
-#
-#
-## Cleanup
-#for c in m.handles:
-#    if c.fp is not None:
-#        c.fp.close()
-#        c.fp = None
-#    c.close()
-#m.close()

File wire/http/client/ul2_client.py

-'''urllib2 client wrapper'''
-
-import urllib2
-from collections import deque
-
-try:
-    import eventlet
-    ulib = eventlet.import_patched('urllib2')
-except ImportError:
-    ulib = urllib2
-from stuf import stuf
-
-from wire.util import lazycls
-from wire.http.response import CoreResponse
-from wire.http.request import CoreRequest, CoreREST
-
-
-class Response(CoreResponse):
-
-    def __init__(self, response, **kw):
-        self._original = response.read()
-        self._code = response.code
-        self._headers = response.headers
-        self.url = response.url
-        super(Response, self).__init__(**kw)
-
-
-class _UL2Base(object):
-
-    def _factory(self, url):
-        handlers = deque([
-            ulib.HTTPHandler,
-            ulib.HTTPDefaultErrorHandler,
-            ulib.HTTPRedirectHandler,
-            ulib.FileHandler,
-            ulib.HTTPErrorProcessor,
-            ulib.HTTPSHandler,
-        ])
-        proxy = self._proxy
-        auth = self._auth
-        if proxy:
-            proxy_set = '{0} {1}'.format(proxy['server'], proxy['host'])
-            handlers.appendleft(
-                ulib.ProxyHandler(dict(http=proxy_set, https=proxy_set))
-            )
-        if auth:
-            password_mgr = ulib.HTTPPasswordMgrWithDefaultRealm()
-            password_mgr.add_password(
-                None, url, auth['username'], auth['password'],
-            )
-            handlers.append(ulib.HTTPBasicAuthHandler(password_mgr))
-        return ulib.build_opener(*tuple(h for h in handlers))
-
-    @lazycls
-    def _response(self):
-        return Response
-
-    def _request(self, **kw):
-        url = kw.pop('url')
-        data = kw.pop('data')
-        headers = kw.pop('headers', dict(self.headers))
-        request = urllib2.Request(url=url, data=data, headers=headers)
-        request.get_method = lambda: kw.pop('method', 'GET')
-        try:
-            return self._factory(url).open(request)
-        except ulib.HTTPError, e:
-            return stuf(
-                read=e.fp.read,
-                code=e.code,
-                headers=e.hdrs,
-                url=e.filename,
-            )
-
-
-class Request(CoreRequest, _UL2Base):
-
-    '''urllib2 Request'''
-
-
-class RESTClient(CoreREST, _UL2Base):
-
-    '''urllib2 REST client'''

File wire/http/core.py

-'''wire http core'''
-
-from collections import defaultdict
-
-from wire.util import lazy, lru_cache
-from wire.http.client.app import HTTP, REST
-
-
-class RESTClient(object):
-
-    '''REST client dispatcher'''
-
-    def __new__(cls, **kw):
-        return REST.resolve(kw.pop('client', 'httplib2'))(**kw)
-
-
-class Client(object):
-
-    '''HTTP client dispatcher'''
-
-    def __new__(cls, **kw):
-        return HTTP.resolve(kw.pop('client', 'httplib2'))(**kw)
-
-
-class Dispatcher(object):
-
-    '''client dispatcher for real'''
-
-    @lazy
-    def _clients(self):
-        '''client cache'''
-        return defaultdict(lambda: Client())
-
-    def __call__(self, **kw):
-        try:
-            client = kw.pop('client')
-            return self._clients.setdefault(client, Client(client=client))
-        except KeyError:
-            return self._clients['default']
-
-
-dispatcher = Dispatcher()
-
-
-def delete(url, **kw):
-    return dispatcher(**kw).delete(url=url, **kw)
-
-def fast_delete(url, **kw):
-    return dispatcher(**kw).fast_delete(url=url, **kw)
-
-def fast_get(url, **kw):
-    return dispatcher(**kw).fast_get(url=url, **kw)
-
-def fast_head(url, **kw):
-    return dispatcher(**kw).fast_head(url=url, **kw)
-
-def fast_options(url, **kw):
-    return dispatcher(**kw).fast_options(url=url, **kw)
-
-def fast_post(url, **kw):
-    return dispatcher(**kw).fast_post(url=url, **kw)
-
-def fast_put(url, **kw):
-    return dispatcher(**kw).fast_put(url=url, **kw)
-
-def get(url, **kw):
-    return dispatcher(**kw).get(url=url, **kw)
-
-def head(url, **kw):
-    return dispatcher(**kw).head(url=url, **kw)
-
-def mget(urls, **kw):
-    return dispatcher(**kw).mget(urls, **kw)
-
-def mdelete(urls, **kw):
-    return dispatcher(**kw).mdelete(urls, **kw)
-
-def mhead(urls, **kw):
-    return dispatcher(**kw).mhead(urls, **kw)
-
-def moptions(urls, **kw):
-    return dispatcher(**kw).mpost(urls, **kw)
-
-def mpost(urls, **kw):
-    return dispatcher(**kw).mpost(urls, **kw)
-
-def mput(urls, **kw):
-    return dispatcher(**kw).mput(urls, **kw)
-
-def options(url, **kw):
-    return dispatcher(**kw).options(url=url, **kw)
-
-def post(url, **kw):
-    return dispatcher(**kw).post(url=url, **kw)
-
-def put(url, **kw):
-    return dispatcher(**kw).put(url=url, **kw)

File wire/http/ext/__init__.py

Empty file added.

File wire/http/ext/hl2_client.py

+'''httplib2 wire HTTP client wrapper'''
+
+#FIXME: don't import eventlet in test mode
+try:
+    import eventlet
+    httplib2 = eventlet.import_patched('httplib2')
+except ImportError:
+    import httplib2
+
+from wire.util import lazy, lazier
+from wire.http.response import CoreResponse
+from wire.http.request import CoreRequest, CoreREST
+
+
+class Response(CoreResponse):
+
+    '''httplib2 response object'''
+
+    def __init__(self, resp, **kw):
+        response, content = resp
+        self._hset('_original', content)
+        h = self._hset('_headers', dict(response))
+        self._hset('_code', h.pop('status', '500'))
+        self._hset('url', h.pop('content-location', None))
+        super(Response, self).__init__(**kw)
+
+
+class Request(CoreRequest):
+
+    def _factory(self):
+        auth = self._auth
+        proxy = self._proxy
+        if proxy:
+            try:
+                import socks
+                h = httplib2.Http(
+                    '.cache',
+                    proxy_info=httplib2.ProxyInfo(
+                        socks.PROXY_TYPE_HTTP, proxy['server'], proxy['host'],
+                    ),
+                )
+            except ImportError:
+                raise ImportWarning(
+                    u'httplib2 proxy support requires "socks" library'
+                )
+        else:
+            h = httplib2.Http('.cache')
+        if auth: h.add_credentials(*(auth['username'], auth['password']))
+        return h
+
+    def _request(self, **kw):
+        try:
+            factory = self._requestor
+            kw, newkw, respkw = self._kwfy(kw)
+            # temporary auth
+            auth = newkw.pop('auth', self._auth)
+            if auth: factory.add_credentials(
+                *(auth['username'], auth['password'])
+            )
+            # temporary timeout
+            factory.timeout = newkw.pop('timeout', self._timeout)
+            result = factory.request(
+                newkw.pop('url'),
+                method=kw.get('method', 'GET'),
+                body=newkw.pop('data', ''),
+                headers=dict(newkw.pop('headers')),
+            )
+            return self._response(result, **respkw)
+        except httplib2.RelativeURIError, e:
+            raise ValueError(e)
+        finally:
+            # clear temporary auth
+            if auth: factory.clear_credentials()
+            # clear temporary timeout
+            factory.timeout = None
+
+    @lazy
+    def _requestor(self):
+        return self._factory()
+
+    @lazier
+    def _response(self):
+        return Response
+
+
+class RESTClient(Request, CoreREST):
+
+    '''httplib2 REST client'''

File wire/http/ext/pycurl_client.py

+'''p wire HTTP client wrapper'''
+
+from StringIO import StringIO
+from httplib import HTTPMessage
+# what pycurl needs...
+try:
+    import signal
+    from signal import SIGPIPE, SIG_IGN
+    signal.signal(SIGPIPE, SIG_IGN)
+except ImportError:
+    pass
+
+try:
+    import pycurl as p
+except ImportError:
+    raise ImportError('pycurl_client requires the "p" library')
+
+from wire.util import lazy, lazier
+from wire.http.response import CoreResponse
+from wire.http.request import CoreRequest, CoreREST
+
+
+class Request(CoreRequest):
+
+    @lazy
+    def _factory(self):
+        factory = p.Curl()
+        proxy = self._proxy
+# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY
+        if proxy:
+            factory.setopt(p.PROXY, ':'.join([proxy['server'], proxy['host']]))
+# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTFOLLOWLOCATION
+        factory.setopt(p.FOLLOWLOCATION, 1)
+# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTMAXREDIRS
+        factory.setopt(p.MAXREDIRS, 5)
+# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTCONNECTTIMEOUT
+        factory.setopt(p.CONNECTTIMEOUT, 15)
+# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTNOSIGNAL
+        factory.setopt(p.NOSIGNAL, 1)
+        # deprecated?
+# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTTRANSFERTEXT
+        factory.setopt(p.TRANSFERTEXT, 1)
+        auth = self._auth
+        if auth:
+# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTHTTPAUTH
+            factory.setopt(p.HTTPAUTH, p.HTTPAUTH_BASIC)
+# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTUSERPWD
+            factory.setopt(
+                p.USERPWD, ':'.join(auth['username'], auth['password'])
+            )
+        return factory
+
+#    @lazy
+#    def multiple(self):
+#        def func(urls, **kw):
+#            multi = p.CurlMulti()
+#            makehandler = self._makehandler
+#            num_urls = len(urls)
+#            handlers = list(makehandler() for i in xrange(urls))
+#            return func
+
+    @lazier
+    def _response(self):
+        return Response
+
+#    def _makehandler(self, **kw):
+#        factory = p.Curl()
+#        getinfo = factory.getinfo
+## http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTFOLLOWLOCATION
+#        factory.setopt(p.FOLLOWLOCATION, 1)
+## http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTMAXREDIRS
+#        factory.setopt(p.MAXREDIRS, 5)
+## http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTCONNECTTIMEOUT
+#        factory.setopt(p.CONNECTTIMEOUT, 15)
+## http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTNOSIGNAL
+#        factory.setopt(p.NOSIGNAL, 1)
+#        # deprecated?
+## http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTTRANSFERTEXT
+#        factory.setopt(p.TRANSFERTEXT, 1)
+#        # handle proxy
+#        proxy = kw.get('proxy', self._proxy)
+#        if proxy:
+#            factory.setopt(
+#                p.PROXY, ':'.join([proxy['server'], proxy['host']]),
+#            )
+#            # handle proxy auth
+#            factory.setopt(p.PROXYAUTH, p.HTTPAUTH_BASIC)
+#        # handle auth
+#        auth = kw.get('auth', self._auth)
+## http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTUSERPWD
+#        if auth:
+#            factory.setopt(p.HTTPAUTH, p.HTTPAUTH_BASIC)
+#            factory.setopt(
+#                p.USERPWD, ':'.join(auth['username'], auth['password'])
+#            )
+#        # handle timeout
+#        timeout = kw.get('timeout', self._timeout)
+#        if timeout: factory.setopt(p.TIMEOUT, timeout)
+#        data = kw.get('data', '')
+#        # handle method
+#        method = kw.get('method', 'get').lower()
+#        headers = list(
+#            ': '.join(i) for i in kw.get('headers', list(self.h))
+#        )
+#        if method == 'post':
+## http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPOST
+#            factory.setopt(p.POST, 1)
+## http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPOSTFIELDS
+#            factory.setopt(p.POSTFIELDS, data)
+#        elif method == 'put':
+#            # deprecated in favor of UPLOAD?
+## http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPUT
+#            factory.setopt(p.PUT, 1)
+## http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTUPLOAD
+#            factory.setopt(p.UPLOAD, 1)
+## http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTINFILESIZE
+#            factory.setopt(p.INFILESIZE, len(data))
+## http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTREADDATA
+#            factory.setopt(p.READDATA, data)
+## http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTNOBODY
+#        elif method == 'head':
+#            factory.setopt(p.NOBODY, 1)
+## http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTCUSTOMREQUEST
+#        elif method == 'delete':
+#            factory.setopt(p.CUSTOMREQUEST, 'DELETE')
+#        elif method == 'options':
+#            factory.setopt(p.CUSTOMREQUEST, 'OPTIONS')
+#        return factory
+
+    def _request(self, **kw):
+        try:
+            factory = self._factory
+            getinfo = factory.getinfo
+            # handle proxy
+            proxy = kw.pop('proxy', self._proxy)
+            if proxy:
+                factory.setopt(
+                    p.PROXY, ':'.join([proxy['server'], proxy['host']]),
+                )
+                # handle proxy auth
+                factory.setopt(p.PROXYAUTH, p.HTTPAUTH_BASIC)
+            # handle auth
+            auth = kw.pop('auth', self._auth)
+# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTUSERPWD
+            if auth:
+                factory.setopt(
+                    p.USERPWD, ':'.join([auth['username'], auth['password']])
+                )
+            # handle timeout
+            timeout = kw.pop('timeout', self._timeout)
+            if timeout: factory.setopt(p.TIMEOUT, timeout)
+            data = kw.pop('data', '')
+            # handle method
+            method = kw.pop('method', 'get').lower()
+            headers = list(
+                ': '.join(i) for i in kw.pop('headers', list(self.headers))
+            )
+            if method == 'post':
+# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPOST
+                factory.setopt(p.POST, 1)
+# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPOSTFIELDS
+                factory.setopt(p.POSTFIELDS, data)
+            elif method == 'put':
+                # deprecated in favor of UPLOAD?
+# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPUT
+                factory.setopt(p.PUT, 1)
+# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTUPLOAD
+                factory.setopt(p.UPLOAD, 1)
+# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTINFILESIZE
+                factory.setopt(p.INFILESIZE, len(data))
+# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTREADDATA
+                factory.setopt(p.READDATA, data)
+# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTNOBODY
+            elif method == 'head':
+                factory.setopt(p.NOBODY, 1)
+# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTCUSTOMREQUEST
+            elif method == 'delete':
+                factory.setopt(p.CUSTOMREQUEST, 'DELETE')
+            elif method == 'options':
+                factory.setopt(p.CUSTOMREQUEST, 'OPTIONS')
+            # handle headers
+            header = StringIO()
+# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTUSERAGENT
+            factory.setopt(p.USERAGENT, self.headers.user_agent)
+# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTHTTPHEADER
+            factory.setopt(p.HTTPHEADER, list(headers))
+# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTHEADERFUNCTION
+            factory.setopt(p.HEADERFUNCTION, header.write)
+            # handle data
+            body = StringIO()
+# http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTWRITEFUNCTION
+            factory.setopt(p.WRITEFUNCTION, body.write)
+            # handle url
+            factory.setopt(p.URL, kw.pop('url'))
+            factory.perform()
+# INFO: http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html
+            result = dict(
+                headers=header,
+                data=data,
+                status=getinfo(p.HTTP_CODE),
+                content_location=getinfo(p.EFFECTIVE_URL),
+            )
+        except p.error, e:
+# INFO: http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html
+            result = dict(
+                headers=header,
+                data=data,
+                status=getinfo(p.HTTP_CODE),
+                url=getinfo(p.EFFECTIVE_URL),
+                redirect_url=getinfo(p.REDIRECT_URL),
+                errors='{0} {1}'.format(e[0], e[1]),
+            )
+        finally:
+            factory.close()
+        return result
+
+
+class RESTClient(Request, CoreREST):
+
+    '''pycurl REST client'''
+
+
+class Response(CoreResponse):
+
+    '''pycurl response object'''
+
+    def __init__(self, resp, **kw):
+        # data
+        data = resp.pop('data', StringIO())
+        data.seek(0)
+        # preserve original data
+        self._original = data.read()
+        # headers...
+        headers = resp.pop('headers', StringIO())
+        headers.seek(0)
+        # clear first status line
+        headers.readline()
+        # yum...headers
+        self._headers = h = dict(i for i in HTTPMessage(headers).items())
+        # it's an int
+        self._code = str(h.pop('status', 500))
+        # final url
+        self.url = h.pop('url', None)
+        super(Response, self).__init__(**kw)
+
+
+#m = pycurl.CurlMulti()
+#m.handles = []
+#for i in range(num_conn):
+#    c = pycurl.Curl()
+#    c.fp = None
+#    c.setopt(pycurl.FOLLOWLOCATION, 1)
+#    c.setopt(pycurl.MAXREDIRS, 5)
+#    c.setopt(pycurl.CONNECTTIMEOUT, 30)
+#    c.setopt(pycurl.TIMEOUT, 300)
+#    c.setopt(pycurl.NOSIGNAL, 1)
+#    m.handles.append(c)
+#
+#
+## Main loop
+#freelist = m.handles[:]
+#num_processed = 0
+#while num_processed < num_urls:
+#    # If there is an url to process and a free curl object, add to multi stack
+#    while queue and freelist:
+#        url, filename = queue.pop(0)
+#        c = freelist.pop()
+#        c.fp = open(filename, "wb")
+#        c.setopt(pycurl.URL, url)
+#        c.setopt(pycurl.WRITEDATA, c.fp)
+#        m.add_handle(c)
+#        # store some info
+#        c.filename = filename
+#        c.url = url
+#    # Run the internal curl state machine for the multi stack
+#    while 1:
+#        ret, num_handles = m.perform()
+#        if ret != pycurl.E_CALL_MULTI_PERFORM:
+#            break
+#    # Check for curl objects which have terminated, and add them to the freelist
+#    while 1:
+#        num_q, ok_list, err_list = m.info_read()
+#        for c in ok_list:
+#            c.fp.close()
+#            c.fp = None
+#            m.remove_handle(c)
+#            print "Success:", c.filename, c.url, c.getinfo(pycurl.EFFECTIVE_URL)
+#            freelist.append(c)
+#        for c, errno, errmsg in err_list:
+#            c.fp.close()
+#            c.fp = None
+#            m.remove_handle(c)
+#            print "Failed: ", c.filename, c.url, errno, errmsg
+#            freelist.append(c)
+#        num_processed = num_processed + len(ok_list) + len(err_list)
+#        if num_q == 0:
+#            break
+#    # Currently no more I/O is pending, could do something in the meantime
+#    # (display a progress bar, etc.).
+#    # We just call select() to sleep until some more data is available.
+#    m.select(1.0)
+#
+#
+## Cleanup
+#for c in m.handles:
+#    if c.fp is not None:
+#        c.fp.close()
+#        c.fp = None
+#    c.close()
+#m.close()

File wire/http/ext/ul2_client.py

+'''urllib2 client wrapper'''
+
+import urllib2
+from collections import deque
+from stuf.utils import lazy_class
+from wire.http.client.response import CoreResponse
+
+#FIXME: don't import eventlet in test mode
+try:
+    import eventlet
+    ulib = eventlet.import_patched('urllib2')
+except ImportError:
+    ulib = urllib2
+
+from stuf import stuf
+
+
+class Response(CoreResponse):
+
+    def __init__(self, response, **kw):
+        self._original = response.read()
+        self._code = response.code
+        self._headers = response.headers
+        self.url = response.url
+        super(Response, self).__init__(**kw)
+
+
+class Request(CoreRequest):
+
+    def _factory(self, **kw):
+        if kw.pop('test', False):
+            handlers = deque([
+                ulib.HTTPHandler,
+                ulib.HTTPDefaultErrorHandler,
+                ulib.HTTPRedirectHandler,
+                ulib.FileHandler,
+                ulib.HTTPErrorProcessor,
+                ulib.HTTPSHandler,
+            ])
+            proxy = self._proxy
+            if proxy:
+                proxy_set = '{0} {1}'.format(proxy['server'], proxy['host'])
+                handlers.appendleft(
+                    ulib.ProxyHandler(dict(http=proxy_set, https=proxy_set))
+                )
+            auth = kw.pop('auth', self._auth)
+            if auth:
+                password_mgr = ulib.HTTPPasswordMgrWithDefaultRealm()
+                password_mgr.add_password(
+                    None, kw.get('url'), auth['username'], auth['password'],
+                )
+                handlers.append(ulib.HTTPBasicAuthHandler(password_mgr))
+            ulib.install_opener(ulib.build_opener(*tuple(h for h in handlers)))
+
+    @lazy_class
+    def _response(self):
+        return Response
+
+    def _request(self, **kw):
+        kw, newkw, respkw = self._kwfy(kw)
+        factory = self._factory(**newkw)
+        url = newkw.pop('url')
+        data = newkw.pop('data')
+        headers = newkw.pop('headers', dict(self.headers))
+        request = urllib2.Request(url=url, data=data, headers=headers)
+        request.get_method = lambda: kw.get('method', 'GET')
+        try:
+            return self._response(factory.open(request), **respkw)
+        except ulib.HTTPError, e:
+            return stuf(
+                read=e.fp.read,
+                code=e.code,
+                headers=e.hdrs,
+                url=e.filename,
+            )
+
+
+class RESTClient(Request, CoreREST):
+
+    '''urllib2 REST client'''

File wire/http/header.py

-'''RFC 2616 data'''
+'''wire HTTP header management'''
 
 import re
-from itertools import chain
+import Cookie as _cook
 from keyword import iskeyword
-
-from wire.util import lazy, lazycls
+from functools import partial
+from Cookie import SimpleCookie, Morsel
+from stuf.utils import lazy_class
 
 
 class HTTPHeaderException(Exception):
     '''Invalid HTTP header value'''
 
 
-class CoreHeaders(object):
+class CoreMorsel(Morsel):
 
-    '''HTTP header management'''
-
-    def __init__(self, headers=()):
-        # "Best practice" sends HTTP headers in order: general, response, entity
-        self.general, self.entity, self.response, self.custom = [], [], [], []
-        # List index position dictionaries
-        self.gtrack, self.rtrack, self.etrack, self.ctrack = {}, {}, {}, {}
-        # Populate manager with any headers passed to init
-        self._update(headers)
-
-    def __getitem__(self, key):
-        return self.get(key)
+    '''HTTP request specific morsel'''
 
     def __getattr__(self, key):
         try:
             return object.__getattribute__(self, key)
         except AttributeError:
-            tkey = self._headerattr.get(key)
-            # Cast to str to cast email.Header to str type
-            if tkey is not None:
-                value = self.get(tkey)
-                # Don't make 'None' a string
-                if value is not None: return str(value)
-                return value
-            raise AttributeError(key)
+            try:
+                return self.__getitem__(key)
+            except KeyError:
+                raise AttributeError(key)
 
-    def __contains__(self, key):
-        return self.get(key) is not None
+    def __setattr__(self, k, v):
+        if k in CoreMorsel._reserved:
+            self.__setitem__(k, v)
+        else:
+            dict.__setattr__(self, k, v)
+
+    def __delattr__(self, key):
+        try:
+            dict.__delattr__(self, key)
+        except AttributeError:
+            try:
+                self.__delitem__(key)
+            except KeyError:
+                raise AttributeError(key)
+
+
+class CoreCookie(SimpleCookie):
+
+    def __init__(self, inpt=None):
+        super(CoreCookie, self).__init__()
+        if inpt:
+            self.load(input)
+
+    def __getattr__(self, key):
+        try:
+            return dict.__getattribute__(self, key)
+        except AttributeError:
+            try:
+                return self.__getitem__(key)
+            except KeyError:
+                try:
+                    return self.__getitem__(key.replace('_', '-'))
+                except KeyError:
+                    raise AttributeError(key)
+
+    def __setattr__(self, k, v):
+        try:
+            dict.__getattribute__(self, k, v)
+            object.__setattr__(self, k, v)
+        except TypeError:
+            rval, cval = self.value_encode(v)
+            self._set(k, rval, cval)
+
+    def __delattr__(self, key):
+        try:
+            dict.__delattr__(self, key)
+        except AttributeError:
+            try:
+                self.__delitem__(key)
+            except KeyError:
+                self.__delitem__(key.replace('_', '-'))
+                raise AttributeError(key)
+
+    def __setitem__(self, key, value):
+        rval, cval = self.value_encode(value)
+        self._set(key, rval, cval)
 
     def __iter__(self):
-        # Cast any header objects strings (calling Header.decode)
-        return ((h[0], str(h[1])) for h in chain(
-            self.general, self.response, self.entity, self.custom
-        ) if h is not None)
-
-    def __len__(self):
-        return len(list(self.__iter__()))
+        return iter(list(
+            tuple(i.split(': ')) for i in str(self).splitlines())
+        )
 
     def __str__(self):
-        return str(self.items())
+        return self.output
 
-    @lazycls
-    def _badheaders(self):
-        '''Bad header detecting regex from wsgiref.'''
-        return re.compile(r'[\000-\037]')
+    def _parse(self, cookie, patt=_cook._CookiePattern):
+        i = 0  # Our starting point
+        n = len(cookie)  # Length of string
+        m = None  # current morsel
+        unquote = _cook._unquote
+        reserved = CoreMorsel._reserved
+        while 0 <= i < n:
+            # Start looking for a cookie
+            match = patt.search(cookie, i)
+            if not match: break  # No more cookies
+            k, v = match.group('key'), match.group('val').rstrip(', ')
+            i = match.end(0)
+            # Parse the key, value in case it's metainfo
+            if k[0] == '$':
+                if m: m._morsel[k[1:]] = v
+            elif k.lower() in reserved:
+                if m: m._morsel[k] = unquote(v)
+            else:
+                rval, cval = self.value_decode(v)
+                self._set(k, rval, cval)
+                m = self[k]
 
-    @lazy
-    def _classkeys(self):
-        return frozenset(
-            vars(self).keys()+self.__class__.__dict__.keys()+
-            ['general', 'entity', 'response', 'custom', 'gtrack', 'rtrack',
-            'etrack', 'ctrack']
-        )
+    def _set(self, k, real_value, coded_value):
+        m = self.get(k, CoreMorsel())
+        m.set(k, real_value, coded_value)
+        dict.__setitem__(self, k, m)
 
-    @lazycls
-    def _headerattr(self):
-        '''Python method name -> header name mapping'''
-        return dict((self._checkname(h), h) for h in self.REQ_HEADERS)
+    def load(self, data):
+        if isinstance(data, str):
+            self._parse(data)
+        else:
+            for k, v in data.iteritems(): self.__setitem__(k, v)
 
-    @lazycls
-    def _langs(self):
-        '''Language set.'''
-        from locale import locale_alias
-        return frozenset(i.replace('_', '-') for i in locale_alias)
+    def output(self):
+        return partial(super(CoreCookie, self).output, header='cookie:')
 
-    @lazycls
-    def COMMA_SEP(self):
-        # May have values that are split on the comma
-        return frozenset([
-            'cache-control', 'pragma', 'accept', 'accept-charset', 'accept-encoding',
-            'accept-language', 'allow', 'content-encoding', 'content-language',
-            'vary', 'via', 'if-match', 'if-none-match',
-        ])
 
-    @lazycls
-    def DATES(self):
-        '''Coverted to datetime fields'''
-        return frozenset([
-            'date', 'expires', 'if-modified-since',  'if-unmodified-since',
-            'if-range', 'last-modified',
-        ])
+class CoreHeaders(object):
 
-    @lazycls
-    def ENTITY(self):
-        '''HTTP entity headers'''
-        return frozenset([
-            'allow', 'content-encoding', 'content-language', 'content-length',
-            'content-location', 'content-md5', 'content-range', 'content-type',
-            'expires', 'last-modified',
-        ])
-
-    @lazycls
-    def GENERAL(self):
-        '''General HTTP headers'''
-        return frozenset([
-            'cache-control', 'connection', 'date', 'pragma', 'trailer', 'warning',
-            'transfer-encoding', 'upgrade', 'via',
-        ]) - self.HOP_BY_HOP
-
-    @lazycls
-    def HOP_BY_HOP(self):
-        return frozenset([
-            'connection', 'keep-alive', 'proxy-authenticate',
-            'proxy-authorization', 'te', 'trailer', 'transfer-encoding',
-            'upgrade',
-        ])
-
-    @lazycls
-    def METHODS(self):
-        '''HTTP methods'''
-        return frozenset([
-            'OPTIONS', 'GET', 'HEAD', 'POST', 'POST', 'DELETE', 'CONNECT',
-        ])
-
-    @lazycls
-    def PATTERNS(self):
-        _PATTERNS = dict(
-        # Integer
-        integer=r'^\d+$',
-        # URL - Brian Bothwell - regexlib.com
-        url=r'^(file|ftp|gopher|hdl|http|https|imap|mailto|mms|news|nntp|prospero|rsync|rtsp|rtspu|shttp|sip|snews|svn|svn+ssh|telnet|wais)\://([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&amp;%\$\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|localhost|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(\:[0-9]+)*(/($|[a-zA-Z0-9\.\,\?\'\\\+&amp;%\$#\=~_\-]+))*$',
-        # RFC 1123 time format validation
-        rfc1123=r'(Mon|Tue|Wed|Thu|Fri|Sat|Sun), \d{2} (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{4} \d{2}:\d{2}:\d{2} GMT',
-        # Content type HTTP header validation
-        content_type=r'bytes\s\d+-\d+/(\d+|\*)',
-        # HTTP header name check (from wsgiref)
-        http_header_name=r'^[a-zA-Z][a-zA-Z0-9\-_]*$',
-        )
-        return dict((k, re.compile(v, re.U)) for k, v in _PATTERNS.iteritems())
-
-    @lazycls
-    def REQUEST(self):
-        # HTTP request headers
-        return frozenset([
-            'accept', 'accept-charset', 'accept-encoding', 'accept-language',
-            'range', 'authorization', 'expect', 'from', 'host', 'if-match',
-            'if-modified-since', 'if-none-match', 'if-range',
-            'if-unmodified-since', 'max-forwards', 'te', 'proxy-authorization',
-            'referer',  'user-agent',
-        ]) - self.HOP_BY_HOP
-
-    @lazycls
-    def REQ_HEADERS(self):
-        '''Combined HTTP request compatible headers'''
-        return self.GENERAL | self.REQUEST | self.ENTITY
-
-    @lazycls
-    def RESPONSE(self):
-        '''HTTP response headers'''
-        return frozenset([
-            'accept-ranges', 'age', 'etag', 'location', 'proxy-authenticate',
-            'retry-after', 'server', 'vary', 'www-authenticate',
-        ]) - self.HOP_BY_HOP
-
-    @lazycls
-    def RES_HEADERS(self):
-        '''Combined HTTP response compatible headers'''
-        return self.GENERAL | self.RESPONSE | self.ENTITY
+    '''HTTP header manager'''
 
     @staticmethod
     def _checkname(name):
-        '''Ensures a string is a legal Python name.
+        '''
+        Ensures string is legal Python name.
 
-        @param name Name to check
+        @param: name Name to check
         '''
         # Remove characters that are illegal in a Python name
-        IC = '()[]{}@,:.`=;+-*/%&|^><\'"#\\$?!~'
         name = name.lower().replace('-', '_').replace(
             '.', '_'
-        ).translate(None, IC)
+        ).translate(None, '()[]{}@,:.`=;+-*/%&|^><\'"#\\$?!~')
         # Add _ if value is a Python keyword
-        if iskeyword(name): return name + '_'
-        return name
+        return name + '_' if iskeyword(name) else name
 
-    def _getheaders(self, key):
-        '''Gets the list and tracker for a header's category.
-
-        @param key Header key
-        '''
-        if key in self.GENERAL:
-            return self.general, self.gtrack
-        elif key in self.RESPONSE:
-            return self.response, self.rtrack
-        elif key in self.ENTITY:
-            return self.entity, self.etrack
-        return self.custom, self.ctrack
+    @lazy_class
+    def _headattr(self):
+        '''Python name -> header name mapping'''
+        return dict((self._checkname(h), h) for h in self.REQ_HEADERS)
 
     def _items(self):
         '''All the header fields and values'''
         '''A list of all header field keys'''
         return list(i[0] for i in self)
 
+    def _parseint(self, value):
+        '''
+        Parses an integer from a header value.
+
+        @param: value Integer value
+        '''
+        return int(value) if self.isinteger(value) else value
+
     @classmethod
-    def _regexvalidate(cls, key, data):
-        '''Validates data by key.'''
-        if cls.PATTERNS[key].search(data): return True
-        return False
-
-    @lazycls
-    def _rules(self):
-        '''Validation rules for individual header fields.'''
-        return {
-            # "Allow" values must be valid HTTP methods
-            'allow': (
-                lambda v: v.upper() not in self.METHODS,
-                '"Allow" value must be valid HTTP method'
-            ),
-            # "Content-Encoding" value must be zip, compress, or deflate
-            'content-encoding': (
-                lambda v: v.lower() not in self.ENCODINGS,
-                '"Content-Encoding" must be gzip, compress, deflate',
-            ),
-            # "Content-Language" values must be valid language codes
-            'content-language': (
-                lambda v: v.lower() not in self._langs,
-                'Unknown language code'
-            ),
-            # "Content-Length" value must be an integer
-            'content-length': (
-                lambda v: not v.isdigit(),
-                '"Content-Length" must be integer'
-            ),
-            # "Content-Range" value must be in the proper format
-            'content-range': (
-                lambda v: not self.iscontentrange(v),
-                'Invalid format for "Content-Range"'
-            ),
-        }
-
-    def _setitem(self, key, value):
-        key = key.lower().replace('_', '-')
-        # Validate header value
-        self._validate(key, value)
-        headers, headtrack = self._getheaders(key)
-        position = headtrack.get(key)
-        if position is not None:
-            prev = headers[position][1]
-            # email.Header instance
-            try:
-                prev.append(value)
-            except AttributeError:
-                # Join with any existing value if header allows multiple values
-                if key in self.COMMA_SEP or key not in self.RES_HEADERS:
-                    headers[position] = (key, ', '.join([prev, value]))
-                # Replace value on headers allowing only single values
-                else:
-                    headers[position] = ((key, value))
-        # Add new value if not present
-        else:
-            headers.append((key, value))
-            headtrack[key] = len(headers) - 1
+    def _regeval(cls, key, data):
+        '''Validates data by key'''
+        return True if cls.PATTERNS[key].search(data) else False
 
     def _values(self):
         '''A list of all header values'''
         return list(i[1] for i in self)
 
-    def _update(self, headers=(), **kw):
-        setitem = self._setitem
-        if kw:
-            for k, v in kw.iteritems(): setitem(k, v)
-        if headers:
-            for h in headers: setitem(h[0], h[1])
+    @lazy_class
+    def _COMMA_SEP(self):
+        '''May have values that split on the comma'''
+        return frozenset([
+            'accept', 'accept-charset', 'accept-encoding', 'accept-language',
+            'allow', 'cache-control', 'content-encoding', 'content-language',
+            'if-match', 'if-none-match', 'pragma', 'vary','via',
+        ])
 
-    def get(self, key, default=None):
-        '''Get the value for "key" or return "default".
+    @lazy_class
+    def _DATES(self):
+        '''Coverted to datetime fields'''
+        return frozenset([
+            'date', 'expires', 'if-modified-since',  'if-unmodified-since',
+            'if-range', 'last-modified', 'retry-after',
+        ])
 
-        @param key HTTP header key
-        @param key HTTP header value
-        '''
-        key = key.lower()
-        headers, headtrack = self._getheaders(key)
-        if key in headtrack:
-            try:
-                return headers[headtrack[key]][1]
-            # Value is None so pass
-            except TypeError: pass
-        return default
+    @lazy_class
+    def _ENCODINGS(self):
+        '''"content-encoding" compatible values'''
+        return frozenset(['gzip', 'compress', 'deflate'])
+
+    @lazy_class
+    def _ENTITY(self):
+        '''HTTP entity headers'''
+        return frozenset([
+            'allow', 'content-encoding', 'content-language', 'content-length',
+            'content-location', 'content-md5', 'content-range', 'content-type',
+            'expires', 'last-modified', 'cookie',
+        ])
+
+    @lazy_class
+    def _GENERAL(self):
+        '''General HTTP headers'''
+        return frozenset([
+            'cache-control', 'connection', 'date', 'pragma', 'trailer', 'via',
+            'transfer-encoding', 'upgrade', 'warning',
+        ])
+
+    @lazy_class
+    def _HOP_BY_HOP(self):
+        '''hop by hop headers'''
+        return frozenset([
+            'connection', 'keep-alive', 'proxy-authenticate', 'upgrade',
+            'proxy-authorization', 'te', 'trailer', 'transfer-encoding',
+        ])
+
+    @lazy_class
+    def _HTTPIF(self):
+        '''HTTP if tests'''
+        return frozenset([
+            'if-match', 'if-none-match',  'if-modified-since', 'if-rangeset',
+            'if-unmodified-since',
+        ])
+
+    @lazy_class
+    def _METHODS(self):
+        '''HTTP methods'''
+        return frozenset([
+            'OPTIONS', 'GET', 'HEAD', 'POST', 'POST', 'DELETE', 'CONNECT',
+        ])
+
+    @lazy_class
+    def _PATTERNS(self):
+        '''relevant HTTP validators'''
+        _PATTERNS = dict(
+        # Integer
+        integer=r'^\d+$',
+        # URL - Brian Bothwell - regexlib.com
+        url=r'^(file|ftp|gopher|hdl|http|https|imap|mailto|mms|news|nntp|'
+            r'prospero|rsync|rtsp|rtspu|shttp|sip|snews|svn|svn+ssh|telnet|'
+            r'wais)\://([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&amp;%\$\-]+)*@)*((25[0'
+            r'-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0'
+            r'-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.'
+            r'(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)'
+            r'\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])'
+            r'|localhost|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.(com|edu|gov|int|'
+            r'mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))'
+            r'(\:[0-9]+)*(/($|[a-zA-Z0-9\.\,\?\'\\\+&amp;%\$#\=~_\-]+))*$',
+        # RFC 1123 time format validation
+        rfc1123=r'(Mon|Tue|Wed|Thu|Fri|Sat|Sun), \d{2} (Jan|Feb|Mar|Apr|May|Jun'
+            r'|Jul|Aug|Sep|Oct|Nov|Dec) \d{4} \d{2}:\d{2}:\d{2} GMT',
+        # Content type HTTP header validation
+        content_type=r'bytes\s\d+-\d+/(\d+|\*)',
+        # HTTP header name check (from wsgiref)
+        http_header_name=r'^[a-zA-Z][a-zA-Z0-9\-_]*$',
+        )
+        return dict((k, re.compile(v, re.U)) for k, v in _PATTERNS.iteritems())
+
+    @lazy_class
+    def _REQUEST(self):
+        '''HTTP request headers'''
+        return frozenset([
+            'accept', 'accept-charset', 'accept-encoding', 'accept-language',
+            'range', 'authorization', 'expect', 'from', 'host', 'if-match',
+            'if-modified-since', 'if-none-match', 'if-range', 'user-agent',
+            'if-unmodified-since', 'max-forwards', 'te', 'proxy-authorization',
+            'referer',
+        ])
+
+    @lazy_class
+    def _REQ_HEADERS(self):
+        '''Combined HTTP request compatible headers'''
+        return self.GENERAL | self.REQUEST | self.ENTITY
+
+    @lazy_class
+    def _RESPONSE(self):
+        '''HTTP response headers'''
+        return frozenset([
+            'accept-ranges', 'age', 'etag', 'location', 'proxy-authenticate',
+            'retry-after', 'server', 'set-cookie', 'vary', 'www-authenticate',
+        ])
+
+    @lazy_class
+    def _RES_HEADERS(self):
+        '''Combined HTTP response compatible headers'''
+        return self.GENERAL | self.RESPONSE | self.ENTITY
+
+    @lazy_class
+    def _SC_SEP(self):
+        '''May have values that are split on semi-colon'''
+        return frozenset([
+            'accept', 'accept-charset', 'accept-encoding', 'accept-language',
+        ])
 
     @classmethod
-    def iscontentrange(cls, data):
-        '''Validates a HTTP content range header.'''
-        return cls._regexvalidate('content_type', data)
+    def _iscontentrange(cls, data):
+        '''Validates a HTTP content range header'''
+        return cls._regeval('content_type', data)
 
     @classmethod
-    def ishttpname(cls, data):
-        '''Validates data is a correct HTTP header name.'''
-        return cls._regexvalidate('http_header_name', data)
+    def _ishttpname(cls, data):
+        '''Validates data is a correct HTTP header name'''
+        return cls._regeval('http_header_name', data)
 
     @classmethod
-    def isinteger(cls, data):
-        '''Validates data is an integer.'''
-        return cls._regexvalidate('integer', data)
+    def _isinteger(cls, data):
+        '''Validates data is an integer'''
+        return cls._regeval('integer', data)
 
     @classmethod
-    def isrfc1123(cls, data):
-        '''RFC1123 compliance checker e.g. Wed, 03 Oct 2007 23:59:24 GMT.'''
-        return cls._regexvalidate('rfc1123', data)
+    def _isrfc1123(cls, data):
+        '''RFC1123 compliance checker e.g. "Wed, 03 Oct 2007 23:59:24 GMT"'''
+        return cls._regeval('rfc1123', data)
 
     @classmethod
-    def isurl(cls, data):
-        '''Validates data is a valid URL.'''
-        return cls._regexvalidate('url', data)
-
-    _ch_badheaders = _badheaders
-    _ch_classkeys = _classkeys
-    _ch_contains = __contains__
-    _ch_get = get
-    _ch_getattr = __getattr__
-    _ch_getitem = __getitem__
-    _ch_items = _items
-    _ch_iter = __iter__
-    _ch_init = __init__
-    _ch_langs = _langs
-    _ch_len = __len__
-    _ch_keys = _keys
-    _ch_setitem = _setitem
-    _ch_str = __repr__ = __str__
-    _ch_update = _update
-    _ch_values = _values
+    def _isurl(cls, data):
+        '''Validates data is a valid URL'''
+        return cls._regeval('url', data)
+    
+    def final(self):
+        return '\r\n'.join(': '.join([k, v]) for k, v in self)

File wire/http/keys/__init__.py

+# -*- coding: utf-8 -*-
+'''http client keys'''

File wire/http/keys/request.py

+# -*- coding: utf-8 -*-
+#@PydevCodeAnalysisIgnore
+#pylint: disable-msg=e0211,e0213
+'''http client keys'''
+
+from appspace.keys import AppspaceKey
+
+
+class KRequest(AppspaceKey):
+
+    '''HTTP client key'''
+
+    def __call__(*urls):
+        '''
+        load urls
+    
+        @param urls: Uniform Resource Locations (URLs)
+        '''
+
+    def get():
+        '''
+        *GET* HTTP method
+    
+        http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3
+        '''
+
+    def post():
+        '''
+        *POST* HTTP method
+    
+        http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5
+        '''
+
+    def delete():
+        '''
+        *DELETE* HTTP method
+    
+        http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.7
+        '''
+
+    def put():
+        '''
+        *PUT* HTTP method
+    
+        http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.6
+        '''
+
+    def head():
+        '''
+        *HEAD* HTTP method
+    
+        http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4
+        '''