first commit
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
"""
|
||||
Mappings between PostgreSQL and Python encodings.
|
||||
"""
|
||||
|
||||
# Copyright (C) 2020 The Psycopg Team
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import codecs
|
||||
import string
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from functools import cache
|
||||
|
||||
from .errors import NotSupportedError
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ._connection_base import BaseConnection
|
||||
|
||||
# From: https://www.postgresql.org/docs/current/multibyte.html#CHARSET-TABLE
|
||||
_py_codecs = (
|
||||
(("BIG5", "WIN950", "Windows950"), "big5"),
|
||||
(("EUC_CN",), "gb2312"),
|
||||
(("EUC_JIS_2004",), "euc_jis_2004"),
|
||||
(("EUC_JP",), "euc_jp"),
|
||||
(("EUC_KR",), "euc_kr"),
|
||||
# (("EUC_TW",), not available in Python
|
||||
(("GB18030",), "gb18030"),
|
||||
(("GBK", "WIN936", "Windows936"), "gbk"),
|
||||
(("ISO_8859_5",), "iso8859-5"),
|
||||
(("ISO_8859_6",), "iso8859-6"),
|
||||
(("ISO_8859_7",), "iso8859-7"),
|
||||
(("ISO_8859_8",), "iso8859-8"),
|
||||
(("JOHAB",), "johab"),
|
||||
(("KOI8R", "KOI8"), "koi8-r"),
|
||||
(("KOI8U",), "koi8-u"),
|
||||
(("LATIN1", "ISO88591"), "iso8859-1"),
|
||||
(("LATIN10", "ISO885916"), "iso8859-16"),
|
||||
(("LATIN2", "ISO88592"), "iso8859-2"),
|
||||
(("LATIN3", "ISO88593"), "iso8859-3"),
|
||||
(("LATIN4", "ISO88594"), "iso8859-4"),
|
||||
(("LATIN5", "ISO88599"), "iso8859-9"),
|
||||
(("LATIN6", "ISO885910"), "iso8859-10"),
|
||||
(("LATIN7", "ISO885913"), "iso8859-13"),
|
||||
(("LATIN8", "ISO885914"), "iso8859-14"),
|
||||
(("LATIN9", "ISO885915"), "iso8859-15"),
|
||||
# "MULE_INTERNAL" not available in Python
|
||||
(("SHIFT_JIS_2004",), "shift_jis_2004"),
|
||||
(("SJIS", "Mskanji", "ShiftJIS", "WIN932", "Windows932"), "shift_jis"),
|
||||
# this actually means no encoding, see PostgreSQL docs
|
||||
# it is special-cased by the text loader.
|
||||
(("SQL_ASCII",), "ascii"),
|
||||
(("UHC", "WIN949", "Windows949"), "cp949"),
|
||||
(("UTF8", "Unicode"), "utf-8"),
|
||||
(("WIN1250",), "cp1250"),
|
||||
(("WIN1251", "WIN"), "cp1251"),
|
||||
(("WIN1252",), "cp1252"),
|
||||
(("WIN1253",), "cp1253"),
|
||||
(("WIN1254",), "cp1254"),
|
||||
(("WIN1255",), "cp1255"),
|
||||
(("WIN1256",), "cp1256"),
|
||||
(("WIN1257",), "cp1257"),
|
||||
(("WIN1258", "ABC", "TCVN", "TCVN5712", "VSCII"), "cp1258"),
|
||||
(("WIN866", "ALT"), "cp866"),
|
||||
(("WIN874",), "cp874"),
|
||||
)
|
||||
|
||||
py_codecs = {a.upper().encode(): v for aliases, v in _py_codecs for a in aliases}
|
||||
|
||||
# Add an alias without underscore, for lenient lookups
|
||||
py_codecs.update(
|
||||
(a.replace("_", "").encode(), v)
|
||||
for aliases, v in _py_codecs
|
||||
for a in aliases
|
||||
if "_" in a
|
||||
)
|
||||
|
||||
pg_codecs = {v: k[0].encode() for k, v in _py_codecs}
|
||||
|
||||
|
||||
def conn_encoding(conn: BaseConnection[Any] | None) -> str:
|
||||
"""
|
||||
Return the Python encoding name of a psycopg connection.
|
||||
|
||||
Default to utf8 if the connection has no encoding info.
|
||||
"""
|
||||
return conn.pgconn._encoding if conn else "utf-8"
|
||||
|
||||
|
||||
def conninfo_encoding(conninfo: str) -> str:
|
||||
"""
|
||||
Return the Python encoding name passed in a conninfo string. Default to utf8.
|
||||
|
||||
Because the input is likely to come from the user and not normalised by the
|
||||
server, be somewhat lenient (non-case-sensitive lookup, ignore noise chars).
|
||||
"""
|
||||
from .conninfo import conninfo_to_dict
|
||||
|
||||
params = conninfo_to_dict(conninfo)
|
||||
if pgenc := params.get("client_encoding"):
|
||||
try:
|
||||
return pg2pyenc(str(pgenc).encode())
|
||||
except NotSupportedError:
|
||||
pass
|
||||
|
||||
return "utf-8"
|
||||
|
||||
|
||||
@cache
|
||||
def py2pgenc(name: str) -> bytes:
|
||||
"""Convert a Python encoding name to PostgreSQL encoding name.
|
||||
|
||||
Raise LookupError if the Python encoding is unknown.
|
||||
"""
|
||||
return pg_codecs[codecs.lookup(name).name]
|
||||
|
||||
|
||||
@cache
|
||||
def pg2pyenc(name: bytes) -> str:
|
||||
"""Convert a PostgreSQL encoding name to Python encoding name.
|
||||
|
||||
Raise NotSupportedError if the PostgreSQL encoding is not supported by
|
||||
Python.
|
||||
"""
|
||||
try:
|
||||
return py_codecs[name.replace(b"-", b"").replace(b"_", b"").upper()]
|
||||
except KeyError:
|
||||
sname = name.decode("utf8", "replace")
|
||||
raise NotSupportedError(f"codec not available in Python: {sname!r}")
|
||||
|
||||
|
||||
def _as_python_identifier(s: str, prefix: str = "f") -> str:
|
||||
"""
|
||||
Reduce a string to a valid Python identifier.
|
||||
|
||||
Replace all non-valid chars with '_' and prefix the value with `!prefix` if
|
||||
the first letter is an '_'.
|
||||
"""
|
||||
if not s.isidentifier():
|
||||
if s[0] in "1234567890":
|
||||
s = prefix + s
|
||||
if not s.isidentifier():
|
||||
s = _re_clean.sub("_", s)
|
||||
# namedtuple fields cannot start with underscore. So...
|
||||
if s[0] == "_":
|
||||
s = prefix + s
|
||||
return s
|
||||
|
||||
|
||||
_re_clean = re.compile(
|
||||
f"[^{string.ascii_lowercase}{string.ascii_uppercase}{string.digits}_]"
|
||||
)
|
||||
Reference in New Issue
Block a user