Загрузить файлы в «venv/Lib/site-packages/sqlalchemy/dialects/postgresql»

This commit is contained in:
2026-07-02 20:23:30 +00:00
parent f5f72d5ce7
commit 32a68c49a8
5 changed files with 7625 additions and 0 deletions

View File

@@ -0,0 +1,166 @@
# dialects/postgresql/__init__.py
# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: https://www.opensource.org/licenses/mit-license.php
# mypy: ignore-errors
from types import ModuleType
from . import array as arraylib # noqa # keep above base and other dialects
from . import asyncpg # noqa
from . import base
from . import pg8000 # noqa
from . import psycopg # noqa
from . import psycopg2 # noqa
from . import psycopg2cffi # noqa
from .array import All
from .array import Any
from .array import ARRAY
from .array import array
from .base import BIGINT
from .base import BOOLEAN
from .base import CHAR
from .base import DATE
from .base import DOMAIN
from .base import DOUBLE_PRECISION
from .base import FLOAT
from .base import INTEGER
from .base import NUMERIC
from .base import REAL
from .base import SMALLINT
from .base import TEXT
from .base import UUID
from .base import VARCHAR
from .dml import Insert
from .dml import insert
from .ext import aggregate_order_by
from .ext import array_agg
from .ext import ExcludeConstraint
from .ext import phraseto_tsquery
from .ext import plainto_tsquery
from .ext import to_tsquery
from .ext import to_tsvector
from .ext import ts_headline
from .ext import websearch_to_tsquery
from .hstore import HSTORE
from .hstore import hstore
from .json import JSON
from .json import JSONB
from .json import JSONPATH
from .named_types import CreateDomainType
from .named_types import CreateEnumType
from .named_types import DropDomainType
from .named_types import DropEnumType
from .named_types import ENUM
from .named_types import NamedType
from .ranges import AbstractMultiRange
from .ranges import AbstractRange
from .ranges import AbstractSingleRange
from .ranges import DATEMULTIRANGE
from .ranges import DATERANGE
from .ranges import INT4MULTIRANGE
from .ranges import INT4RANGE
from .ranges import INT8MULTIRANGE
from .ranges import INT8RANGE
from .ranges import MultiRange
from .ranges import NUMMULTIRANGE
from .ranges import NUMRANGE
from .ranges import Range
from .ranges import TSMULTIRANGE
from .ranges import TSRANGE
from .ranges import TSTZMULTIRANGE
from .ranges import TSTZRANGE
from .types import BIT
from .types import BYTEA
from .types import CIDR
from .types import CITEXT
from .types import INET
from .types import INTERVAL
from .types import MACADDR
from .types import MACADDR8
from .types import MONEY
from .types import OID
from .types import REGCLASS
from .types import REGCONFIG
from .types import TIME
from .types import TIMESTAMP
from .types import TSQUERY
from .types import TSVECTOR
# Alias psycopg also as psycopg_async
psycopg_async = type(
"psycopg_async", (ModuleType,), {"dialect": psycopg.dialect_async}
)
base.dialect = dialect = psycopg2.dialect
__all__ = (
"INTEGER",
"BIGINT",
"SMALLINT",
"VARCHAR",
"CHAR",
"TEXT",
"NUMERIC",
"FLOAT",
"REAL",
"INET",
"CIDR",
"CITEXT",
"UUID",
"BIT",
"MACADDR",
"MACADDR8",
"MONEY",
"OID",
"REGCLASS",
"REGCONFIG",
"TSQUERY",
"TSVECTOR",
"DOUBLE_PRECISION",
"TIMESTAMP",
"TIME",
"DATE",
"BYTEA",
"BOOLEAN",
"INTERVAL",
"ARRAY",
"ENUM",
"DOMAIN",
"dialect",
"array",
"HSTORE",
"hstore",
"INT4RANGE",
"INT8RANGE",
"NUMRANGE",
"DATERANGE",
"INT4MULTIRANGE",
"INT8MULTIRANGE",
"NUMMULTIRANGE",
"DATEMULTIRANGE",
"TSVECTOR",
"TSRANGE",
"TSTZRANGE",
"TSMULTIRANGE",
"TSTZMULTIRANGE",
"JSON",
"JSONB",
"JSONPATH",
"Any",
"All",
"DropEnumType",
"DropDomainType",
"CreateDomainType",
"NamedType",
"CreateEnumType",
"ExcludeConstraint",
"Range",
"aggregate_order_by",
"array_agg",
"insert",
"Insert",
)

View File

@@ -0,0 +1,225 @@
# dialects/postgresql/_psycopg_common.py
# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: https://www.opensource.org/licenses/mit-license.php
# mypy: ignore-errors
from __future__ import annotations
import decimal
from .array import ARRAY as PGARRAY
from .base import _DECIMAL_TYPES
from .base import _FLOAT_TYPES
from .base import _INT_TYPES
from .base import PGDialect
from .base import PGExecutionContext
from .hstore import HSTORE
from .pg_catalog import _SpaceVector
from .pg_catalog import INT2VECTOR
from .pg_catalog import OIDVECTOR
from ... import exc
from ... import types as sqltypes
from ... import util
from ...engine import processors
_server_side_id = util.counter()
class _PsycopgNumeric(sqltypes.Numeric):
def bind_processor(self, dialect):
return None
def result_processor(self, dialect, coltype):
if self.asdecimal:
if coltype in _FLOAT_TYPES:
return processors.to_decimal_processor_factory(
decimal.Decimal, self._effective_decimal_return_scale
)
elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES:
# psycopg returns Decimal natively for 1700
return None
else:
raise exc.InvalidRequestError(
"Unknown PG numeric type: %d" % coltype
)
else:
if coltype in _FLOAT_TYPES:
# psycopg returns float natively for 701
return None
elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES:
return processors.to_float
else:
raise exc.InvalidRequestError(
"Unknown PG numeric type: %d" % coltype
)
class _PsycopgFloat(_PsycopgNumeric):
__visit_name__ = "float"
class _PsycopgHStore(HSTORE):
def bind_processor(self, dialect):
if dialect._has_native_hstore:
return None
else:
return super().bind_processor(dialect)
def result_processor(self, dialect, coltype):
if dialect._has_native_hstore:
return None
else:
return super().result_processor(dialect, coltype)
class _PsycopgARRAY(PGARRAY):
render_bind_cast = True
class _PsycopgINT2VECTOR(_SpaceVector, INT2VECTOR):
pass
class _PsycopgOIDVECTOR(_SpaceVector, OIDVECTOR):
pass
class _PGExecutionContext_common_psycopg(PGExecutionContext):
def create_server_side_cursor(self):
# use server-side cursors:
# psycopg
# https://www.psycopg.org/psycopg3/docs/advanced/cursors.html#server-side-cursors
# psycopg2
# https://www.psycopg.org/docs/usage.html#server-side-cursors
ident = "c_%s_%s" % (hex(id(self))[2:], hex(_server_side_id())[2:])
return self._dbapi_connection.cursor(ident)
class _PGDialect_common_psycopg(PGDialect):
supports_statement_cache = True
supports_server_side_cursors = True
default_paramstyle = "pyformat"
_has_native_hstore = True
colspecs = util.update_copy(
PGDialect.colspecs,
{
sqltypes.Numeric: _PsycopgNumeric,
sqltypes.Float: _PsycopgFloat,
HSTORE: _PsycopgHStore,
sqltypes.ARRAY: _PsycopgARRAY,
INT2VECTOR: _PsycopgINT2VECTOR,
OIDVECTOR: _PsycopgOIDVECTOR,
},
)
def __init__(
self,
client_encoding=None,
use_native_hstore=True,
**kwargs,
):
PGDialect.__init__(self, **kwargs)
if not use_native_hstore:
self._has_native_hstore = False
self.use_native_hstore = use_native_hstore
self.client_encoding = client_encoding
def create_connect_args(self, url):
opts = url.translate_connect_args(username="user", database="dbname")
multihosts, multiports = self._split_multihost_from_url(url)
if opts or url.query:
if not opts:
opts = {}
if "port" in opts:
opts["port"] = int(opts["port"])
opts.update(url.query)
if multihosts:
opts["host"] = ",".join(multihosts)
comma_ports = ",".join(str(p) if p else "" for p in multiports)
if comma_ports:
opts["port"] = comma_ports
return ([], opts)
else:
# no connection arguments whatsoever; psycopg2.connect()
# requires that "dsn" be present as a blank string.
return ([""], opts)
def get_isolation_level_values(self, dbapi_connection):
return (
"AUTOCOMMIT",
"READ COMMITTED",
"READ UNCOMMITTED",
"REPEATABLE READ",
"SERIALIZABLE",
)
def set_deferrable(self, connection, value):
connection.deferrable = value
def get_deferrable(self, connection):
return connection.deferrable
def _do_autocommit(self, connection, value):
connection.autocommit = value
def detect_autocommit_setting(self, dbapi_connection):
return bool(dbapi_connection.autocommit)
def do_ping(self, dbapi_connection):
before_autocommit = dbapi_connection.autocommit
if not before_autocommit:
dbapi_connection.autocommit = True
cursor = dbapi_connection.cursor()
try:
cursor.execute(self._dialect_specific_select_one)
finally:
cursor.close()
if not before_autocommit and not dbapi_connection.closed:
dbapi_connection.autocommit = before_autocommit
return True
def do_begin_twophase(self, connection, xid):
connection.connection.tpc_begin(xid)
def do_prepare_twophase(self, connection, xid):
connection.connection.tpc_prepare()
def _do_twophase(self, dbapi_conn, operation, xid, recover=False):
if recover:
if not self._twophase_idle_check(dbapi_conn):
dbapi_conn.rollback()
operation(xid)
else:
operation()
def _twophase_idle_check(self, dbapi_conn):
raise NotImplementedError
def do_rollback_twophase(
self, connection, xid, is_prepared=True, recover=False
):
dbapi_conn = connection.connection.dbapi_connection
self._do_twophase(
dbapi_conn, dbapi_conn.tpc_rollback, xid, recover=recover
)
def do_commit_twophase(
self, connection, xid, is_prepared=True, recover=False
):
dbapi_conn = connection.connection.dbapi_connection
self._do_twophase(
dbapi_conn, dbapi_conn.tpc_commit, xid, recover=recover
)
def do_recover_twophase(self, connection):
return [str(row) for row in connection.connection.tpc_recover()]

View File

@@ -0,0 +1,519 @@
# dialects/postgresql/array.py
# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: https://www.opensource.org/licenses/mit-license.php
from __future__ import annotations
import re
from typing import Any as typing_Any
from typing import Iterable
from typing import Optional
from typing import Sequence
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union
from .operators import CONTAINED_BY
from .operators import CONTAINS
from .operators import OVERLAP
from ... import types as sqltypes
from ... import util
from ...sql import expression
from ...sql import operators
from ...sql.visitors import InternalTraversal
if TYPE_CHECKING:
from ...engine.interfaces import Dialect
from ...sql._typing import _ColumnExpressionArgument
from ...sql._typing import _TypeEngineArgument
from ...sql.elements import ColumnElement
from ...sql.elements import Grouping
from ...sql.expression import BindParameter
from ...sql.operators import OperatorType
from ...sql.selectable import _SelectIterable
from ...sql.type_api import _BindProcessorType
from ...sql.type_api import _LiteralProcessorType
from ...sql.type_api import _ResultProcessorType
from ...sql.type_api import TypeEngine
from ...sql.visitors import _TraverseInternalsType
from ...util.typing import Self
_T = TypeVar("_T", bound=typing_Any)
_CT = TypeVar("_CT", bound=typing_Any)
def Any(
other: typing_Any,
arrexpr: _ColumnExpressionArgument[_T],
operator: OperatorType = operators.eq,
) -> ColumnElement[bool]:
"""A synonym for the ARRAY-level :meth:`.ARRAY.Comparator.any` method.
See that method for details.
"""
return arrexpr.any(other, operator) # type: ignore[no-any-return, union-attr] # noqa: E501
def All(
other: typing_Any,
arrexpr: _ColumnExpressionArgument[_T],
operator: OperatorType = operators.eq,
) -> ColumnElement[bool]:
"""A synonym for the ARRAY-level :meth:`.ARRAY.Comparator.all` method.
See that method for details.
"""
return arrexpr.all(other, operator) # type: ignore[no-any-return, union-attr] # noqa: E501
class array(expression.ExpressionClauseList[_T]):
"""A PostgreSQL ARRAY literal.
This is used to produce ARRAY literals in SQL expressions, e.g.::
from sqlalchemy.dialects.postgresql import array
from sqlalchemy.dialects import postgresql
from sqlalchemy import select, func
stmt = select(array([1, 2]) + array([3, 4, 5]))
print(stmt.compile(dialect=postgresql.dialect()))
Produces the SQL:
.. sourcecode:: sql
SELECT ARRAY[%(param_1)s, %(param_2)s] ||
ARRAY[%(param_3)s, %(param_4)s, %(param_5)s]) AS anon_1
An instance of :class:`.array` will always have the datatype
:class:`_types.ARRAY`. The "inner" type of the array is inferred from the
values present, unless the :paramref:`_postgresql.array.type_` keyword
argument is passed::
array(["foo", "bar"], type_=CHAR)
When constructing an empty array, the :paramref:`_postgresql.array.type_`
argument is particularly important as PostgreSQL server typically requires
a cast to be rendered for the inner type in order to render an empty array.
SQLAlchemy's compilation for the empty array will produce this cast so
that::
stmt = array([], type_=Integer)
print(stmt.compile(dialect=postgresql.dialect()))
Produces:
.. sourcecode:: sql
ARRAY[]::INTEGER[]
As required by PostgreSQL for empty arrays.
.. versionadded:: 2.0.40 added support to render empty PostgreSQL array
literals with a required cast.
Multidimensional arrays are produced by nesting :class:`.array` constructs.
The dimensionality of the final :class:`_types.ARRAY`
type is calculated by
recursively adding the dimensions of the inner :class:`_types.ARRAY`
type::
stmt = select(
array(
[array([1, 2]), array([3, 4]), array([column("q"), column("x")])]
)
)
print(stmt.compile(dialect=postgresql.dialect()))
Produces:
.. sourcecode:: sql
SELECT ARRAY[
ARRAY[%(param_1)s, %(param_2)s],
ARRAY[%(param_3)s, %(param_4)s],
ARRAY[q, x]
] AS anon_1
.. versionadded:: 1.3.6 added support for multidimensional array literals
.. seealso::
:class:`_postgresql.ARRAY`
""" # noqa: E501
__visit_name__ = "array"
stringify_dialect = "postgresql"
_traverse_internals: _TraverseInternalsType = [
("clauses", InternalTraversal.dp_clauseelement_tuple),
("type", InternalTraversal.dp_type),
]
def __init__(
self,
clauses: Iterable[_T],
*,
type_: Optional[_TypeEngineArgument[_T]] = None,
**kw: typing_Any,
):
r"""Construct an ARRAY literal.
:param clauses: iterable, such as a list, containing elements to be
rendered in the array
:param type\_: optional type. If omitted, the type is inferred
from the contents of the array.
"""
super().__init__(operators.comma_op, *clauses, **kw)
main_type = (
type_
if type_ is not None
else self.clauses[0].type if self.clauses else sqltypes.NULLTYPE
)
if isinstance(main_type, ARRAY):
self.type = ARRAY(
main_type.item_type,
dimensions=(
main_type.dimensions + 1
if main_type.dimensions is not None
else 2
),
) # type: ignore[assignment]
else:
self.type = ARRAY(main_type) # type: ignore[assignment]
@property
def _select_iterable(self) -> _SelectIterable:
return (self,)
def _bind_param(
self,
operator: OperatorType,
obj: typing_Any,
type_: Optional[TypeEngine[_T]] = None,
_assume_scalar: bool = False,
) -> BindParameter[_T]:
if _assume_scalar or operator is operators.getitem:
return expression.BindParameter(
None,
obj,
_compared_to_operator=operator,
type_=type_,
_compared_to_type=self.type,
unique=True,
)
else:
return array(
[
self._bind_param(
operator, o, _assume_scalar=True, type_=type_
)
for o in obj
]
) # type: ignore[return-value]
def self_group(
self, against: Optional[OperatorType] = None
) -> Union[Self, Grouping[_T]]:
if against in (operators.any_op, operators.all_op, operators.getitem):
return expression.Grouping(self)
else:
return self
class ARRAY(sqltypes.ARRAY[_T]):
"""PostgreSQL ARRAY type.
The :class:`_postgresql.ARRAY` type is constructed in the same way
as the core :class:`_types.ARRAY` type; a member type is required, and a
number of dimensions is recommended if the type is to be used for more
than one dimension::
from sqlalchemy.dialects import postgresql
mytable = Table(
"mytable",
metadata,
Column("data", postgresql.ARRAY(Integer, dimensions=2)),
)
The :class:`_postgresql.ARRAY` type provides all operations defined on the
core :class:`_types.ARRAY` type, including support for "dimensions",
indexed access, and simple matching such as
:meth:`.types.ARRAY.Comparator.any` and
:meth:`.types.ARRAY.Comparator.all`. :class:`_postgresql.ARRAY`
class also
provides PostgreSQL-specific methods for containment operations, including
:meth:`.postgresql.ARRAY.Comparator.contains`
:meth:`.postgresql.ARRAY.Comparator.contained_by`, and
:meth:`.postgresql.ARRAY.Comparator.overlap`, e.g.::
mytable.c.data.contains([1, 2])
Indexed access is one-based by default, to match that of PostgreSQL;
for zero-based indexed access, set
:paramref:`_postgresql.ARRAY.zero_indexes`.
Additionally, the :class:`_postgresql.ARRAY`
type does not work directly in
conjunction with the :class:`.ENUM` type. For a workaround, see the
special type at :ref:`postgresql_array_of_enum`.
.. container:: topic
**Detecting Changes in ARRAY columns when using the ORM**
The :class:`_postgresql.ARRAY` type, when used with the SQLAlchemy ORM,
does not detect in-place mutations to the array. In order to detect
these, the :mod:`sqlalchemy.ext.mutable` extension must be used, using
the :class:`.MutableList` class::
from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.ext.mutable import MutableList
class SomeOrmClass(Base):
# ...
data = Column(MutableList.as_mutable(ARRAY(Integer)))
This extension will allow "in-place" changes such to the array
such as ``.append()`` to produce events which will be detected by the
unit of work. Note that changes to elements **inside** the array,
including subarrays that are mutated in place, are **not** detected.
Alternatively, assigning a new array value to an ORM element that
replaces the old one will always trigger a change event.
.. seealso::
:class:`_types.ARRAY` - base array type
:class:`_postgresql.array` - produces a literal array value.
"""
def __init__(
self,
item_type: _TypeEngineArgument[_T],
as_tuple: bool = False,
dimensions: Optional[int] = None,
zero_indexes: bool = False,
):
"""Construct an ARRAY.
E.g.::
Column("myarray", ARRAY(Integer))
Arguments are:
:param item_type: The data type of items of this array. Note that
dimensionality is irrelevant here, so multi-dimensional arrays like
``INTEGER[][]``, are constructed as ``ARRAY(Integer)``, not as
``ARRAY(ARRAY(Integer))`` or such.
:param as_tuple=False: Specify whether return results
should be converted to tuples from lists. DBAPIs such
as psycopg2 return lists by default. When tuples are
returned, the results are hashable.
:param dimensions: if non-None, the ARRAY will assume a fixed
number of dimensions. This will cause the DDL emitted for this
ARRAY to include the exact number of bracket clauses ``[]``,
and will also optimize the performance of the type overall.
Note that PG arrays are always implicitly "non-dimensioned",
meaning they can store any number of dimensions no matter how
they were declared.
:param zero_indexes=False: when True, index values will be converted
between Python zero-based and PostgreSQL one-based indexes, e.g.
a value of one will be added to all index values before passing
to the database.
"""
if isinstance(item_type, ARRAY):
raise ValueError(
"Do not nest ARRAY types; ARRAY(basetype) "
"handles multi-dimensional arrays of basetype"
)
if isinstance(item_type, type):
item_type = item_type()
self.item_type = item_type
self.as_tuple = as_tuple
self.dimensions = dimensions
self.zero_indexes = zero_indexes
class Comparator(sqltypes.ARRAY.Comparator[_CT]):
"""Define comparison operations for :class:`_types.ARRAY`.
Note that these operations are in addition to those provided
by the base :class:`.types.ARRAY.Comparator` class, including
:meth:`.types.ARRAY.Comparator.any` and
:meth:`.types.ARRAY.Comparator.all`.
"""
def contains(
self, other: typing_Any, **kwargs: typing_Any
) -> ColumnElement[bool]:
"""Boolean expression. Test if elements are a superset of the
elements of the argument array expression.
kwargs may be ignored by this operator but are required for API
conformance.
"""
return self.operate(CONTAINS, other, result_type=sqltypes.Boolean)
def contained_by(self, other: typing_Any) -> ColumnElement[bool]:
"""Boolean expression. Test if elements are a proper subset of the
elements of the argument array expression.
"""
return self.operate(
CONTAINED_BY, other, result_type=sqltypes.Boolean
)
def overlap(self, other: typing_Any) -> ColumnElement[bool]:
"""Boolean expression. Test if array has elements in common with
an argument array expression.
"""
return self.operate(OVERLAP, other, result_type=sqltypes.Boolean)
comparator_factory = Comparator
@util.memoized_property
def _against_native_enum(self) -> bool:
return (
isinstance(self.item_type, sqltypes.Enum)
and self.item_type.native_enum
)
def literal_processor(
self, dialect: Dialect
) -> Optional[_LiteralProcessorType[_T]]:
item_proc = self.item_type.dialect_impl(dialect).literal_processor(
dialect
)
if item_proc is None:
return None
def to_str(elements: Iterable[typing_Any]) -> str:
return f"ARRAY[{', '.join(elements)}]"
def process(value: Sequence[typing_Any]) -> str:
inner = self._apply_item_processor(
value, item_proc, self.dimensions, to_str
)
return inner
return process
def bind_processor(
self, dialect: Dialect
) -> Optional[_BindProcessorType[Sequence[typing_Any]]]:
item_proc = self.item_type.dialect_impl(dialect).bind_processor(
dialect
)
def process(
value: Optional[Sequence[typing_Any]],
) -> Optional[list[typing_Any]]:
if value is None:
return value
else:
return self._apply_item_processor(
value, item_proc, self.dimensions, list
)
return process
def result_processor(
self, dialect: Dialect, coltype: object
) -> _ResultProcessorType[Sequence[typing_Any]]:
item_proc = self.item_type.dialect_impl(dialect).result_processor(
dialect, coltype
)
def process(
value: Sequence[typing_Any],
) -> Optional[Sequence[typing_Any]]:
if value is None:
return value
else:
return self._apply_item_processor(
value,
item_proc,
self.dimensions,
tuple if self.as_tuple else list,
)
if self._against_native_enum:
super_rp = process
pattern = re.compile(r"^{(.*)}$")
def handle_raw_string(value: str) -> Sequence[Optional[str]]:
inner = pattern.match(value).group(1) # type: ignore[union-attr] # noqa: E501
return _split_enum_values(inner)
def process(
value: Sequence[typing_Any],
) -> Optional[Sequence[typing_Any]]:
if value is None:
return value
# isinstance(value, str) is required to handle
# the case where a TypeDecorator for and Array of Enum is
# used like was required in sa < 1.3.17
return super_rp(
handle_raw_string(value)
if isinstance(value, str)
else value
)
return process
def _split_enum_values(array_string: str) -> Sequence[Optional[str]]:
if '"' not in array_string:
# no escape char is present so it can just split on the comma
return [
r if r != "NULL" else None
for r in (array_string.split(",") if array_string else [])
]
# handles quoted strings from:
# r'abc,"quoted","also\\\\quoted", "quoted, comma", "esc \" quot", qpr'
# returns
# ['abc', 'quoted', 'also\\quoted', 'quoted, comma', 'esc " quot', 'qpr']
text = array_string.replace(r"\"", "_$ESC_QUOTE$_")
text = text.replace(r"\\", "\\")
result = []
on_quotes = re.split(r'(")', text)
in_quotes = False
for tok in on_quotes:
if tok == '"':
in_quotes = not in_quotes
elif in_quotes:
result.append(tok.replace("_$ESC_QUOTE$_", '"'))
else:
# interpret NULL (without quotes!) as None
result.extend(
[
r if r != "NULL" else None
for r in re.findall(r"([^\s,]+),?", tok)
]
)
return result

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff