# $SnapHashLicense:
#
# SnapLogic - Open source data services
#
# Copyright (C) 2008-2009, SnapLogic, Inc. All rights reserved.
#
# See http://www.snaplogic.org for more information about
# the SnapLogic project.
#
# This program is free software, distributed under the terms of
# the GNU General Public License Version 2. See the LEGAL file
# at the top of the source tree.
#
# "SnapLogic" is a trademark of SnapLogic, Inc.
#
#
# $
# $Id: __init__.py 6314 2009-02-11 01:07:59Z grisha $
"""
This package contains SnapLogic-provided Representation Plugins (RPs).
"""
from snaplogic.common.snap_exceptions import *
content_type_map = {}
SNAP_MAGIC_CONTENT_TYPE = 'snapi-undefined/snapi-undefined'
SNAP_ASN1_CONTENT_TYPE = 'application/x-snap-asn1'
def get_supported_content_types():
"""
Get supported content types.
Get a list of content types supported by this RP Layer.
@return: a list of supported content types with preferences.
@rtype: sequence, each of whose elements is a two-tuple, with first
element being content type, and the second being the preference
(q-value)
"""
return ['application/x-snap-asn1;q=1.0',
'application/json;q=0.9',
'application/asn1;q=0.8',
'text/html;q=0.6',
'text/csv;q=0.6',
'text/tab-separated-values;q=0.6']
def _content_type_comparator(x, y):
"""
Compare content type preferences.
A comparator used to sort accepted content types based on
preferences. It is intended to be used in L{select_content_type}.
See L{cmp}.
@param x: a tuple describing an acceptable content type, with three elements:
content-type, preference (q-value), and further specification of media range
(e.g., 'level=1').
@type x: tuple
@param y: same as x
@type y: tuple
@return: -1 if x is preferable, 1 if y is preferable, 0 if they are the same.
@rtype: int
"""
if type(x) == tuple:
x = list(x)
if type(y) == tuple:
y = list(y)
if x[2] is None:
x[2] = '1.0'
if y[2] is None:
y[2] = '1.0'
compare_quality = -cmp(float(x[2]),float(y[2]))
if compare_quality:
return compare_quality
(xtype,xsubtype) = x[0].split('/')
(ytype,ysubtype) = y[0].split('/')
if xtype == '*':
return 1
if ytype == '*':
return -1
if xsubtype == '*':
return 1
if ysubtype == '*':
return -1
if xtype == ytype and xsubtype == ysubtype:
if x[1] and not y[1]:
return -1
elif y[1] and not x[1]:
return 1
return 0
def negotiate_content_type_str(client_ctype, server_ctype):
"""
Carries out content negotiation between client and this HTTP server with content types specified in string form.
@param client_ctype: A content type requested by client GET request (via Accept header) or
specified in client POST request (via Content-Type header).
@type client_ctype: str
@param server_ctype: A list of content type names to consider for satisfying the client request. If None,
the list defaults to the supported RP content types.
@type server_ctype: list
@return: The negotiated content type name or None if negotiation fails.
@rtype: str
"""
if not client_ctype:
client_ctype = "*/*"
else:
client_list = client_ctype.split(',')
matched_ct = select_content_type(client_list, server_ctype)
if matched_ct is None:
return None
return matched_ct
def select_content_type(accept_list, supported=None):
"""
Select content type for reply to client.
Applies the algorithm specified in RFC 2616, section 14.1 to
select the content type with which to respond to the client,
based on the provided list of content types acceptable by
the client.
@param accept_list: list of strings, each representing a media type, e.g.,
['image/gif', 'text/html;level=1', 'text/xml;q=1.0']. Each element of the list
can legitimately occur by itself in the Accept: header field.
@param supported: list of supported content types. If this
parameter is None, the content types (see L{get_supported_content_types})
from the config files are used.
@type supported: list
@return: a tuple consisting of a negotiated content type and negotiated version (or None
if not applicable)
@rtype: tuple
"""
if supported is None:
supported = get_supported_content_types()
# For now we just ignore the q parameter, and treat 'supported'
# list
supported = _parse_accept_list(supported)
accept = _parse_accept_list(accept_list)
accept.sort(_content_type_comparator)
for candidate in accept:
try:
(type,subtype) = candidate[0].split('/')
except Exception, e:
raise ValueError('Malformed content type: %s, expected: type/subtype' % candidate[0])
for sup in supported:
sup_media_with_range = sup[0]
if sup[1]:
sup_media_with_range += ';' + sup[1]
if type == '*':
return sup_media_with_range
(sup_type, sup_subtype) = sup_media_with_range.split('/')
if type == sup_type:
if subtype == '*' or subtype == sup_subtype:
return sup_media_with_range
return None
def _parse_accept_list(accept_list):
"""
Parses the list of accept_strings, for internal use.
@param accept_list: a list of media ranges with accept parameters. Basically this is
the value of the Accept: header as specified in RFC 2616, split on comma.
@type accepts: sequence
@return: list of tuples representing values in the client-supplied Accept:
header. Each tuple contains 3 elements:
content-type, further specification of media range (if any) and
preference (assumed to be '1.0' if None).
@rtype: list of tuples. Each tuple contains 3 elements:
content-type, further specification of media range (e.g., 'level=1'),
and q value (preference; assumed to be '1.0' if None),
"""
retval = []
for accept in accept_list:
elements = [s.strip() for s in accept.split(';')]
media_type = elements[0]
media_range = ''
# Default q value
q_value = '1.0'
for param in elements[1:]:
(name,value) = param.split('=')
if name != 'q':
if media_range:
media_range += ';'
media_range += param
else:
q_value = value
break
tup = (media_type, media_range, q_value)
retval.append(tup)
return retval
def get_rp(accept, supported=None):
"""
Get RP plugin based on the contents of the HTTP Accept: header.
@param accept: Value of HTTP Accept: header, as specified in RFC 2616, section 14.1.
@type accept: str
@return: RP plugin, or None if not found.
@rtype: module
"""
accept_list = [s.strip() for s in accept.split(',')]
ct = select_content_type(accept_list, supported)
rp = _get_rp(ct)
return rp
def _get_rp(content_type):
"""
Facade method for getting the correct RP plugin module given the content type.
In the future, this will be based on the values of config file.
@param content_type: MIME content type to use
@type content_type: str
@return: RP module, which has the Reader and Writer class defined
@rtype: module
"""
global content_type_map
if not content_type_map:
from snaplogic.rp import json_rp,snap_asn1_rp,snap_asn1_rp_old,html_rp,tsv_rp,csv_rp
content_type_map = {
'application/json' : json_rp,
'application/asn1' : snap_asn1_rp_old,
'application/x-snap-asn1' : snap_asn1_rp,
'text/html' : html_rp,
tsv_rp.CONTENT_TYPE: tsv_rp,
csv_rp.CONTENT_TYPE: csv_rp
}
try:
rp_module = content_type_map[content_type]
except KeyError:
rp_module = None
return rp_module
def create_record(view, **kwargs):
"""
Create a record out of a keyword-value dictionary
This can be used to create a minimal record data structure
from a view.
The record will contain those keyword-value pairs in the kwargs
that are also defined in the view.
@param view : View to base the record on
@type view : dict
@param kwargs : Key-value pairs to put in the record
@type kwargs : dict
@return : record structure
@rtype : list
"""
def extract(viewpoint):
return viewpoint[1]
return [kwargs[extract(viewpoint)]
for viewpoint in view
if extract(viewpoint) in kwargs]
def create_simple_view(**kwargs):
"""
Create a view out of a keyword-value dictionary
This is a function for creating simple views. The final field is
always an empty string.
@param kwargs : Mapping of field names to their types
@type kwargs : dict (string -> string)
@return : view structure
@rtype : list
"""
return [(_field, _type, '')
for _field, _type in kwargs.iteritems()]
def makeRawRecord(record, fieldNumbers = None):
"""
Creates a "raw record" out of Snap Record. A "raw record" is merely a tuple
of all field values, with no information on field names and types.
@param record: Snap record
@type record: L{SnapLogic.Utils.DataTypes.Record}
@param fieldNumbers: Mapping of record to actual fields requested downstream.
@type fieldNumbers: list
@return: a record as a list of values (Python objects)
@rtype: list
"""
if fieldNumbers != None:
i = 1
raw_rec = []
for fieldName in record.fieldNames:
if i in fieldNumbers:
raw_rec.append(record.fields[fieldName])
i += 1
return raw_rec
else:
return [record.fields[f] for f in record.fieldNames] + record.passThroughFields
OBJECT_RESDEF = 'resdef'
"""Denotes a ResDef object type"""
OBJECT_TYPES = [OBJECT_RESDEF]
class _RPReader(object):
"""
A marker interface for a Reader class of an RP plugin.
All readers are iterators.
"""
def __init__(self, stream=None, options=None):
"""
Initialize the reader.
@param stream: Stream to read from. If None, the stream instance variable
must be set later, before any other calls.
@type stream: L{SnapStream}
@param options: A dictionary with optional parameters that may be interpreted
by the selected RP in any way. For example, an indication to
the HTML RP to use fixed-width rather than proportional font.
@type options: dict
"""
self.stream = stream
self.options = options
def read_nb(self):
"""
Non-blocking read.
Reads as many objects from the stream as possible, and returns them as a sequence.
@return: Sequence of objects read. Empty sequence is returned when no objects can
be read. None is returned when end of stream has been reached.
@rtype: sequence
"""
if self.__class__.supports_non_blocking_read():
raise SnapException('Implementation error: RPReader must implement this method.')
else:
raise SnapException('Operation not supported.')
@classmethod
def supports_non_blocking_read(cls):
"""
Returns True if this Reader class supports non-blocking read (see L{read_nb})
@return: True if this Reader supports non-blocking read, False otherwise
@rtype: bool
"""
return False
def __iter__(self):
return self
def next(self):
"""
This is a no-op implementation of a next() method necessary to make
this an iterator. Reads the next raw record -- a list of field values,
as supported Python objects. Subclasses should override.
@raise SnapEOFError: when the premature end of the underlying stream
has been reached.
"""
pass
class _RPWriter(object):
"""
For writing Python objects, in their appropriate representation, to a stream.
"""
def __init__(self, stream=None, human_readable=False, stream_type=None, options=None):
"""
Initializes the RP Writer object.
@param stream: stream to write to. If None, L{stream} instance variable must be set
prior to calling other methods on this object.
@type stream: a duck-typed stream, supporting write() method.
@param version: Version, as 'MAJOR.MINOR'
@param human_readable:
@type human_readable: bool
@param stream_type: describes types of objects that will be written to this stream. This
is only useful if human_readable parameter is True, and will be used to appropriately
format the objects. Note that they "type" here means one of L{OBJECT_TYPES}. If this argument is
None, then the RPWriter is object-agnostic. Note also that RPWRiter MAY choose to be
object-agnostic in any case.
@type stream_type: str
@param options: A dictionary with optional parameters that may be interpreted
by the selected RP in any way. For example, an indication to
the HTML RP to use fixed-width rather than proportional font.
@type options: dict
"""
self.stream = stream
self.human_readable = human_readable
self.stream_type = stream_type
self.options = options
def initialize(self, header=""):
"""
Initialize the underlying stream (by writing any headers
necessary before starting to write records).
@param header: header to write before sending records
@type header: str
@return: raw data written to the stream
@rtype: str
"""
pass
def end(self, footer=""):
"""
Write out any footers necessary. Does not actually close
the underlying stream.
@param header: footer to write before sending records
@type footer: str
@return: raw data written to the stream
@rtype: str
"""
pass
def write(self, record, options=None):
"""
Writes out a record's representation onto the underlying stream. This
is not a Snap Record, but a Python data type (primitives, dicts, lists,
etc).
@param record: a record to write
@type record: a python object
@param options: a per-object dictionary of option, which may be used
or ignored by specific RPs.
@type options: dict
@return: raw data written to the stream
@rtype: str
"""
pass
|