url.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. # engine/url.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. """Provides the :class:`~sqlalchemy.engine.url.URL` class which encapsulates
  8. information about a database connection specification.
  9. The URL object is created automatically when
  10. :func:`~sqlalchemy.engine.create_engine` is called with a string
  11. argument; alternatively, the URL is a public-facing construct which can
  12. be used directly and is also accepted directly by ``create_engine()``.
  13. """
  14. import re
  15. from .. import exc, util
  16. from . import Dialect
  17. from ..dialects import registry, plugins
  18. class URL(object):
  19. """
  20. Represent the components of a URL used to connect to a database.
  21. This object is suitable to be passed directly to a
  22. :func:`~sqlalchemy.create_engine` call. The fields of the URL are parsed
  23. from a string by the :func:`.make_url` function. the string
  24. format of the URL is an RFC-1738-style string.
  25. All initialization parameters are available as public attributes.
  26. :param drivername: the name of the database backend.
  27. This name will correspond to a module in sqlalchemy/databases
  28. or a third party plug-in.
  29. :param username: The user name.
  30. :param password: database password.
  31. :param host: The name of the host.
  32. :param port: The port number.
  33. :param database: The database name.
  34. :param query: A dictionary of options to be passed to the
  35. dialect and/or the DBAPI upon connect.
  36. """
  37. def __init__(self, drivername, username=None, password=None,
  38. host=None, port=None, database=None, query=None):
  39. self.drivername = drivername
  40. self.username = username
  41. self.password = password
  42. self.host = host
  43. if port is not None:
  44. self.port = int(port)
  45. else:
  46. self.port = None
  47. self.database = database
  48. self.query = query or {}
  49. def __to_string__(self, hide_password=True):
  50. s = self.drivername + "://"
  51. if self.username is not None:
  52. s += _rfc_1738_quote(self.username)
  53. if self.password is not None:
  54. s += ':' + ('***' if hide_password
  55. else _rfc_1738_quote(self.password))
  56. s += "@"
  57. if self.host is not None:
  58. if ':' in self.host:
  59. s += "[%s]" % self.host
  60. else:
  61. s += self.host
  62. if self.port is not None:
  63. s += ':' + str(self.port)
  64. if self.database is not None:
  65. s += '/' + self.database
  66. if self.query:
  67. keys = list(self.query)
  68. keys.sort()
  69. s += '?' + "&".join("%s=%s" % (k, self.query[k]) for k in keys)
  70. return s
  71. def __str__(self):
  72. return self.__to_string__(hide_password=False)
  73. def __repr__(self):
  74. return self.__to_string__()
  75. def __hash__(self):
  76. return hash(str(self))
  77. def __eq__(self, other):
  78. return \
  79. isinstance(other, URL) and \
  80. self.drivername == other.drivername and \
  81. self.username == other.username and \
  82. self.password == other.password and \
  83. self.host == other.host and \
  84. self.database == other.database and \
  85. self.query == other.query
  86. def get_backend_name(self):
  87. if '+' not in self.drivername:
  88. return self.drivername
  89. else:
  90. return self.drivername.split('+')[0]
  91. def get_driver_name(self):
  92. if '+' not in self.drivername:
  93. return self.get_dialect().driver
  94. else:
  95. return self.drivername.split('+')[1]
  96. def _instantiate_plugins(self, kwargs):
  97. plugin_names = util.to_list(self.query.get('plugin', ()))
  98. return [
  99. plugins.load(plugin_name)(self, kwargs)
  100. for plugin_name in plugin_names
  101. ]
  102. def _get_entrypoint(self):
  103. """Return the "entry point" dialect class.
  104. This is normally the dialect itself except in the case when the
  105. returned class implements the get_dialect_cls() method.
  106. """
  107. if '+' not in self.drivername:
  108. name = self.drivername
  109. else:
  110. name = self.drivername.replace('+', '.')
  111. cls = registry.load(name)
  112. # check for legacy dialects that
  113. # would return a module with 'dialect' as the
  114. # actual class
  115. if hasattr(cls, 'dialect') and \
  116. isinstance(cls.dialect, type) and \
  117. issubclass(cls.dialect, Dialect):
  118. return cls.dialect
  119. else:
  120. return cls
  121. def get_dialect(self):
  122. """Return the SQLAlchemy database dialect class corresponding
  123. to this URL's driver name.
  124. """
  125. entrypoint = self._get_entrypoint()
  126. dialect_cls = entrypoint.get_dialect_cls(self)
  127. return dialect_cls
  128. def translate_connect_args(self, names=[], **kw):
  129. r"""Translate url attributes into a dictionary of connection arguments.
  130. Returns attributes of this url (`host`, `database`, `username`,
  131. `password`, `port`) as a plain dictionary. The attribute names are
  132. used as the keys by default. Unset or false attributes are omitted
  133. from the final dictionary.
  134. :param \**kw: Optional, alternate key names for url attributes.
  135. :param names: Deprecated. Same purpose as the keyword-based alternate
  136. names, but correlates the name to the original positionally.
  137. """
  138. translated = {}
  139. attribute_names = ['host', 'database', 'username', 'password', 'port']
  140. for sname in attribute_names:
  141. if names:
  142. name = names.pop(0)
  143. elif sname in kw:
  144. name = kw[sname]
  145. else:
  146. name = sname
  147. if name is not None and getattr(self, sname, False):
  148. translated[name] = getattr(self, sname)
  149. return translated
  150. def make_url(name_or_url):
  151. """Given a string or unicode instance, produce a new URL instance.
  152. The given string is parsed according to the RFC 1738 spec. If an
  153. existing URL object is passed, just returns the object.
  154. """
  155. if isinstance(name_or_url, util.string_types):
  156. return _parse_rfc1738_args(name_or_url)
  157. else:
  158. return name_or_url
  159. def _parse_rfc1738_args(name):
  160. pattern = re.compile(r'''
  161. (?P<name>[\w\+]+)://
  162. (?:
  163. (?P<username>[^:/]*)
  164. (?::(?P<password>.*))?
  165. @)?
  166. (?:
  167. (?:
  168. \[(?P<ipv6host>[^/]+)\] |
  169. (?P<ipv4host>[^/:]+)
  170. )?
  171. (?::(?P<port>[^/]*))?
  172. )?
  173. (?:/(?P<database>.*))?
  174. ''', re.X)
  175. m = pattern.match(name)
  176. if m is not None:
  177. components = m.groupdict()
  178. if components['database'] is not None:
  179. tokens = components['database'].split('?', 2)
  180. components['database'] = tokens[0]
  181. query = (
  182. len(tokens) > 1 and dict(util.parse_qsl(tokens[1]))) or None
  183. if util.py2k and query is not None:
  184. query = dict((k.encode('ascii'), query[k]) for k in query)
  185. else:
  186. query = None
  187. components['query'] = query
  188. if components['username'] is not None:
  189. components['username'] = _rfc_1738_unquote(components['username'])
  190. if components['password'] is not None:
  191. components['password'] = _rfc_1738_unquote(components['password'])
  192. ipv4host = components.pop('ipv4host')
  193. ipv6host = components.pop('ipv6host')
  194. components['host'] = ipv4host or ipv6host
  195. name = components.pop('name')
  196. return URL(name, **components)
  197. else:
  198. raise exc.ArgumentError(
  199. "Could not parse rfc1738 URL from string '%s'" % name)
  200. def _rfc_1738_quote(text):
  201. return re.sub(r'[:@/]', lambda m: "%%%X" % ord(m.group(0)), text)
  202. def _rfc_1738_unquote(text):
  203. return util.unquote(text)
  204. def _parse_keyvalue_args(name):
  205. m = re.match(r'(\w+)://(.*)', name)
  206. if m is not None:
  207. (name, args) = m.group(1, 2)
  208. opts = dict(util.parse_qsl(args))
  209. return URL(name, *opts)
  210. else:
  211. return None