enumerated.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. # mysql/enumerated.py
  2. # Copyright (C) 2005-2017 the SQLAlchemy authors and contributors
  3. # <see AUTHORS file>
  4. #
  5. # This module is part of SQLAlchemy and is released under
  6. # the MIT License: http://www.opensource.org/licenses/mit-license.php
  7. import re
  8. from .types import _StringType
  9. from ... import exc, sql, util
  10. from ... import types as sqltypes
  11. class _EnumeratedValues(_StringType):
  12. def _init_values(self, values, kw):
  13. self.quoting = kw.pop('quoting', 'auto')
  14. if self.quoting == 'auto' and len(values):
  15. # What quoting character are we using?
  16. q = None
  17. for e in values:
  18. if len(e) == 0:
  19. self.quoting = 'unquoted'
  20. break
  21. elif q is None:
  22. q = e[0]
  23. if len(e) == 1 or e[0] != q or e[-1] != q:
  24. self.quoting = 'unquoted'
  25. break
  26. else:
  27. self.quoting = 'quoted'
  28. if self.quoting == 'quoted':
  29. util.warn_deprecated(
  30. 'Manually quoting %s value literals is deprecated. Supply '
  31. 'unquoted values and use the quoting= option in cases of '
  32. 'ambiguity.' % self.__class__.__name__)
  33. values = self._strip_values(values)
  34. self._enumerated_values = values
  35. length = max([len(v) for v in values] + [0])
  36. return values, length
  37. @classmethod
  38. def _strip_values(cls, values):
  39. strip_values = []
  40. for a in values:
  41. if a[0:1] == '"' or a[0:1] == "'":
  42. # strip enclosing quotes and unquote interior
  43. a = a[1:-1].replace(a[0] * 2, a[0])
  44. strip_values.append(a)
  45. return strip_values
  46. class ENUM(sqltypes.Enum, _EnumeratedValues):
  47. """MySQL ENUM type."""
  48. __visit_name__ = 'ENUM'
  49. def __init__(self, *enums, **kw):
  50. """Construct an ENUM.
  51. E.g.::
  52. Column('myenum', ENUM("foo", "bar", "baz"))
  53. :param enums: The range of valid values for this ENUM. Values will be
  54. quoted when generating the schema according to the quoting flag (see
  55. below). This object may also be a PEP-435-compliant enumerated
  56. type.
  57. .. versionadded: 1.1 added support for PEP-435-compliant enumerated
  58. types.
  59. :param strict: This flag has no effect.
  60. .. versionchanged:: The MySQL ENUM type as well as the base Enum
  61. type now validates all Python data values.
  62. :param charset: Optional, a column-level character set for this string
  63. value. Takes precedence to 'ascii' or 'unicode' short-hand.
  64. :param collation: Optional, a column-level collation for this string
  65. value. Takes precedence to 'binary' short-hand.
  66. :param ascii: Defaults to False: short-hand for the ``latin1``
  67. character set, generates ASCII in schema.
  68. :param unicode: Defaults to False: short-hand for the ``ucs2``
  69. character set, generates UNICODE in schema.
  70. :param binary: Defaults to False: short-hand, pick the binary
  71. collation type that matches the column's character set. Generates
  72. BINARY in schema. This does not affect the type of data stored,
  73. only the collation of character data.
  74. :param quoting: Defaults to 'auto': automatically determine enum value
  75. quoting. If all enum values are surrounded by the same quoting
  76. character, then use 'quoted' mode. Otherwise, use 'unquoted' mode.
  77. 'quoted': values in enums are already quoted, they will be used
  78. directly when generating the schema - this usage is deprecated.
  79. 'unquoted': values in enums are not quoted, they will be escaped and
  80. surrounded by single quotes when generating the schema.
  81. Previous versions of this type always required manually quoted
  82. values to be supplied; future versions will always quote the string
  83. literals for you. This is a transitional option.
  84. """
  85. kw.pop('strict', None)
  86. validate_strings = kw.pop("validate_strings", False)
  87. sqltypes.Enum.__init__(
  88. self, validate_strings=validate_strings, *enums)
  89. kw.pop('metadata', None)
  90. kw.pop('schema', None)
  91. kw.pop('name', None)
  92. kw.pop('quote', None)
  93. kw.pop('native_enum', None)
  94. kw.pop('inherit_schema', None)
  95. kw.pop('_create_events', None)
  96. _StringType.__init__(self, length=self.length, **kw)
  97. def _setup_for_values(self, values, objects, kw):
  98. values, length = self._init_values(values, kw)
  99. return sqltypes.Enum._setup_for_values(self, values, objects, kw)
  100. def _object_value_for_elem(self, elem):
  101. # mysql sends back a blank string for any value that
  102. # was persisted that was not in the enums; that is, it does no
  103. # validation on the incoming data, it "truncates" it to be
  104. # the blank string. Return it straight.
  105. if elem == "":
  106. return elem
  107. else:
  108. return super(ENUM, self)._object_value_for_elem(elem)
  109. def __repr__(self):
  110. return util.generic_repr(
  111. self, to_inspect=[ENUM, _StringType, sqltypes.Enum])
  112. def adapt(self, cls, **kw):
  113. return sqltypes.Enum.adapt(self, cls, **kw)
  114. class SET(_EnumeratedValues):
  115. """MySQL SET type."""
  116. __visit_name__ = 'SET'
  117. def __init__(self, *values, **kw):
  118. """Construct a SET.
  119. E.g.::
  120. Column('myset', SET("foo", "bar", "baz"))
  121. The list of potential values is required in the case that this
  122. set will be used to generate DDL for a table, or if the
  123. :paramref:`.SET.retrieve_as_bitwise` flag is set to True.
  124. :param values: The range of valid values for this SET.
  125. :param convert_unicode: Same flag as that of
  126. :paramref:`.String.convert_unicode`.
  127. :param collation: same as that of :paramref:`.String.collation`
  128. :param charset: same as that of :paramref:`.VARCHAR.charset`.
  129. :param ascii: same as that of :paramref:`.VARCHAR.ascii`.
  130. :param unicode: same as that of :paramref:`.VARCHAR.unicode`.
  131. :param binary: same as that of :paramref:`.VARCHAR.binary`.
  132. :param quoting: Defaults to 'auto': automatically determine set value
  133. quoting. If all values are surrounded by the same quoting
  134. character, then use 'quoted' mode. Otherwise, use 'unquoted' mode.
  135. 'quoted': values in enums are already quoted, they will be used
  136. directly when generating the schema - this usage is deprecated.
  137. 'unquoted': values in enums are not quoted, they will be escaped and
  138. surrounded by single quotes when generating the schema.
  139. Previous versions of this type always required manually quoted
  140. values to be supplied; future versions will always quote the string
  141. literals for you. This is a transitional option.
  142. .. versionadded:: 0.9.0
  143. :param retrieve_as_bitwise: if True, the data for the set type will be
  144. persisted and selected using an integer value, where a set is coerced
  145. into a bitwise mask for persistence. MySQL allows this mode which
  146. has the advantage of being able to store values unambiguously,
  147. such as the blank string ``''``. The datatype will appear
  148. as the expression ``col + 0`` in a SELECT statement, so that the
  149. value is coerced into an integer value in result sets.
  150. This flag is required if one wishes
  151. to persist a set that can store the blank string ``''`` as a value.
  152. .. warning::
  153. When using :paramref:`.mysql.SET.retrieve_as_bitwise`, it is
  154. essential that the list of set values is expressed in the
  155. **exact same order** as exists on the MySQL database.
  156. .. versionadded:: 1.0.0
  157. """
  158. self.retrieve_as_bitwise = kw.pop('retrieve_as_bitwise', False)
  159. values, length = self._init_values(values, kw)
  160. self.values = tuple(values)
  161. if not self.retrieve_as_bitwise and '' in values:
  162. raise exc.ArgumentError(
  163. "Can't use the blank value '' in a SET without "
  164. "setting retrieve_as_bitwise=True")
  165. if self.retrieve_as_bitwise:
  166. self._bitmap = dict(
  167. (value, 2 ** idx)
  168. for idx, value in enumerate(self.values)
  169. )
  170. self._bitmap.update(
  171. (2 ** idx, value)
  172. for idx, value in enumerate(self.values)
  173. )
  174. kw.setdefault('length', length)
  175. super(SET, self).__init__(**kw)
  176. def column_expression(self, colexpr):
  177. if self.retrieve_as_bitwise:
  178. return sql.type_coerce(
  179. sql.type_coerce(colexpr, sqltypes.Integer) + 0,
  180. self
  181. )
  182. else:
  183. return colexpr
  184. def result_processor(self, dialect, coltype):
  185. if self.retrieve_as_bitwise:
  186. def process(value):
  187. if value is not None:
  188. value = int(value)
  189. return set(
  190. util.map_bits(self._bitmap.__getitem__, value)
  191. )
  192. else:
  193. return None
  194. else:
  195. super_convert = super(SET, self).result_processor(dialect, coltype)
  196. def process(value):
  197. if isinstance(value, util.string_types):
  198. # MySQLdb returns a string, let's parse
  199. if super_convert:
  200. value = super_convert(value)
  201. return set(re.findall(r'[^,]+', value))
  202. else:
  203. # mysql-connector-python does a naive
  204. # split(",") which throws in an empty string
  205. if value is not None:
  206. value.discard('')
  207. return value
  208. return process
  209. def bind_processor(self, dialect):
  210. super_convert = super(SET, self).bind_processor(dialect)
  211. if self.retrieve_as_bitwise:
  212. def process(value):
  213. if value is None:
  214. return None
  215. elif isinstance(value, util.int_types + util.string_types):
  216. if super_convert:
  217. return super_convert(value)
  218. else:
  219. return value
  220. else:
  221. int_value = 0
  222. for v in value:
  223. int_value |= self._bitmap[v]
  224. return int_value
  225. else:
  226. def process(value):
  227. # accept strings and int (actually bitflag) values directly
  228. if value is not None and not isinstance(
  229. value, util.int_types + util.string_types):
  230. value = ",".join(value)
  231. if super_convert:
  232. return super_convert(value)
  233. else:
  234. return value
  235. return process
  236. def adapt(self, impltype, **kw):
  237. kw['retrieve_as_bitwise'] = self.retrieve_as_bitwise
  238. return util.constructor_copy(
  239. self, impltype,
  240. *self.values,
  241. **kw
  242. )