debian-zeep/src/zeep/xsd/types/builtins.py

542 lines
13 KiB
Python

import base64
import datetime
import math
import re
from decimal import Decimal as _Decimal
import isodate
import pytz
import six
from zeep.xsd.const import xsd_ns
from zeep.xsd.types.any import AnyType
from zeep.xsd.types.simple import AnySimpleType
class ParseError(ValueError):
pass
def check_no_collection(func):
def _wrapper(self, value):
if isinstance(value, (list, dict, set)):
raise ValueError(
"The %s type doesn't accept collections as value" % (
self.__class__.__name__))
return func(self, value)
return _wrapper
##
# Primitive types
class String(AnySimpleType):
_default_qname = xsd_ns('string')
accepted_types = six.string_types
@check_no_collection
def xmlvalue(self, value):
if isinstance(value, bytes):
return value.decode('utf-8')
return six.text_type(value if value is not None else '')
def pythonvalue(self, value):
return value
class Boolean(AnySimpleType):
_default_qname = xsd_ns('boolean')
accepted_types = (bool,)
@check_no_collection
def xmlvalue(self, value):
return 'true' if value else 'false'
def pythonvalue(self, value):
"""Return True if the 'true' or '1'. 'false' and '0' are legal false
values, but we consider everything not true as false.
"""
return value in ('true', '1')
class Decimal(AnySimpleType):
_default_qname = xsd_ns('decimal')
accepted_types = (_Decimal, float) + six.string_types
@check_no_collection
def xmlvalue(self, value):
return str(value)
def pythonvalue(self, value):
return _Decimal(value)
class Float(AnySimpleType):
_default_qname = xsd_ns('float')
accepted_types = (float, _Decimal) + six.string_types
def xmlvalue(self, value):
return str(value).upper()
def pythonvalue(self, value):
return float(value)
class Double(AnySimpleType):
_default_qname = xsd_ns('double')
accepted_types = (_Decimal, float) + six.string_types
@check_no_collection
def xmlvalue(self, value):
return str(value)
def pythonvalue(self, value):
return float(value)
class Duration(AnySimpleType):
_default_qname = xsd_ns('duration')
accepted_types = (isodate.duration.Duration,) + six.string_types
@check_no_collection
def xmlvalue(self, value):
return isodate.duration_isoformat(value)
def pythonvalue(self, value):
return isodate.parse_duration(value)
class DateTime(AnySimpleType):
_default_qname = xsd_ns('dateTime')
accepted_types = (datetime.datetime,) + six.string_types
@check_no_collection
def xmlvalue(self, value):
# Bit of a hack, since datetime is a subclass of date we can't just
# test it with an isinstance(). And actually, we should not really
# care about the type, as long as it has the required attributes
if not all(hasattr(value, attr) for attr in ('hour', 'minute', 'second')):
value = datetime.datetime.combine(value, datetime.time(
getattr(value, 'hour', 0),
getattr(value, 'minute', 0),
getattr(value, 'second', 0)))
if getattr(value, 'microsecond', 0):
return isodate.isostrf.strftime(value, '%Y-%m-%dT%H:%M:%S.%f%Z')
return isodate.isostrf.strftime(value, '%Y-%m-%dT%H:%M:%S%Z')
def pythonvalue(self, value):
return isodate.parse_datetime(value)
class Time(AnySimpleType):
_default_qname = xsd_ns('time')
accepted_types = (datetime.time,) + six.string_types
@check_no_collection
def xmlvalue(self, value):
if value.microsecond:
return isodate.isostrf.strftime(value, '%H:%M:%S.%f%Z')
return isodate.isostrf.strftime(value, '%H:%M:%S%Z')
def pythonvalue(self, value):
return isodate.parse_time(value)
class Date(AnySimpleType):
_default_qname = xsd_ns('date')
accepted_types = (datetime.date,) + six.string_types
@check_no_collection
def xmlvalue(self, value):
if isinstance(value, six.string_types):
return value
return isodate.isostrf.strftime(value, '%Y-%m-%d')
def pythonvalue(self, value):
return isodate.parse_date(value)
class gYearMonth(AnySimpleType):
"""gYearMonth represents a specific gregorian month in a specific gregorian
year.
Lexical representation: CCYY-MM
"""
accepted_types = (datetime.date,) + six.string_types
_default_qname = xsd_ns('gYearMonth')
_pattern = re.compile(
r'^(?P<year>-?\d{4,})-(?P<month>\d\d)(?P<timezone>Z|[-+]\d\d:?\d\d)?$')
@check_no_collection
def xmlvalue(self, value):
year, month, tzinfo = value
return '%04d-%02d%s' % (year, month, _unparse_timezone(tzinfo))
def pythonvalue(self, value):
match = self._pattern.match(value)
if not match:
raise ParseError()
group = match.groupdict()
return (
int(group['year']), int(group['month']),
_parse_timezone(group['timezone']))
class gYear(AnySimpleType):
"""gYear represents a gregorian calendar year.
Lexical representation: CCYY
"""
accepted_types = (datetime.date,) + six.string_types
_default_qname = xsd_ns('gYear')
_pattern = re.compile(r'^(?P<year>-?\d{4,})(?P<timezone>Z|[-+]\d\d:?\d\d)?$')
@check_no_collection
def xmlvalue(self, value):
year, tzinfo = value
return '%04d%s' % (year, _unparse_timezone(tzinfo))
def pythonvalue(self, value):
match = self._pattern.match(value)
if not match:
raise ParseError()
group = match.groupdict()
return (int(group['year']), _parse_timezone(group['timezone']))
class gMonthDay(AnySimpleType):
"""gMonthDay is a gregorian date that recurs, specifically a day of the
year such as the third of May.
Lexical representation: --MM-DD
"""
accepted_types = (datetime.date, ) + six.string_types
_default_qname = xsd_ns('gMonthDay')
_pattern = re.compile(
r'^--(?P<month>\d\d)-(?P<day>\d\d)(?P<timezone>Z|[-+]\d\d:?\d\d)?$')
@check_no_collection
def xmlvalue(self, value):
month, day, tzinfo = value
return '--%02d-%02d%s' % (month, day, _unparse_timezone(tzinfo))
def pythonvalue(self, value):
match = self._pattern.match(value)
if not match:
raise ParseError()
group = match.groupdict()
return (
int(group['month']), int(group['day']),
_parse_timezone(group['timezone']))
class gDay(AnySimpleType):
"""gDay is a gregorian day that recurs, specifically a day of the month
such as the 5th of the month
Lexical representation: ---DD
"""
accepted_types = (datetime.date,) + six.string_types
_default_qname = xsd_ns('gDay')
_pattern = re.compile(r'^---(?P<day>\d\d)(?P<timezone>Z|[-+]\d\d:?\d\d)?$')
@check_no_collection
def xmlvalue(self, value):
day, tzinfo = value
return '---%02d%s' % (day, _unparse_timezone(tzinfo))
def pythonvalue(self, value):
match = self._pattern.match(value)
if not match:
raise ParseError()
group = match.groupdict()
return (int(group['day']), _parse_timezone(group['timezone']))
class gMonth(AnySimpleType):
"""gMonth is a gregorian month that recurs every year.
Lexical representation: --MM
"""
accepted_types = (datetime.date,) + six.string_types
_default_qname = xsd_ns('gMonth')
_pattern = re.compile(r'^--(?P<month>\d\d)(?P<timezone>Z|[-+]\d\d:?\d\d)?$')
@check_no_collection
def xmlvalue(self, value):
month, tzinfo = value
return '--%d%s' % (month, _unparse_timezone(tzinfo))
def pythonvalue(self, value):
match = self._pattern.match(value)
if not match:
raise ParseError()
group = match.groupdict()
return (int(group['month']), _parse_timezone(group['timezone']))
class HexBinary(AnySimpleType):
accepted_types = six.string_types
_default_qname = xsd_ns('hexBinary')
@check_no_collection
def xmlvalue(self, value):
return value
def pythonvalue(self, value):
return value
class Base64Binary(AnySimpleType):
accepted_types = six.string_types
_default_qname = xsd_ns('base64Binary')
@check_no_collection
def xmlvalue(self, value):
return base64.b64encode(value)
def pythonvalue(self, value):
return base64.b64decode(value)
class AnyURI(AnySimpleType):
accepted_types = six.string_types
_default_qname = xsd_ns('anyURI')
@check_no_collection
def xmlvalue(self, value):
return value
def pythonvalue(self, value):
return value
class QName(AnySimpleType):
accepted_types = six.string_types
_default_qname = xsd_ns('QName')
@check_no_collection
def xmlvalue(self, value):
return value
def pythonvalue(self, value):
return value
class Notation(AnySimpleType):
accepted_types = six.string_types
_default_qname = xsd_ns('NOTATION')
##
# Derived datatypes
class NormalizedString(String):
_default_qname = xsd_ns('normalizedString')
class Token(NormalizedString):
_default_qname = xsd_ns('token')
class Language(Token):
_default_qname = xsd_ns('language')
class NmToken(Token):
_default_qname = xsd_ns('NMTOKEN')
class NmTokens(NmToken):
_default_qname = xsd_ns('NMTOKENS')
class Name(Token):
_default_qname = xsd_ns('Name')
class NCName(Name):
_default_qname = xsd_ns('NCName')
class ID(NCName):
_default_qname = xsd_ns('ID')
class IDREF(NCName):
_default_qname = xsd_ns('IDREF')
class IDREFS(IDREF):
_default_qname = xsd_ns('IDREFS')
class Entity(NCName):
_default_qname = xsd_ns('ENTITY')
class Entities(Entity):
_default_qname = xsd_ns('ENTITIES')
class Integer(Decimal):
_default_qname = xsd_ns('integer')
def xmlvalue(self, value):
return str(value)
def pythonvalue(self, value):
return int(value)
class NonPositiveInteger(Integer):
_default_qname = xsd_ns('nonPositiveInteger')
class NegativeInteger(Integer):
_default_qname = xsd_ns('negativeInteger')
class Long(Integer):
_default_qname = xsd_ns('long')
def pythonvalue(self, value):
return long(value) if six.PY2 else int(value) # noqa
class Int(Long):
_default_qname = xsd_ns('int')
class Short(Int):
_default_qname = xsd_ns('short')
class Byte(Short):
"""A signed 8-bit integer"""
_default_qname = xsd_ns('byte')
class NonNegativeInteger(Integer):
_default_qname = xsd_ns('nonNegativeInteger')
class UnsignedLong(NonNegativeInteger):
_default_qname = xsd_ns('unsignedLong')
class UnsignedInt(UnsignedLong):
_default_qname = xsd_ns('unsignedInt')
class UnsignedShort(UnsignedInt):
_default_qname = xsd_ns('unsignedShort')
class UnsignedByte(UnsignedShort):
_default_qname = xsd_ns('unsignedByte')
class PositiveInteger(NonNegativeInteger):
_default_qname = xsd_ns('positiveInteger')
##
# Other
def _parse_timezone(val):
"""Return a pytz.tzinfo object"""
if not val:
return
if val == 'Z' or val == '+00:00':
return pytz.utc
negative = val.startswith('-')
minutes = int(val[-2:])
minutes += int(val[1:3]) * 60
if negative:
minutes = 0 - minutes
return pytz.FixedOffset(minutes)
def _unparse_timezone(tzinfo):
if not tzinfo:
return ''
if tzinfo == pytz.utc:
return 'Z'
hours = math.floor(tzinfo._minutes / 60)
minutes = tzinfo._minutes % 60
if hours > 0:
return '+%02d:%02d' % (hours, minutes)
return '-%02d:%02d' % (abs(hours), minutes)
default_types = {
cls._default_qname: cls() for cls in [
# Primitive
String,
Boolean,
Decimal,
Float,
Double,
Duration,
DateTime,
Time,
Date,
gYearMonth,
gYear,
gMonthDay,
gDay,
gMonth,
HexBinary,
Base64Binary,
AnyURI,
QName,
Notation,
# Derived
NormalizedString,
Token,
Language,
NmToken,
NmTokens,
Name,
NCName,
ID,
IDREF,
IDREFS,
Entity,
Entities,
Integer,
NonPositiveInteger, # noqa
NegativeInteger,
Long,
Int,
Short,
Byte,
NonNegativeInteger, # noqa
UnsignedByte,
UnsignedInt,
UnsignedLong,
UnsignedShort,
PositiveInteger,
# Other
AnyType,
AnySimpleType,
]
}