1 2# 3# spyne - Copyright (C) Spyne contributors. 4# 5# This library is free software; you can redistribute it and/or 6# modify it under the terms of the GNU Lesser General Public 7# License as published by the Free Software Foundation; either 8# version 2.1 of the License, or (at your option) any later version. 9# 10# This library is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13# Lesser General Public License for more details. 14# 15# You should have received a copy of the GNU Lesser General Public 16# License along with this library; if not, write to the Free Software 17# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 18# 19 20from __future__ import print_function, unicode_literals 21 22import logging 23logger = logging.getLogger(__name__) 24 25import re 26import uuid 27import errno 28 29from os.path import isabs, join, abspath 30from collections import deque 31from datetime import datetime 32from decimal import Decimal as D 33from mmap import mmap, ACCESS_READ 34from time import mktime, strftime 35 36try: 37 from lxml import etree 38 from lxml import html 39except ImportError: 40 etree = None 41 html = None 42 43from spyne.protocol._base import ProtocolMixin 44from spyne.model import ModelBase, XmlAttribute, SimpleModel, Null, \ 45 ByteArray, File, ComplexModelBase, AnyXml, AnyHtml, Unicode, Decimal, \ 46 Double, Integer, Time, DateTime, Uuid, Duration, Boolean, AnyDict, \ 47 AnyUri, PushBase, Date 48from spyne.model.relational import FileData 49 50from spyne.const.http import HTTP_400, HTTP_401, HTTP_404, HTTP_405, HTTP_413, \ 51 HTTP_500 52from spyne.error import Fault, InternalError, ResourceNotFoundError, \ 53 RequestTooLongError, RequestNotAllowed, InvalidCredentialsError 54from spyne.model.binary import binary_encoding_handlers, \ 55 BINARY_ENCODING_USE_DEFAULT 56 57from spyne.util import six 58from spyne.util.cdict import cdict 59 60 61class OutProtocolBase(ProtocolMixin): 62 """This is the abstract base class for all out protocol implementations. 63 Child classes can implement only the required subset of the public methods. 64 65 An output protocol must implement :func:`serialize` and 66 :func:`create_out_string`. 67 68 The OutProtocolBase class supports the following events: 69 70 * ``before_serialize``: 71 Called before after the serialization operation is attempted. 72 73 * ``after_serialize``: 74 Called after the serialization operation is finished. 75 76 The arguments the constructor takes are as follows: 77 78 :param app: The application this protocol belongs to. 79 :param mime_type: The mime_type this protocol should set for transports 80 that support this. This is a quick way to override the mime_type by 81 default instead of subclassing the releavant protocol implementation. 82 :param ignore_uncap: Silently ignore cases when the protocol is not capable 83 of serializing return values instead of raising a TypeError. 84 """ 85 86 def __init__(self, app=None, mime_type=None, ignore_uncap=False, 87 ignore_wrappers=False, binary_encoding=None, string_encoding=None): 88 89 super(OutProtocolBase, self).__init__(app=app, mime_type=mime_type, 90 ignore_wrappers=ignore_wrappers, 91 binary_encoding=binary_encoding, string_encoding=string_encoding) 92 93 self.ignore_uncap = ignore_uncap 94 self.message = None 95 96 if mime_type is not None: 97 self.mime_type = mime_type 98 99 self._to_bytes_handlers = cdict({ 100 ModelBase: self.model_base_to_bytes, 101 File: self.file_to_bytes, 102 Time: self.time_to_bytes, 103 Uuid: self.uuid_to_bytes, 104 Null: self.null_to_bytes, 105 Date: self.date_to_bytes, 106 Double: self.double_to_bytes, 107 AnyXml: self.any_xml_to_bytes, 108 Unicode: self.unicode_to_bytes, 109 Boolean: self.boolean_to_bytes, 110 Decimal: self.decimal_to_bytes, 111 Integer: self.integer_to_bytes, 112 AnyHtml: self.any_html_to_bytes, 113 DateTime: self.datetime_to_bytes, 114 Duration: self.duration_to_bytes, 115 ByteArray: self.byte_array_to_bytes, 116 XmlAttribute: self.xmlattribute_to_bytes, 117 ComplexModelBase: self.complex_model_base_to_bytes, 118 }) 119 120 self._to_unicode_handlers = cdict({ 121 ModelBase: self.model_base_to_unicode, 122 File: self.file_to_unicode, 123 Time: self.time_to_unicode, 124 Date: self.date_to_unicode, 125 Uuid: self.uuid_to_unicode, 126 Null: self.null_to_unicode, 127 Double: self.double_to_unicode, 128 AnyXml: self.any_xml_to_unicode, 129 AnyUri: self.any_uri_to_unicode, 130 AnyDict: self.any_dict_to_unicode, 131 AnyHtml: self.any_html_to_unicode, 132 Unicode: self.unicode_to_unicode, 133 Boolean: self.boolean_to_unicode, 134 Decimal: self.decimal_to_unicode, 135 Integer: self.integer_to_unicode, 136 # FIXME: Would we need a to_unicode for localized dates? 137 DateTime: self.datetime_to_unicode, 138 Duration: self.duration_to_unicode, 139 ByteArray: self.byte_array_to_unicode, 140 XmlAttribute: self.xmlattribute_to_unicode, 141 ComplexModelBase: self.complex_model_base_to_unicode, 142 }) 143 144 self._to_bytes_iterable_handlers = cdict({ 145 File: self.file_to_bytes_iterable, 146 ByteArray: self.byte_array_to_bytes_iterable, 147 ModelBase: self.model_base_to_bytes_iterable, 148 SimpleModel: self.simple_model_to_bytes_iterable, 149 ComplexModelBase: self.complex_model_to_bytes_iterable, 150 }) 151 152 153 def serialize(self, ctx, message): 154 """Serializes ``ctx.out_object``. 155 156 If ctx.out_stream is not None, ``ctx.out_document`` and 157 ``ctx.out_string`` are skipped and the response is written directly to 158 ``ctx.out_stream``. 159 160 :param ctx: :class:`MethodContext` instance. 161 :param message: One of ``(ProtocolBase.REQUEST, ProtocolBase.RESPONSE)``. 162 """ 163 164 def create_out_string(self, ctx, out_string_encoding=None): 165 """Uses ctx.out_document to set ctx.out_string""" 166 167 def fault_to_http_response_code(self, fault): 168 """Special function to convert native Python exceptions to Http response 169 codes. 170 """ 171 172 if isinstance(fault, RequestTooLongError): 173 return HTTP_413 174 175 if isinstance(fault, ResourceNotFoundError): 176 return HTTP_404 177 178 if isinstance(fault, RequestNotAllowed): 179 return HTTP_405 180 181 if isinstance(fault, InvalidCredentialsError): 182 return HTTP_401 183 184 if isinstance(fault, Fault) and (fault.faultcode.startswith('Client.') 185 or fault.faultcode == 'Client'): 186 return HTTP_400 187 188 return HTTP_500 189 190 def set_validator(self, validator): 191 """You must override this function if you want your protocol to support 192 validation.""" 193 194 assert validator is None 195 196 self.validator = None 197 198 def to_bytes(self, cls, value, *args, **kwargs): 199 if value is None: 200 return None 201 202 handler = self._to_bytes_handlers[cls] 203 retval = handler(cls, value, *args, **kwargs) 204 205 # enable this only for testing. we're not as strict for performance 206 # reasons 207 # assert isinstance(retval, six.binary_type), \ 208 # "AssertionError: %r %r %r handler: %r" % \ 209 # (type(retval), six.binary_type, retval, handler) 210 return retval 211 212 def to_unicode(self, cls, value, *args, **kwargs): 213 if value is None: 214 return None 215 216 handler = self._to_unicode_handlers[cls] 217 retval = handler(cls, value, *args, **kwargs) 218 219 # enable this only for testing. we're not as strict for performance 220 # reasons as well as not to take the joy of dealing with duck typing 221 # from the user 222 # assert isinstance(retval, six.text_type), \ 223 # "AssertionError: %r %r handler: %r" % \ 224 # (type(retval), retval, handler) 225 226 return retval 227 228 def to_bytes_iterable(self, cls, value): 229 if value is None: 230 return [] 231 232 if isinstance(value, PushBase): 233 return value 234 235 handler = self._to_bytes_iterable_handlers[cls] 236 return handler(cls, value) 237 238 def null_to_bytes(self, cls, value, **_): 239 return b"" 240 241 def null_to_unicode(self, cls, value, **_): 242 return u"" 243 244 def any_xml_to_bytes(self, cls, value, **_): 245 return etree.tostring(value) 246 247 def any_xml_to_unicode(self, cls, value, **_): 248 return etree.tostring(value, encoding='unicode') 249 250 def any_dict_to_unicode(self, cls, value, **_): 251 return repr(value) 252 253 def any_html_to_bytes(self, cls, value, **_): 254 return html.tostring(value) 255 256 def any_html_to_unicode(self, cls, value, **_): 257 return html.tostring(value, encoding='unicode') 258 259 def uuid_to_bytes(self, cls, value, suggested_encoding=None, **_): 260 ser_as = self.get_cls_attrs(cls).serialize_as 261 retval = self.uuid_to_unicode(cls, value, 262 suggested_encoding=suggested_encoding, **_) 263 264 if ser_as in ('bytes', 'bytes_le', 'fields', 'int', six.binary_type): 265 return retval 266 267 return retval.encode('ascii') 268 269 def uuid_to_unicode(self, cls, value, suggested_encoding=None, **_): 270 attr = self.get_cls_attrs(cls) 271 ser_as = attr.serialize_as 272 encoding = attr.encoding 273 274 if encoding is None: 275 encoding = suggested_encoding 276 277 retval = _uuid_serialize[ser_as](value) 278 if ser_as in ('bytes', 'bytes_le'): 279 retval = binary_encoding_handlers[encoding]((retval,)) 280 return retval 281 282 def unicode_to_bytes(self, cls, value, **_): 283 retval = value 284 285 cls_attrs = self.get_cls_attrs(cls) 286 287 if isinstance(value, six.text_type): 288 if cls_attrs.encoding is not None: 289 retval = value.encode(cls_attrs.encoding) 290 elif self.default_string_encoding is not None: 291 retval = value.encode(self.default_string_encoding) 292 elif not six.PY2: 293 logger.warning("You need to set either an encoding for %r " 294 "or a default_string_encoding for %r", cls, self) 295 296 if cls_attrs.str_format is not None: 297 return cls_attrs.str_format.format(value) 298 elif cls_attrs.format is not None: 299 return cls_attrs.format % retval 300 301 return retval 302 303 def any_uri_to_unicode(self, cls, value, **_): 304 return self.unicode_to_unicode(cls, value, **_) 305 306 def unicode_to_unicode(self, cls, value, **_): # :))) 307 cls_attrs = self.get_cls_attrs(cls) 308 309 retval = value 310 311 if isinstance(value, six.binary_type): 312 if cls_attrs.encoding is not None: 313 retval = value.decode(cls_attrs.encoding) 314 315 if self.default_string_encoding is not None: 316 retval = value.decode(self.default_string_encoding) 317 318 elif not six.PY2: 319 logger.warning("You need to set either an encoding for %r " 320 "or a default_string_encoding for %r", cls, self) 321 322 if cls_attrs.str_format is not None: 323 return cls_attrs.str_format.format(value) 324 elif cls_attrs.format is not None: 325 return cls_attrs.format % retval 326 327 return retval 328 329 def decimal_to_bytes(self, cls, value, **_): 330 return self.decimal_to_unicode(cls, value, **_).encode('utf8') 331 332 def decimal_to_unicode(self, cls, value, **_): 333 D(value) # sanity check 334 cls_attrs = self.get_cls_attrs(cls) 335 336 if cls_attrs.str_format is not None: 337 return cls_attrs.str_format.format(value) 338 elif cls_attrs.format is not None: 339 return cls_attrs.format % value 340 341 return str(value) 342 343 def double_to_bytes(self, cls, value, **_): 344 return self.double_to_unicode(cls, value, **_).encode('utf8') 345 346 def double_to_unicode(self, cls, value, **_): 347 float(value) # sanity check 348 cls_attrs = self.get_cls_attrs(cls) 349 350 if cls_attrs.str_format is not None: 351 return cls_attrs.str_format.format(value) 352 elif cls_attrs.format is not None: 353 return cls_attrs.format % value 354 355 return repr(value) 356 357 def integer_to_bytes(self, cls, value, **_): 358 return self.integer_to_unicode(cls, value, **_).encode('utf8') 359 360 def integer_to_unicode(self, cls, value, **_): 361 int(value) # sanity check 362 cls_attrs = self.get_cls_attrs(cls) 363 364 if cls_attrs.str_format is not None: 365 return cls_attrs.str_format.format(value) 366 elif cls_attrs.format is not None: 367 return cls_attrs.format % value 368 369 return str(value) 370 371 def time_to_bytes(self, cls, value, **kwargs): 372 return self.time_to_unicode(cls, value, **kwargs) 373 374 def time_to_unicode(self, cls, value, **_): 375 """Returns ISO formatted times.""" 376 if isinstance(value, datetime): 377 value = value.time() 378 return value.isoformat() 379 380 def date_to_bytes(self, cls, val, **_): 381 return self.date_to_unicode(cls, val, **_).encode("utf8") 382 383 def date_to_unicode(self, cls, val, **_): 384 if isinstance(val, datetime): 385 val = val.date() 386 387 sa = self.get_cls_attrs(cls).serialize_as 388 389 if sa is None or sa in (str, 'str'): 390 return self._date_to_bytes(cls, val) 391 392 return _datetime_smap[sa](cls, val) 393 394 def datetime_to_bytes(self, cls, val, **_): 395 retval = self.datetime_to_unicode(cls, val, **_) 396 sa = self.get_cls_attrs(cls).serialize_as 397 if sa is None or sa in (six.text_type, str, 'str'): 398 return retval.encode('ascii') 399 return retval 400 401 def datetime_to_unicode(self, cls, val, **_): 402 sa = self.get_cls_attrs(cls).serialize_as 403 404 if sa is None or sa in (six.text_type, str, 'str'): 405 return self._datetime_to_unicode(cls, val) 406 407 return _datetime_smap[sa](cls, val) 408 409 def duration_to_bytes(self, cls, value, **_): 410 return self.duration_to_unicode(cls, value, **_).encode("utf8") 411 412 def duration_to_unicode(self, cls, value, **_): 413 if value.days < 0: 414 value = -value 415 negative = True 416 else: 417 negative = False 418 419 tot_sec = int(value.total_seconds()) 420 seconds = value.seconds % 60 421 minutes = value.seconds // 60 422 hours = minutes // 60 423 minutes %= 60 424 seconds = float(seconds) 425 useconds = value.microseconds 426 427 retval = deque() 428 if negative: 429 retval.append("-P") 430 else: 431 retval.append("P") 432 if value.days != 0: 433 retval.append("%iD" % value.days) 434 435 if tot_sec != 0 and tot_sec % 86400 == 0 and useconds == 0: 436 return ''.join(retval) 437 438 retval.append('T') 439 440 if hours > 0: 441 retval.append("%iH" % hours) 442 443 if minutes > 0: 444 retval.append("%iM" % minutes) 445 446 if seconds > 0 or useconds > 0: 447 retval.append("%i" % seconds) 448 if useconds > 0: 449 retval.append(".%i" % useconds) 450 retval.append("S") 451 452 if len(retval) == 2: 453 retval.append('0S') 454 455 return ''.join(retval) 456 457 def boolean_to_bytes(self, cls, value, **_): 458 return str(bool(value)).lower().encode('ascii') 459 460 def boolean_to_unicode(self, cls, value, **_): 461 return str(bool(value)).lower() 462 463 def byte_array_to_bytes(self, cls, value, suggested_encoding=None, **_): 464 cls_attrs = self.get_cls_attrs(cls) 465 466 encoding = cls_attrs.encoding 467 if encoding is BINARY_ENCODING_USE_DEFAULT: 468 if suggested_encoding is None: 469 encoding = self.binary_encoding 470 else: 471 encoding = suggested_encoding 472 473 if encoding is None and isinstance(value, (list, tuple)) \ 474 and len(value) == 1 and isinstance(value[0], mmap): 475 return value[0] 476 477 encoder = binary_encoding_handlers[encoding] 478 logger.debug("Using binary encoder %r for encoding %r", 479 encoder, encoding) 480 retval = encoder(value) 481 if encoding is not None and isinstance(retval, six.text_type): 482 retval = retval.encode('ascii') 483 484 return retval 485 486 def byte_array_to_unicode(self, cls, value, suggested_encoding=None, **_): 487 encoding = self.get_cls_attrs(cls).encoding 488 if encoding is BINARY_ENCODING_USE_DEFAULT: 489 if suggested_encoding is None: 490 encoding = self.binary_encoding 491 else: 492 encoding = suggested_encoding 493 494 if encoding is None: 495 raise ValueError("Arbitrary binary data can't be serialized to " 496 "unicode") 497 498 retval = binary_encoding_handlers[encoding](value) 499 if not isinstance(retval, six.text_type): 500 retval = retval.decode('ascii') 501 502 return retval 503 504 def byte_array_to_bytes_iterable(self, cls, value, **_): 505 return value 506 507 def file_to_bytes(self, cls, value, suggested_encoding=None): 508 """ 509 :param cls: A :class:`spyne.model.File` subclass 510 :param value: Either a sequence of byte chunks or a 511 :class:`spyne.model.File.Value` instance. 512 """ 513 514 encoding = self.get_cls_attrs(cls).encoding 515 if encoding is BINARY_ENCODING_USE_DEFAULT: 516 if suggested_encoding is None: 517 encoding = self.binary_encoding 518 else: 519 encoding = suggested_encoding 520 521 if isinstance(value, File.Value): 522 if value.data is not None: 523 return binary_encoding_handlers[encoding](value.data) 524 525 if value.handle is not None: 526 # maybe we should have used the sweeping except: here. 527 if hasattr(value.handle, 'fileno'): 528 if six.PY2: 529 fileno = value.handle.fileno() 530 data = (mmap(fileno, 0, access=ACCESS_READ),) 531 else: 532 import io 533 try: 534 fileno = value.handle.fileno() 535 data = mmap(fileno, 0, access=ACCESS_READ) 536 except io.UnsupportedOperation: 537 data = (value.handle.read(),) 538 else: 539 data = (value.handle.read(),) 540 541 return binary_encoding_handlers[encoding](data) 542 543 if value.path is not None: 544 handle = open(value.path, 'rb') 545 fileno = handle.fileno() 546 data = mmap(fileno, 0, access=ACCESS_READ) 547 548 return binary_encoding_handlers[encoding](data) 549 550 assert False, "Unhandled file type" 551 552 if isinstance(value, FileData): 553 try: 554 return binary_encoding_handlers[encoding](value.data) 555 except Exception as e: 556 logger.error("Error encoding value to binary. Error: %r, Value: %r", 557 e, value) 558 raise 559 560 try: 561 return binary_encoding_handlers[encoding](value) 562 except Exception as e: 563 logger.error("Error encoding value to binary. Error: %r, Value: %r", 564 e, value) 565 raise 566 567 def file_to_unicode(self, cls, value, suggested_encoding=None): 568 """ 569 :param cls: A :class:`spyne.model.File` subclass 570 :param value: Either a sequence of byte chunks or a 571 :class:`spyne.model.File.Value` instance. 572 """ 573 574 cls_attrs = self.get_cls_attrs(cls) 575 encoding = cls_attrs.encoding 576 if encoding is BINARY_ENCODING_USE_DEFAULT: 577 encoding = suggested_encoding 578 579 if encoding is None and cls_attrs.mode is File.TEXT: 580 raise ValueError("Arbitrary binary data can't be serialized to " 581 "unicode.") 582 583 retval = self.file_to_bytes(cls, value, suggested_encoding) 584 if not isinstance(retval, six.text_type): 585 retval = retval.decode('ascii') 586 return retval 587 588 def file_to_bytes_iterable(self, cls, value, **_): 589 if value.data is not None: 590 if isinstance(value.data, (list, tuple)) and \ 591 isinstance(value.data[0], mmap): 592 return _file_to_iter(value.data[0]) 593 return iter(value.data) 594 595 if value.handle is not None: 596 f = value.handle 597 f.seek(0) 598 return _file_to_iter(f) 599 600 assert value.path is not None, "You need to write data to " \ 601 "persistent storage first if you want to read it back." 602 603 try: 604 path = value.path 605 if not isabs(value.path): 606 path = join(value.store, value.path) 607 assert abspath(path).startswith(value.store), \ 608 "No relative paths are allowed" 609 return _file_to_iter(open(path, 'rb')) 610 611 except IOError as e: 612 if e.errno == errno.ENOENT: 613 raise ResourceNotFoundError(value.path) 614 else: 615 raise InternalError("Error accessing requested file") 616 617 def simple_model_to_bytes_iterable(self, cls, value, **kwargs): 618 retval = self.to_bytes(cls, value, **kwargs) 619 if retval is None: 620 return (b'',) 621 return (retval,) 622 623 def complex_model_to_bytes_iterable(self, cls, value, **_): 624 if self.ignore_uncap: 625 return tuple() 626 raise TypeError("This protocol can only serialize primitives.") 627 628 def complex_model_base_to_bytes(self, cls, value, **_): 629 raise TypeError("Only primitives can be serialized to string.") 630 631 def complex_model_base_to_unicode(self, cls, value, **_): 632 raise TypeError("Only primitives can be serialized to string.") 633 634 def xmlattribute_to_bytes(self, cls, string, **kwargs): 635 return self.to_bytes(cls.type, string, **kwargs) 636 637 def xmlattribute_to_unicode(self, cls, string, **kwargs): 638 return self.to_unicode(cls.type, string, **kwargs) 639 640 def model_base_to_bytes_iterable(self, cls, value, **kwargs): 641 return cls.to_bytes_iterable(value, **kwargs) 642 643 def model_base_to_bytes(self, cls, value, **kwargs): 644 return cls.to_bytes(value, **kwargs) 645 646 def model_base_to_unicode(self, cls, value, **kwargs): 647 return cls.to_unicode(value, **kwargs) 648 649 def _datetime_to_unicode(self, cls, value, **_): 650 """Returns ISO formatted datetimes.""" 651 652 cls_attrs = self.get_cls_attrs(cls) 653 654 if cls_attrs.as_timezone is not None and value.tzinfo is not None: 655 value = value.astimezone(cls_attrs.as_timezone) 656 657 if not cls_attrs.timezone: 658 value = value.replace(tzinfo=None) 659 660 dt_format = self._get_datetime_format(cls_attrs) 661 662 if dt_format is None: 663 retval = value.isoformat() 664 665 elif six.PY2 and isinstance(dt_format, unicode): 666 retval = self.strftime(value, dt_format.encode('utf8')).decode('utf8') 667 668 else: 669 retval = self.strftime(value, dt_format) 670 671 # FIXME: must deprecate string_format, this should have been str_format 672 str_format = cls_attrs.string_format 673 if str_format is None: 674 str_format = cls_attrs.str_format 675 if str_format is not None: 676 return str_format.format(value) 677 678 # FIXME: must deprecate interp_format, this should have been just format 679 interp_format = cls_attrs.interp_format 680 if interp_format is not None: 681 return interp_format.format(value) 682 683 return retval 684 685 def _date_to_bytes(self, cls, value, **_): 686 cls_attrs = self.get_cls_attrs(cls) 687 688 date_format = cls_attrs.date_format 689 if date_format is None: 690 retval = value.isoformat() 691 692 elif six.PY2 and isinstance(date_format, unicode): 693 date_format = date_format.encode('utf8') 694 retval = self.strftime(value, date_format).decode('utf8') 695 696 else: 697 retval = self.strftime(value, date_format) 698 699 str_format = cls_attrs.str_format 700 if str_format is not None: 701 return str_format.format(value) 702 703 format = cls_attrs.format 704 if format is not None: 705 return format.format(value) 706 707 return retval 708 709 # Format a datetime through its full proleptic Gregorian date range. 710 # http://code.activestate.com/recipes/ 711 # 306860-proleptic-gregorian-dates-and-strftime-before-1900/ 712 # http://stackoverflow.com/a/32206673 713 # 714 # >>> strftime(datetime.date(1850, 8, 2), "%Y/%M/%d was a %A") 715 # '1850/00/02 was a Friday' 716 # >>> 717 718 719 # remove the unsupposed "%s" command. But don't 720 # do it if there's an even number of %s before the s 721 # because those are all escaped. Can't simply 722 # remove the s because the result of 723 # %sY 724 # should be %Y if %s isn't supported, not the 725 # 4 digit year. 726 _illegal_s = re.compile(r"((^|[^%])(%%)*%s)") 727 728 @staticmethod 729 def _findall_datetime(text, substr): 730 # Also finds overlaps 731 sites = [] 732 i = 0 733 while 1: 734 j = text.find(substr, i) 735 if j == -1: 736 break 737 sites.append(j) 738 i=j+1 739 return sites 740 741 # Every 28 years the calendar repeats, except through century leap 742 # years where it's 6 years. But only if you're using the Gregorian 743 # calendar. ;) 744 745 @classmethod 746 def strftime(cls, dt, fmt): 747 if cls._illegal_s.search(fmt): 748 raise TypeError("This strftime implementation does not handle %s") 749 if dt.year > 1900: 750 return dt.strftime(fmt) 751 752 year = dt.year 753 # For every non-leap year century, advance by 754 # 6 years to get into the 28-year repeat cycle 755 delta = 2000 - year 756 off = 6*(delta // 100 + delta // 400) 757 year += off 758 759 # Move to around the year 2000 760 year += ((2000 - year) // 28) * 28 761 timetuple = dt.timetuple() 762 s1 = strftime(fmt, (year,) + timetuple[1:]) 763 sites1 = cls._findall_datetime(s1, str(year)) 764 765 s2 = strftime(fmt, (year+28,) + timetuple[1:]) 766 sites2 = cls._findall_datetime(s2, str(year+28)) 767 768 sites = [] 769 for site in sites1: 770 if site in sites2: 771 sites.append(site) 772 773 s = s1 774 syear = "%4d" % (dt.year,) 775 for site in sites: 776 s = s[:site] + syear + s[site+4:] 777 return s 778 779 780_uuid_serialize = { 781 None: str, 782 str: str, 783 'str': str, 784 785 'hex': lambda u: u.hex, 786 'urn': lambda u: u.urn, 787 'bytes': lambda u: u.bytes, 788 'bytes_le': lambda u: u.bytes_le, 789 'fields': lambda u: u.fields, 790 791 int: lambda u: u.int, 792 'int': lambda u: u.int, 793} 794 795_uuid_deserialize = { 796 None: uuid.UUID, 797 str: uuid.UUID, 798 'str': uuid.UUID, 799 800 'hex': lambda s: uuid.UUID(hex=s), 801 'urn': lambda s: uuid.UUID(hex=s), 802 'bytes': lambda s: uuid.UUID(bytes=s), 803 'bytes_le': lambda s: uuid.UUID(bytes_le=s), 804 'fields': lambda s: uuid.UUID(fields=s), 805 806 int: lambda s: uuid.UUID(int=s), 807 'int': lambda s: uuid.UUID(int=s), 808 809 (int, int): lambda s: uuid.UUID(int=s), 810 ('int', int): lambda s: uuid.UUID(int=s), 811 812 (int, str): lambda s: uuid.UUID(int=int(s)), 813 ('int', str): lambda s: uuid.UUID(int=int(s)), 814} 815 816if six.PY2: 817 _uuid_deserialize[('int', long)] = _uuid_deserialize[('int', int)] 818 _uuid_deserialize[(int, long)] = _uuid_deserialize[('int', int)] 819 820 821def _parse_datetime_iso_match(date_match, tz=None): 822 fields = date_match.groupdict() 823 824 year = int(fields.get('year')) 825 month = int(fields.get('month')) 826 day = int(fields.get('day')) 827 hour = int(fields.get('hr')) 828 minute = int(fields.get('min')) 829 second = int(fields.get('sec')) 830 usecond = fields.get("sec_frac") 831 if usecond is None: 832 usecond = 0 833 else: 834 # we only get the most significant 6 digits because that's what 835 # datetime can handle. 836 usecond = int(round(float(usecond) * 1e6)) 837 838 return datetime(year, month, day, hour, minute, second, usecond, tz) 839 840 841_dt_sec = lambda cls, val: \ 842 int(mktime(val.timetuple())) 843_dt_sec_float = lambda cls, val: \ 844 mktime(val.timetuple()) + (val.microsecond / 1e6) 845 846_dt_msec = lambda cls, val: \ 847 int(mktime(val.timetuple())) * 1000 + (val.microsecond // 1000) 848_dt_msec_float = lambda cls, val: \ 849 mktime(val.timetuple()) * 1000 + (val.microsecond / 1000.0) 850 851_dt_usec = lambda cls, val: \ 852 int(mktime(val.timetuple())) * 1000000 + val.microsecond 853 854_datetime_smap = { 855 'sec': _dt_sec, 856 'secs': _dt_sec, 857 'second': _dt_sec, 858 'seconds': _dt_sec, 859 860 'sec_float': _dt_sec_float, 861 'secs_float': _dt_sec_float, 862 'second_float': _dt_sec_float, 863 'seconds_float': _dt_sec_float, 864 865 'msec': _dt_msec, 866 'msecs': _dt_msec, 867 'msecond': _dt_msec, 868 'mseconds': _dt_msec, 869 'millisecond': _dt_msec, 870 'milliseconds': _dt_msec, 871 872 'msec_float': _dt_msec_float, 873 'msecs_float': _dt_msec_float, 874 'msecond_float': _dt_msec_float, 875 'mseconds_float': _dt_msec_float, 876 'millisecond_float': _dt_msec_float, 877 'milliseconds_float': _dt_msec_float, 878 879 'usec': _dt_usec, 880 'usecs': _dt_usec, 881 'usecond': _dt_usec, 882 'useconds': _dt_usec, 883 'microsecond': _dt_usec, 884 'microseconds': _dt_usec, 885} 886 887 888def _file_to_iter(f): 889 try: 890 data = f.read(8192) 891 while len(data) > 0: 892 yield data 893 data = f.read(8192) 894 895 finally: 896 f.close() 897 898 899META_ATTR = ['nullable', 'default_factory'] 900