converters.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. import datetime
  2. from decimal import Decimal
  3. import re
  4. import time
  5. from .err import ProgrammingError
  6. from .constants import FIELD_TYPE
  7. def escape_item(val, charset, mapping=None):
  8. if mapping is None:
  9. mapping = encoders
  10. encoder = mapping.get(type(val))
  11. # Fallback to default when no encoder found
  12. if not encoder:
  13. try:
  14. encoder = mapping[str]
  15. except KeyError:
  16. raise TypeError("no default type converter defined")
  17. if encoder in (escape_dict, escape_sequence):
  18. val = encoder(val, charset, mapping)
  19. else:
  20. val = encoder(val, mapping)
  21. return val
  22. def escape_dict(val, charset, mapping=None):
  23. n = {}
  24. for k, v in val.items():
  25. quoted = escape_item(v, charset, mapping)
  26. n[k] = quoted
  27. return n
  28. def escape_sequence(val, charset, mapping=None):
  29. n = []
  30. for item in val:
  31. quoted = escape_item(item, charset, mapping)
  32. n.append(quoted)
  33. return "(" + ",".join(n) + ")"
  34. def escape_set(val, charset, mapping=None):
  35. return ",".join([escape_item(x, charset, mapping) for x in val])
  36. def escape_bool(value, mapping=None):
  37. return str(int(value))
  38. def escape_int(value, mapping=None):
  39. return str(value)
  40. def escape_float(value, mapping=None):
  41. s = repr(value)
  42. if s in ("inf", "-inf", "nan"):
  43. raise ProgrammingError("%s can not be used with MySQL" % s)
  44. if "e" not in s:
  45. s += "e0"
  46. return s
  47. _escape_table = [chr(x) for x in range(128)]
  48. _escape_table[0] = "\\0"
  49. _escape_table[ord("\\")] = "\\\\"
  50. _escape_table[ord("\n")] = "\\n"
  51. _escape_table[ord("\r")] = "\\r"
  52. _escape_table[ord("\032")] = "\\Z"
  53. _escape_table[ord('"')] = '\\"'
  54. _escape_table[ord("'")] = "\\'"
  55. def escape_string(value, mapping=None):
  56. """escapes *value* without adding quote.
  57. Value should be unicode
  58. """
  59. return value.translate(_escape_table)
  60. def escape_bytes_prefixed(value, mapping=None):
  61. return "_binary'%s'" % value.decode("ascii", "surrogateescape").translate(
  62. _escape_table
  63. )
  64. def escape_bytes(value, mapping=None):
  65. return "'%s'" % value.decode("ascii", "surrogateescape").translate(_escape_table)
  66. def escape_str(value, mapping=None):
  67. return "'%s'" % escape_string(str(value), mapping)
  68. def escape_None(value, mapping=None):
  69. return "NULL"
  70. def escape_timedelta(obj, mapping=None):
  71. seconds = int(obj.seconds) % 60
  72. minutes = int(obj.seconds // 60) % 60
  73. hours = int(obj.seconds // 3600) % 24 + int(obj.days) * 24
  74. if obj.microseconds:
  75. fmt = "'{0:02d}:{1:02d}:{2:02d}.{3:06d}'"
  76. else:
  77. fmt = "'{0:02d}:{1:02d}:{2:02d}'"
  78. return fmt.format(hours, minutes, seconds, obj.microseconds)
  79. def escape_time(obj, mapping=None):
  80. if obj.microsecond:
  81. fmt = "'{0.hour:02}:{0.minute:02}:{0.second:02}.{0.microsecond:06}'"
  82. else:
  83. fmt = "'{0.hour:02}:{0.minute:02}:{0.second:02}'"
  84. return fmt.format(obj)
  85. def escape_datetime(obj, mapping=None):
  86. if obj.microsecond:
  87. fmt = (
  88. "'{0.year:04}-{0.month:02}-{0.day:02}"
  89. + " {0.hour:02}:{0.minute:02}:{0.second:02}.{0.microsecond:06}'"
  90. )
  91. else:
  92. fmt = "'{0.year:04}-{0.month:02}-{0.day:02} {0.hour:02}:{0.minute:02}:{0.second:02}'"
  93. return fmt.format(obj)
  94. def escape_date(obj, mapping=None):
  95. fmt = "'{0.year:04}-{0.month:02}-{0.day:02}'"
  96. return fmt.format(obj)
  97. def escape_struct_time(obj, mapping=None):
  98. return escape_datetime(datetime.datetime(*obj[:6]))
  99. def Decimal2Literal(o, d):
  100. return format(o, "f")
  101. def _convert_second_fraction(s):
  102. if not s:
  103. return 0
  104. # Pad zeros to ensure the fraction length in microseconds
  105. s = s.ljust(6, "0")
  106. return int(s[:6])
  107. DATETIME_RE = re.compile(
  108. r"(\d{1,4})-(\d{1,2})-(\d{1,2})[T ](\d{1,2}):(\d{1,2}):(\d{1,2})(?:.(\d{1,6}))?"
  109. )
  110. def convert_datetime(obj):
  111. """Returns a DATETIME or TIMESTAMP column value as a datetime object:
  112. >>> convert_datetime('2007-02-25 23:06:20')
  113. datetime.datetime(2007, 2, 25, 23, 6, 20)
  114. >>> convert_datetime('2007-02-25T23:06:20')
  115. datetime.datetime(2007, 2, 25, 23, 6, 20)
  116. Illegal values are returned as str:
  117. >>> convert_datetime('2007-02-31T23:06:20')
  118. '2007-02-31T23:06:20'
  119. >>> convert_datetime('0000-00-00 00:00:00')
  120. '0000-00-00 00:00:00'
  121. """
  122. if isinstance(obj, (bytes, bytearray)):
  123. obj = obj.decode("ascii")
  124. m = DATETIME_RE.match(obj)
  125. if not m:
  126. return convert_date(obj)
  127. try:
  128. groups = list(m.groups())
  129. groups[-1] = _convert_second_fraction(groups[-1])
  130. return datetime.datetime(*[int(x) for x in groups])
  131. except ValueError:
  132. return convert_date(obj)
  133. TIMEDELTA_RE = re.compile(r"(-)?(\d{1,3}):(\d{1,2}):(\d{1,2})(?:.(\d{1,6}))?")
  134. def convert_timedelta(obj):
  135. """Returns a TIME column as a timedelta object:
  136. >>> convert_timedelta('25:06:17')
  137. datetime.timedelta(days=1, seconds=3977)
  138. >>> convert_timedelta('-25:06:17')
  139. datetime.timedelta(days=-2, seconds=82423)
  140. Illegal values are returned as string:
  141. >>> convert_timedelta('random crap')
  142. 'random crap'
  143. Note that MySQL always returns TIME columns as (+|-)HH:MM:SS, but
  144. can accept values as (+|-)DD HH:MM:SS. The latter format will not
  145. be parsed correctly by this function.
  146. """
  147. if isinstance(obj, (bytes, bytearray)):
  148. obj = obj.decode("ascii")
  149. m = TIMEDELTA_RE.match(obj)
  150. if not m:
  151. return obj
  152. try:
  153. groups = list(m.groups())
  154. groups[-1] = _convert_second_fraction(groups[-1])
  155. negate = -1 if groups[0] else 1
  156. hours, minutes, seconds, microseconds = groups[1:]
  157. tdelta = (
  158. datetime.timedelta(
  159. hours=int(hours),
  160. minutes=int(minutes),
  161. seconds=int(seconds),
  162. microseconds=int(microseconds),
  163. )
  164. * negate
  165. )
  166. return tdelta
  167. except ValueError:
  168. return obj
  169. TIME_RE = re.compile(r"(\d{1,2}):(\d{1,2}):(\d{1,2})(?:.(\d{1,6}))?")
  170. def convert_time(obj):
  171. """Returns a TIME column as a time object:
  172. >>> convert_time('15:06:17')
  173. datetime.time(15, 6, 17)
  174. Illegal values are returned as str:
  175. >>> convert_time('-25:06:17')
  176. '-25:06:17'
  177. >>> convert_time('random crap')
  178. 'random crap'
  179. Note that MySQL always returns TIME columns as (+|-)HH:MM:SS, but
  180. can accept values as (+|-)DD HH:MM:SS. The latter format will not
  181. be parsed correctly by this function.
  182. Also note that MySQL's TIME column corresponds more closely to
  183. Python's timedelta and not time. However if you want TIME columns
  184. to be treated as time-of-day and not a time offset, then you can
  185. use set this function as the converter for FIELD_TYPE.TIME.
  186. """
  187. if isinstance(obj, (bytes, bytearray)):
  188. obj = obj.decode("ascii")
  189. m = TIME_RE.match(obj)
  190. if not m:
  191. return obj
  192. try:
  193. groups = list(m.groups())
  194. groups[-1] = _convert_second_fraction(groups[-1])
  195. hours, minutes, seconds, microseconds = groups
  196. return datetime.time(
  197. hour=int(hours),
  198. minute=int(minutes),
  199. second=int(seconds),
  200. microsecond=int(microseconds),
  201. )
  202. except ValueError:
  203. return obj
  204. def convert_date(obj):
  205. """Returns a DATE column as a date object:
  206. >>> convert_date('2007-02-26')
  207. datetime.date(2007, 2, 26)
  208. Illegal values are returned as str:
  209. >>> convert_date('2007-02-31')
  210. '2007-02-31'
  211. >>> convert_date('0000-00-00')
  212. '0000-00-00'
  213. """
  214. if isinstance(obj, (bytes, bytearray)):
  215. obj = obj.decode("ascii")
  216. try:
  217. return datetime.date(*[int(x) for x in obj.split("-", 2)])
  218. except ValueError:
  219. return obj
  220. def through(x):
  221. return x
  222. # def convert_bit(b):
  223. # b = "\x00" * (8 - len(b)) + b # pad w/ zeroes
  224. # return struct.unpack(">Q", b)[0]
  225. #
  226. # the snippet above is right, but MySQLdb doesn't process bits,
  227. # so we shouldn't either
  228. convert_bit = through
  229. encoders = {
  230. bool: escape_bool,
  231. int: escape_int,
  232. float: escape_float,
  233. str: escape_str,
  234. bytes: escape_bytes,
  235. tuple: escape_sequence,
  236. list: escape_sequence,
  237. set: escape_sequence,
  238. frozenset: escape_sequence,
  239. dict: escape_dict,
  240. type(None): escape_None,
  241. datetime.date: escape_date,
  242. datetime.datetime: escape_datetime,
  243. datetime.timedelta: escape_timedelta,
  244. datetime.time: escape_time,
  245. time.struct_time: escape_struct_time,
  246. Decimal: Decimal2Literal,
  247. }
  248. decoders = {
  249. FIELD_TYPE.BIT: convert_bit,
  250. FIELD_TYPE.TINY: int,
  251. FIELD_TYPE.SHORT: int,
  252. FIELD_TYPE.LONG: int,
  253. FIELD_TYPE.FLOAT: float,
  254. FIELD_TYPE.DOUBLE: float,
  255. FIELD_TYPE.LONGLONG: int,
  256. FIELD_TYPE.INT24: int,
  257. FIELD_TYPE.YEAR: int,
  258. FIELD_TYPE.TIMESTAMP: convert_datetime,
  259. FIELD_TYPE.DATETIME: convert_datetime,
  260. FIELD_TYPE.TIME: convert_timedelta,
  261. FIELD_TYPE.DATE: convert_date,
  262. FIELD_TYPE.BLOB: through,
  263. FIELD_TYPE.TINY_BLOB: through,
  264. FIELD_TYPE.MEDIUM_BLOB: through,
  265. FIELD_TYPE.LONG_BLOB: through,
  266. FIELD_TYPE.STRING: through,
  267. FIELD_TYPE.VAR_STRING: through,
  268. FIELD_TYPE.VARCHAR: through,
  269. FIELD_TYPE.DECIMAL: Decimal,
  270. FIELD_TYPE.NEWDECIMAL: Decimal,
  271. }
  272. # for MySQLdb compatibility
  273. conversions = encoders.copy()
  274. conversions.update(decoders)
  275. Thing2Literal = escape_str
  276. # Run doctests with `pytest --doctest-modules pymysql/converters.py`