mirror of
https://github.com/sqlalchemy/sqlalchemy.git
synced 2026-05-17 06:07:27 -04:00
1f7a1f777d
method, and its interaction with result-row processing, now allows the columns passed to the method to be positionally matched with the result columns in the statement, rather than matching on name alone. The advantage to this includes that when linking a textual SQL statement to an ORM or Core table model, no system of labeling or de-duping of common column names needs to occur, which also means there's no need to worry about how label names match to ORM columns and so-forth. In addition, the :class:`.ResultProxy` has been further enhanced to map column and string keys to a row with greater precision in some cases. fixes #3501 - reorganize the initialization of ResultMetaData for readability and complexity; use the name "cursor_description", define the task of "merging" cursor_description with compiled column information as its own function, and also define "name extraction" as a separate task. - fully change the name we use in the "ambiguous column" error to be the actual name that was ambiguous, modify the C ext also
146 lines
4.7 KiB
Python
146 lines
4.7 KiB
Python
from __future__ import print_function
|
|
from sqlalchemy.testing import fixtures
|
|
from sqlalchemy.testing import config
|
|
import doctest
|
|
import logging
|
|
import sys
|
|
import re
|
|
import os
|
|
|
|
|
|
class DocTest(fixtures.TestBase):
|
|
def _setup_logger(self):
|
|
rootlogger = logging.getLogger('sqlalchemy.engine.base.Engine')
|
|
|
|
class MyStream(object):
|
|
def write(self, string):
|
|
sys.stdout.write(string)
|
|
sys.stdout.flush()
|
|
|
|
def flush(self):
|
|
pass
|
|
|
|
self._handler = handler = logging.StreamHandler(MyStream())
|
|
handler.setFormatter(logging.Formatter('%(message)s'))
|
|
rootlogger.addHandler(handler)
|
|
|
|
def _teardown_logger(self):
|
|
rootlogger = logging.getLogger('sqlalchemy.engine.base.Engine')
|
|
rootlogger.removeHandler(self._handler)
|
|
|
|
def _setup_create_table_patcher(self):
|
|
from sqlalchemy.sql import ddl
|
|
self.orig_sort = ddl.sort_tables_and_constraints
|
|
|
|
def our_sort(tables, **kw):
|
|
return self.orig_sort(
|
|
sorted(tables, key=lambda t: t.key), **kw
|
|
)
|
|
ddl.sort_tables_and_constraints = our_sort
|
|
|
|
def _teardown_create_table_patcher(self):
|
|
from sqlalchemy.sql import ddl
|
|
ddl.sort_tables_and_constraints = self.orig_sort
|
|
|
|
def setup(self):
|
|
self._setup_logger()
|
|
self._setup_create_table_patcher()
|
|
|
|
def teardown(self):
|
|
self._teardown_create_table_patcher()
|
|
self._teardown_logger()
|
|
|
|
def _run_doctest_for_content(self, name, content):
|
|
optionflags = (
|
|
doctest.ELLIPSIS |
|
|
doctest.NORMALIZE_WHITESPACE |
|
|
doctest.IGNORE_EXCEPTION_DETAIL |
|
|
_get_allow_unicode_flag()
|
|
)
|
|
runner = doctest.DocTestRunner(
|
|
verbose=None, optionflags=optionflags,
|
|
checker=_get_unicode_checker())
|
|
globs = {
|
|
'print_function': print_function}
|
|
parser = doctest.DocTestParser()
|
|
test = parser.get_doctest(content, globs, name, name, 0)
|
|
runner.run(test)
|
|
runner.summarize()
|
|
assert not runner.failures
|
|
|
|
def _run_doctest(self, fname):
|
|
here = os.path.dirname(__file__)
|
|
sqla_base = os.path.normpath(os.path.join(here, "..", ".."))
|
|
path = os.path.join(sqla_base, "doc/build", fname)
|
|
if not os.path.exists(path):
|
|
config.skip_test("Can't find documentation file %r" % path)
|
|
with open(path) as file_:
|
|
content = file_.read()
|
|
content = re.sub(r'{(?:stop|sql|opensql)}', '', content)
|
|
self._run_doctest_for_content(fname, content)
|
|
|
|
def test_orm(self):
|
|
self._run_doctest("orm/tutorial.rst")
|
|
|
|
def test_core(self):
|
|
self._run_doctest("core/tutorial.rst")
|
|
|
|
|
|
# unicode checker courtesy py.test
|
|
|
|
|
|
def _get_unicode_checker():
|
|
"""
|
|
Returns a doctest.OutputChecker subclass that takes in account the
|
|
ALLOW_UNICODE option to ignore u'' prefixes in strings. Useful
|
|
when the same doctest should run in Python 2 and Python 3.
|
|
|
|
An inner class is used to avoid importing "doctest" at the module
|
|
level.
|
|
"""
|
|
if hasattr(_get_unicode_checker, 'UnicodeOutputChecker'):
|
|
return _get_unicode_checker.UnicodeOutputChecker()
|
|
|
|
import doctest
|
|
import re
|
|
|
|
class UnicodeOutputChecker(doctest.OutputChecker):
|
|
"""
|
|
Copied from doctest_nose_plugin.py from the nltk project:
|
|
https://github.com/nltk/nltk
|
|
"""
|
|
|
|
_literal_re = re.compile(r"(\W|^)[uU]([rR]?[\'\"])", re.UNICODE)
|
|
|
|
def check_output(self, want, got, optionflags):
|
|
res = doctest.OutputChecker.check_output(self, want, got,
|
|
optionflags)
|
|
if res:
|
|
return True
|
|
|
|
if not (optionflags & _get_allow_unicode_flag()):
|
|
return False
|
|
|
|
else: # pragma: no cover
|
|
# the code below will end up executed only in Python 2 in
|
|
# our tests, and our coverage check runs in Python 3 only
|
|
def remove_u_prefixes(txt):
|
|
return re.sub(self._literal_re, r'\1\2', txt)
|
|
|
|
want = remove_u_prefixes(want)
|
|
got = remove_u_prefixes(got)
|
|
res = doctest.OutputChecker.check_output(self, want, got,
|
|
optionflags)
|
|
return res
|
|
|
|
_get_unicode_checker.UnicodeOutputChecker = UnicodeOutputChecker
|
|
return _get_unicode_checker.UnicodeOutputChecker()
|
|
|
|
|
|
def _get_allow_unicode_flag():
|
|
"""
|
|
Registers and returns the ALLOW_UNICODE flag.
|
|
"""
|
|
import doctest
|
|
return doctest.register_optionflag('ALLOW_UNICODE')
|