converters.py 9.3 KB

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