Commits

Lynn Rees committed 3b8d89f

- cleanup
- test infrastructure
- throw away stdlib cookie stuff

  • Participants
  • Parent commits 9b1f0ea
  • Branches pu

Comments (0)

Files changed (18)

 .settings/*
 *.cache/*
 syntax: regexp
-^unfinished/snippet\.py$
+^unfinished/snippet\.py$
+syntax: regexp
+^support\.py$
+'''wire fabfile'''
+
+from fabric.api import prompt, local, settings, env
+
+
+def _test(val):
+    truth = val in ['py26', 'py27', 'py32']
+    if truth is False:
+        raise KeyError(val)
+    return val
+
+
+def tox():
+    '''test wire'''
+    local('tox')
+
+
+def tox_recreate():
+    '''recreate wire test env'''
+    prompt(
+        'Enter testenv: [py26, py27, py32]',
+        'testenv',
+        validate=_test,
+    )
+    local('tox --recreate -e %(testenv)s' % env)
+
+
+def release():
+    '''release wire'''
+    local('hg update pu')
+    local('hg update next')
+    local('hg merge pu; hg ci -m automerge')
+    local('hg update maint')
+    local('hg merge default; hg ci -m automerge')
+    local('hg update default')
+    local('hg merge next; hg ci -m automerge')
+    local('hg update pu')
+    local('hg merge default; hg ci -m automerge')
+    prompt('Enter tag', 'tag')
+    with settings(warn_only=True):
+        local('hg tag "%(tag)s"' % env)
+        local('hg push ssh://hg@bitbucket.org/lcrees/wire')
+        local('hg push git+ssh://git@github.com:kwarterthieves/wire.git')
+#    local('python setup.py register sdist --format=bztar,gztar,zip upload')
 except ImportError:
     from distutils.core import setup
 
-install_requires = ['urllib3', 'stuf', 'omnijson']
+install_requires = ['urllib3', 'stuf>=0.8.7', 'omnijson', 'cookies']
 if sys.version_info <= (2, 7):
     install_requires.append('importlib')
 
+[tox]
+envlist = py26,py27,py32
+
+[testenv]
+deps=
+  nose
+  coverage
+commands=
+  nosetests {posargs:--with-coverage}
+  
+[testenv:py26]
+deps=
+  unittest2
+  nose
+  coverage
+commands=
+  nosetests {posargs:--with-coverage}

File wire/__init__.py

+# -*- coding: utf-8 -*-
+'''wire'''
+
+__version__ = (0, 8, 8)

File wire/datalet.py

 '''wire datalet'''
 
 from twoq.lazy.mixins import AutoQMixin
+from callchain.lazy.mixins import LazyChainlet
 from callchain.mixin.reset import ResetLocalMixin
-from callchain.assembly.chainlet import CallChainletQ
 
 
 class Package(ResetLocalMixin):
         self.charset = charset
 
 
-class datalet(CallChainletQ, AutoQMixin):
+class datalet(LazyChainlet, AutoQMixin):
 
     def __init__(self, root):
         '''
         super(datalet, self).__init__(root)
         self._params = root._params
 
-    def _process(self, encoder):
-        data = self._data
-        length = len(data)
-        dpop = data.popleft
-        dappend = data.append
-        encode = self.encode
-        while length:
-            dappend(encode(encoder(dpop())))
-            length -= 1
-        return self
-
 
 class talklet(datalet):
 
 
     def _encode(self, data):
         return Package(data, self.mime, self.charset)
+
+    def _process(self, encoder):
+        with self._sync as _sync:
+            _encode = self._encode
+            _sync(_encode(encoder(d)) for d in _sync.iterable)
+        return self

File wire/http/errors.py

 
 class OffsetError(Exception):
 
-    '''invalide offset'''
+    '''invalid offset'''
 
 
 class HTTPHeaderException(Exception):
 
-    '''Invalid HTTP header value'''
+    '''invalid HTTP header value'''

File wire/http/request/client.py

 
 from appspace.keys import appifies
 from callchain.internal import inside
+from callchain.assembly.chain import ChainQ
 from twoq.lazy.mixins import AutoResultMixin
 from stuf.utils import OrderedDict, lazy_class
-from callchain.assembly.chain import CallChainQ
 
 from wire.http.request.apps import apps
 from wire.http.request.collection.client import Request
 
 @appifies(KRequestClient)
 @inside(apps)
-class http(CallChainQ, AutoResultMixin):
+class http(ChainQ, AutoResultMixin):
 
     '''HTTP client call chain'''
 
         @param defaults: default settings (default: None)
         '''
         super(http, self).__init__(pattern, required, defaults, **kw)
-        # files demand multipart as default otherwise go with urlencode
-        _setdefault = self._setdefault
+        _setdefault = self._m_setdefault
         _defaults = self.defaults
-        # allow redirects
+        # allow redirects for request
         _setdefault('_redirects', kw.get('redirects'), _defaults.redirects)
         # cert verification
         _setdefault('_verify', kw.get('verify'), _defaults.verify)
-        # default timeout
+        # default request timeout
         _setdefault('_timeout', kw.get('timeout'), _defaults.timeout)
-        # use random user agent
+        # use random user agent identifier
         _setdefault('_randomua', kw.pop('randomua'), _defaults.random_ua)
         # authentication config
         _setdefault('_auth', _defaults.authentication)
         # proxy config
         _setdefault('_proxy', _defaults.proxy)
-        # headers
+        # headers store
         self._headers = OrderedDict()
-        # cookies
+        # cookies stub
         self._cookies = None
 
     def __getattr__(self, key):
         return partial(
             self._pack, key
-        ) if key.lower() in self._METHODS else self._fgetr(key)
+        ) if key.lower() in self._METHODS else self._f_getattr(key)
 
-    _hgetter = __getattr__
+    _h_gettar = __getattr__
+
+    ###########################################################################
+    ## http configuration #####################################################
+    ###########################################################################
+
+    def __call__(self, *urls, **config):
+        '''
+        load URLs
+
+        @param *urls: Uniform Resource Locators (URLs)
+        '''
+        # default timeout
+        timeout = config.pop('timeout', self._timeout)
+        # authentication configuration
+        auth = config.pop('auth', self._auth)
+        if auth:
+            self.auth(*auth)
+        # use random user agent identifier
+        randomua = config.get('randomua', self._randomua)
+        # proxy configuration
+        proxy = config.pop('proxy', self._proxy)
+        if proxy:
+            self.proxy(*proxy)
+        # allow redirects
+        redirects = self._redirects
+        # verify certs
+        verify = self._verify
+        # call chain
+        chain = self._cchain
+        # requester
+        useragent = self._h_useragent
+        # urls, if any
+        self._chain.extend(chain(Request(
+            u,
+            useragent,
+            verify,
+            redirects,
+            timeout,
+            auth,
+            proxy,
+            randomua,
+        )) for u in urls)
+        return self._dcall()
+
+    _h_call = __call__
+
+    def auth(self, username, password):
+        '''
+        set HTTP request authentication settings
+
+        @param username: usernameish credential
+        @param password: passwordish credential
+        '''
+        self._auth = (username, password)
+        return self
+
+    _hauth = auth
+
+    def proxy(self, server, host):
+        '''
+        set HTTP request proxy settings
+
+        @param server: proxy server
+        @param host: proxy host
+        '''
+        self._proxy = (server, host)
+        return self
+
+    _hproxy = proxy
 
     ###########################################################################
     ## internal management ####################################################
 
     def clear(self):
         '''clear all queues'''
-        self._resetdefaults()
+        self._m_defaults()
         self._qclear()
         return self
 
         '''
         self._headers = link._headers
         self._cookies = link._cookies
-        self._data = link._data
         self._files = link._files
         self._params = link._params
-        return self._rback(link)
+        return self._qback(link)
 
     _hback = back
 
             'OPTIONS', 'GET', 'HEAD', 'POST', 'POST', 'DELETE', 'CONNECT',
         ])
 
-    _hMETHODS = _METHODS
+    @property
+    def _useragent(self):
+        '''http user agent'''
+        return self.M.get(self.G.useragent, self.G.userspace)
 
-    @property
-    def _requester(self):
-        '''fetch http requester'''
-        return self.M.get(self.G.request, self.G.userspace)
-
-    _hrequester = _requester
+    _h_useragent = _useragent
 
     def _pack(self, method):
         '''
         (self.args(method).invoke('method')
         .args(self._headers).invoke('headers')
         .args(self._cookies).invoke('cookies')
-        .args(self._data).invoke('data')
         .args(self._params).invoke('params')
         .args(self._files).invoke('files'))
         with self._sync as sync:
     _hpack = _pack
 
     def content_md5(self):
-        '''
-        take MD5 hash of data and set *CONTENT-MD5* HTTP header on items
-
-        @param value: HTTP response data
-        '''
-        self.args(self._headers).invoke('content_md5')
+        '''take MD5 hash of data and set *CONTENT-MD5* HTTP header'''
+        self.invoke('content_md5')
         return self
-
-    ###########################################################################
-    ## http configuration #####################################################
-    ###########################################################################
-
-    def __call__(self, *urls, **config):
-        '''
-        load urls
-
-        @param *urls: Uniform Resource Locations (URLs)
-        '''
-        allow_redirects = self._allow_redirects
-        # verify certs
-        verify = self._verify
-        # default timeout
-        timeout = config.pop('timeout', self._timeout)
-        # authentication
-        auth = config.pop('auth', self._auth)
-        if auth:
-            self.auth(*auth)
-        randomua = config.get('randomua', self._randomua)
-        # proxy
-        proxy = config.pop('proxy', self._proxy)
-        if proxy:
-            self.proxy(*proxy)
-        # urls, if any
-        chain = self.chain
-        requestor = self._requestor
-        self._chain.extend(chain(Request(
-            u,
-            requestor,
-            verify,
-            allow_redirects,
-            timeout,
-            auth,
-            proxy,
-            randomua,
-        )) for u in urls)
-        return self._dcall()
-
-    _hcall = __call__
-
-    def auth(self, username, password):
-        '''
-        set authentication settings
-
-        @param username: usernameish credential
-        @param password: passwordish credential
-        '''
-        self._auth = (username, password)
-        return self
-
-    _hauth = auth
-
-    def proxy(self, server, host):
-        '''
-        set proxy settings
-
-        @param server: proxy server
-        @param host: proxy host
-        '''
-        self._proxy = (server, host)
-        return self
-
-    _hproxy = proxy

File wire/http/request/collection/client.py

 
 import hashlib
 
-from stuf.six import items
-from stuf.utils import OrderedDict
+from stuf.six import items, values
 from callchain.mixin.reset import ResetTypeMixin
 
 from wire.support import base64_encode
+from wire.http.request.collection.header import Headers
 
 
 class Request(ResetTypeMixin):
 
     '''HTTP request collection'''
 
-    def __init__(self, requester, url, **kw):
+    def __init__(self, useragent, url, **kw):
         '''
         init
 
+        @param useragent: user agent callable
         @param url: url for request
         @param verify: verify request
         @param allow_redirects: allow redirects in request
         # url for request
         self.url = url
         # requestor
-        self._requester = requester
+        self._useragent = useragent
         # verify request
         self._verify = kw.pop('verify', False)
         # allow redirects
-        self._allow_redirects = kw.pop('allow_redirects', True)
+        self._redirects = kw.pop('redirects', True)
         # timeout
         self._timeout = kw.pop('timeout', 10)
         # authentication configuration
         # files stub
         self._files = None
         # headers store
-        self._headers = OrderedDict()
+        self._headers = Headers()
         # parameters stub
         self._params = None
         self._method = None
     @property
     def args(self):
         '''arguments for requester'''
+        self._headers.set('cookie', '; '.join(
+            c.render_request(prefix='') for c in values(self._cookies)
+        ))
         return dict(
             verify=self._verify,
-            allow_redirects=self._allow_redirects,
+            allow_redirects=self._redirects,
             timeout=self._timeout,
             auth=self._auth,
             proxy=self._proxy,
-            cookies=self._cookies,
             data=self._data,
             files=self._files,
-            headers=self._headers,
+            headers=dict(i for i in self._headers),
             params=self._params,
         )
 
     def __call__(self):
-        return self._requester(self._method, self.url, **self.args)
+        return self._useragent(self._method, self.url, **self.args)
 
     def cookies(self, cookies):
         '''

File wire/http/request/collection/cookie.py

-# -*- coding: utf-8 -*-
-'''wire HTTP request cookie collection'''
-
-# pylint: disable-msg=f0401
-from stuf.six.moves import http_cookies  # @UnresolvedImport
-# pylint: enable-msg=f0401
-
-from wire.support import n2b
-
-
-class Cookie(http_cookies.SimpleCookie):
-
-    '''HTTP request cookie'''
-
-    def set(self, key, field, value):
-        '''
-        set cookie `field`, `value`
-
-        @param key: cookie (or morsel) name
-        @param field: cookie field value
-        @param value: putative header value
-        '''
-        # fetch existing or new cookie morsel
-        m = self.get(key, http_cookies.Morsel())
-        # set existing cookie fields
-        if key.lower() in m._reserved:
-            m[field] = http_cookies._unquote(value)
-        # update existing value
-        else:
-            real_value, coded_value = self.value_decode(value)
-            m.set(field, real_value, coded_value)
-        # save morsel
-        dict.__setitem__(self, key, m)
-
-    _cset = set
-
-    def dumps(self):
-        '''dump cookie in HTTP header form'''
-        return n2b(self.output(header='cookie:'))
-
-    _cdumps = dumps

File wire/http/request/collection/header.py

 from datetime import datetime
 from email.header import Header
 
-from stuf.utils import OrderedDict, lazy_class
+from stuf.utils import OrderedDict, lazy_class, exhaustmap
 from twoq.support import isstring, isunicode, items
 
 from wire.support import n2b
             items(self._entity),
             # 4. custom headers
             items(self._custom),
-        ) if k is not None)
+        ))
 
     ###########################################################################
     ## header definitions #####################################################
             n('Mozilla/4.0 (compatible; MSIE 6.1; Windows XP)'),
         ])
 
-    _hrandom = _randomua
+    _h_random = _randomua
 
     ###########################################################################
     ## header validation ######################################################
         if err_msg is not None:
             raise HTTPHeaderException(err_msg)
 
-    _hvalidate = _validate
+    _h_validate = _validate
 
     ###########################################################################
     ## header retrieval #######################################################
         # 4. custom headers
         return self._custom
 
-    _hgetheaders = _getheaders
+    _h_getheaders = _getheaders
 
     def get(self, key):
         '''
         del self.get(key)[self._key(key)]
 
     _hdelete = delete
+
+    def update(self, headers):
+        '''
+        update `headers` with `headers`
+
+        @param headers: other headers
+        '''
+        exhaustmap(headers, self._hset)
+
+    _hupdate = update

File wire/http/request/cookie.py

-# -*- coding: utf-8 -*-
-'''wire HTTP request cookie management'''
-
-from stuf.utils import exhaustmap
-from appspace.keys import appifies
-
-from wire.http.mixins import httplet
-from wire.http.request.collection.cookie import Cookie
-from wire.http.request.keys.cookie import KRequestCookie
-
-
-@appifies(KRequestCookie)
-class RequestCookie(httplet):
-
-    '''HTTP request cookie setter'''
-
-    def __init__(self, root):
-        '''
-        init
-
-        @param root: root object
-        '''
-        super(RequestCookie, self).__init__(root)
-        # initialize cookies
-        self._cookies = self._synchback(
-            '_cookies', Cookie() if self._cookies is None else self._cookies,
-        )
-
-    def cookie(self, key, **values):
-        '''
-        set cookie values
-
-        @param key: cookie key
-        @param **values: header values
-        '''
-        setr = self._cookies.set
-        exhaustmap(values, lambda x, y: setr(key, x, y))
-        return self
-
-    _ccookie = cookie

File wire/http/request/data.py

 from appspace.keys import appifies
 from callchain.internal import inside
 
-from wire.support import n2b
+from wire.support import n2b, n2u
 
 from wire.http.mixins import httplet
 from wire.http.request.apps import apps
     '''HTTP request data manager'''
 
     def __init__(self, root):
+        '''
+        init
+
+        @param root: root object
+        '''
         super(RequestData, self).__init__(root)
-        # data
-        self._data = deque() if self._data is None else self._data
-        # files
-        self._files = deque()
-        # params
-        self._params = deque()
+        self._params = deque() if self._params is None else self._params
 
     def bytes(self):
-        '''
-        convert data to bytes
-        '''
+        '''convert data to bytes'''
         return self._process(n2b)
 
     def data(self, *data):
         '''
-        `data` for urls (must equal number of previously passed urls)
+        `data` for urls
 
         param *data: data for request body
+
+        either send one data chunk to multiple URLs or send one data chunk or
+        every one URL
         '''
-        if len(data) == len(self):
-            self._data.extend(data)
-        elif len(self) == 1:
-            self._data.extend(data)
+        length = len(self)
+        if len(data) == length or length == 1:
+            with self._sync() as _sync:
+                _sync(data)
         else:
-            raise OffsetError(
-                'number of data items does not match number of URLs'
-            )
+            raise OffsetError('{} data items does not fit {} URLs'.format(
+                len(data), len(self),
+            ))
         return self
 
     def files(self, *files):
         '''
-        `files` for urls (must equal number of previously passed urls)
+        `files` to upload via HTTP request
 
-        param *data: data for request body
+        param *files: files data to upload
+
+        either upload one file to multiple URLs or upload one file for every
+        one URL
         '''
-        if len(files) == len(self):
-            self._files.extend(files)
-        elif len(self) == 1:
-            self._files.extend(files)
+        length = len(self)
+        if len(files) == length or length == 1:
+            with self._sync() as _sync:
+                _sync(files)
         else:
-            raise OffsetError(
-                'number of file items does not match number of URLs'
-            )
+            raise OffsetError('{} file items does not fit {} URLs'.format(
+                len(files), len(self),
+            ))
         return self
 
     def params(self, *params):
         '''
-        request parameters for all urls
+        request parameters for urls
 
-        param **params: parameters
+        param *params: parameters to send with HTTP request
+
+        either use one set of parameters for multiple URLs or one set of
+        parameters for every one URL
         '''
-        if len(params) == len(self):
-            self._params.extend(params)
-        elif len(self) == 1:
+        length = len(self)
+        if len(params) == length or length == 1:
             self._params.extend(params)
         else:
-            raise OffsetError(
-                'number of parameter items does not match number of URLs'
-            )
+            raise OffsetError('{} parameter items does not fit {} URLs'.format(
+                len(params), len(self),
+            ))
         return self
+
+    def unicode(self):
+        '''convert data to unicode'''
+        return self._process(n2u)

File wire/http/request/header.py

 
 from stuf.utils import lazy_class
 from appspace.keys import appifies
+from cookies import Cookies, Cookie
 from appspace.utils import checkname
 
-from wire.support import n2b
-
 from wire.http.mixins import httplet
 from wire.http.request.collection.header import Headers
 from wire.http.request.keys.header import KRequestHeaders
 
-n = n2b
-
 
 @appifies(KRequestHeaders)
 class RequestHeaders(httplet):
 
-    '''HTTP request header setter'''
+    '''HTTP request header configuration'''
 
     def __init__(self, root):
         '''
         self._headers = self._synchback(
             '_headers', Headers() if self._headers is None else self._headers,
         )
+        self._cookies = self._synchback(
+            '_cookies', Cookies() if self._cookies is None else self._cookies,
+        )
         if self.G.randomua:
             # set random user agent
             self.user_agent(self._randomua())
 
     def __getattr__(self, key):
         try:
-            return object.__getattribute__(self, key)
-        except AttributeError:
             tkey = self._headmap.get(key)
             # cast to native string to cast email.Header to native string type
             if tkey is not None:
                 value = self._headers.get(tkey)
                 # don't make `None` a string
                 if value is not None:
+                    # set header with partial
                     return partial(self.bheader, key)
-            return self._fgetr(key)
+            raise AttributeError()
+        except AttributeError:
+            return self._f_getattr(key)
 
-    _hgetattr = __getattr__
+    _h_getattr = __getattr__
 
     @lazy_class
     def _headmap(self):
         '''python name -> header name mapping'''
         return dict((checkname(h), h) for h in self._headers.REQ_HEADERS)
 
+    def bheader(self, key, value):
+        '''
+        set HTTP request header to `bytes` (native string) `value`
+
+        @param key: header key
+        @param value: header value
+        '''
+        self._headers.set(key, value)
+        return self
+
+    header = _hheader = bheader
+
+    def cookie(self, key, value, **attrs):
+        '''
+        set cookie values
+
+        @param key: cookie key
+        @param **values: header values
+        '''
+        self._cookies.add(Cookie(key, value, **attrs))
+        return self
+
+    _ccookie = cookie
+
     def content_range(self, first, last, length='*'):
         '''
         set rangeset for HTTP header *CONTENT-RANGE*
         @param length: instance length
         '''
         if length == '*':
-            self._headers.set('content-range', 'bytes %d-%d/*' % (first, last))
+            self._headers.set(
+                'content-range', 'bytes {}-{}/*'.format(first, last)
+            )
         else:
             self._headers.set(
-                'content-range', 'bytes %d-%d/%d' % (first, last, length)
+                'content-range', 'bytes {}-{}/{}'.format(first, last, length)
             )
         return self
 
             self._headers.set('Content-Type', mime)
         else:
             self._headers.set(
-                'Content-Type', '{0}; charset={1}'.format(mime, charset),
+                'Content-Type', '{}; charset={}'.format(mime, charset),
             )
         return self
 
-    def bheader(self, key, value):
-        '''
-        set HTTP request header to `bytes` (native string) `value`
-
-        @param key: header key
-        @param value: header value
-        '''
-        self._headers.set(key, value)
-        return self
-
-    header = _hheader = bheader
+    _hcontent_type = content_type
 
     def uheader(self, key, value, charset=None):
         '''
-        set HTTP request header to `unicode` `value`
+        set a HTTP request header to unicode `value`
 
         @param key: header key
         @param value: header value

File wire/http/request/keys/client.py

         @param urls: Uniform Resource Locations (URLs)
         '''
 
+    def content_md5():
+        '''take MD5 hash of data and set *CONTENT-MD5* HTTP header'''
+
     def get():
         '''
         *GET* HTTP method

File wire/http/request/keys/cookie.py

-# -*- coding: utf-8 -*-
-#@PydevCodeAnalysisIgnore
-#pylint: disable-msg=e0211,e0213
-'''http request cookie keys'''
-
-from appspace.keys import AppspaceKey
-
-
-class KRequestCookie(AppspaceKey):
-    
-    '''HTTP request cookie setter'''
-
-    def cookie(key, **values):
-        '''
-        cookie setter
-        
-        @param key: cookie key
-        @param **values: cookie field names, values
-        '''

File wire/http/request/keys/data.py

 
     '''HTTP client key'''
 
+    def bytes():
+        '''convert data to bytes'''
+
     def data(*data):
         '''
-        `data` for urls (must equal number of previously passed urls)
+        `data` for urls
 
         param *data: data for request body
+
+        either send one data chunk to multiple URLs or send one data chunk or
+        every one URL
         '''
 
     def files(*files):
         '''
-        `files` for urls (must equal number of previously passed urls)
+        `files` to upload via HTTP request
 
-        param *files: files for request
+        param *files: files data to upload
+
+        either upload one file to multiple URLs or upload one file for every
+        one URL
         '''
 
     def params(*params):
         '''
-        request parameters for all urls
+        request parameters for urls
 
-        param *params: parameters for request
+        param *params: parameters to send with HTTP request
+
+        either use one set of parameters for multiple URLs or one set of
+        parameters for every one URL
         '''
 
-    def content_md5():
-        '''take MD5 hash of data and set *CONTENT-MD5* HTTP header'''
+    def unicode():
+        '''convert data to unicode'''

File wire/http/request/keys/header.py

         
         @param value: HTTP header value
         '''
+        
+    def cookie(key, **values):
+        '''
+        cookie setter
+        
+        @param key: cookie key
+        @param **values: cookie field names, values
+        '''
     
     def connection(value):
         '''