generated from kgod/ai-review-template
提交
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
|
||||
|
||||
/* Greenlet object interface */
|
||||
|
||||
#ifndef Py_GREENLETOBJECT_H
|
||||
#define Py_GREENLETOBJECT_H
|
||||
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* This is deprecated and undocumented. It does not change. */
|
||||
#define GREENLET_VERSION "1.0.0"
|
||||
|
||||
#ifndef GREENLET_MODULE
|
||||
#define implementation_ptr_t void*
|
||||
#endif
|
||||
|
||||
typedef struct _greenlet {
|
||||
PyObject_HEAD
|
||||
PyObject* weakreflist;
|
||||
PyObject* dict;
|
||||
implementation_ptr_t pimpl;
|
||||
} PyGreenlet;
|
||||
|
||||
#define PyGreenlet_Check(op) (op && PyObject_TypeCheck(op, &PyGreenlet_Type))
|
||||
|
||||
|
||||
/* C API functions */
|
||||
|
||||
/* Total number of symbols that are exported */
|
||||
#define PyGreenlet_API_pointers 12
|
||||
|
||||
#define PyGreenlet_Type_NUM 0
|
||||
#define PyExc_GreenletError_NUM 1
|
||||
#define PyExc_GreenletExit_NUM 2
|
||||
|
||||
#define PyGreenlet_New_NUM 3
|
||||
#define PyGreenlet_GetCurrent_NUM 4
|
||||
#define PyGreenlet_Throw_NUM 5
|
||||
#define PyGreenlet_Switch_NUM 6
|
||||
#define PyGreenlet_SetParent_NUM 7
|
||||
|
||||
#define PyGreenlet_MAIN_NUM 8
|
||||
#define PyGreenlet_STARTED_NUM 9
|
||||
#define PyGreenlet_ACTIVE_NUM 10
|
||||
#define PyGreenlet_GET_PARENT_NUM 11
|
||||
|
||||
#ifndef GREENLET_MODULE
|
||||
/* This section is used by modules that uses the greenlet C API */
|
||||
static void** _PyGreenlet_API = NULL;
|
||||
|
||||
# define PyGreenlet_Type \
|
||||
(*(PyTypeObject*)_PyGreenlet_API[PyGreenlet_Type_NUM])
|
||||
|
||||
# define PyExc_GreenletError \
|
||||
((PyObject*)_PyGreenlet_API[PyExc_GreenletError_NUM])
|
||||
|
||||
# define PyExc_GreenletExit \
|
||||
((PyObject*)_PyGreenlet_API[PyExc_GreenletExit_NUM])
|
||||
|
||||
/*
|
||||
* PyGreenlet_New(PyObject *args)
|
||||
*
|
||||
* greenlet.greenlet(run, parent=None)
|
||||
*/
|
||||
# define PyGreenlet_New \
|
||||
(*(PyGreenlet * (*)(PyObject * run, PyGreenlet * parent)) \
|
||||
_PyGreenlet_API[PyGreenlet_New_NUM])
|
||||
|
||||
/*
|
||||
* PyGreenlet_GetCurrent(void)
|
||||
*
|
||||
* greenlet.getcurrent()
|
||||
*/
|
||||
# define PyGreenlet_GetCurrent \
|
||||
(*(PyGreenlet * (*)(void)) _PyGreenlet_API[PyGreenlet_GetCurrent_NUM])
|
||||
|
||||
/*
|
||||
* PyGreenlet_Throw(
|
||||
* PyGreenlet *greenlet,
|
||||
* PyObject *typ,
|
||||
* PyObject *val,
|
||||
* PyObject *tb)
|
||||
*
|
||||
* g.throw(...)
|
||||
*/
|
||||
# define PyGreenlet_Throw \
|
||||
(*(PyObject * (*)(PyGreenlet * self, \
|
||||
PyObject * typ, \
|
||||
PyObject * val, \
|
||||
PyObject * tb)) \
|
||||
_PyGreenlet_API[PyGreenlet_Throw_NUM])
|
||||
|
||||
/*
|
||||
* PyGreenlet_Switch(PyGreenlet *greenlet, PyObject *args)
|
||||
*
|
||||
* g.switch(*args, **kwargs)
|
||||
*/
|
||||
# define PyGreenlet_Switch \
|
||||
(*(PyObject * \
|
||||
(*)(PyGreenlet * greenlet, PyObject * args, PyObject * kwargs)) \
|
||||
_PyGreenlet_API[PyGreenlet_Switch_NUM])
|
||||
|
||||
/*
|
||||
* PyGreenlet_SetParent(PyObject *greenlet, PyObject *new_parent)
|
||||
*
|
||||
* g.parent = new_parent
|
||||
*/
|
||||
# define PyGreenlet_SetParent \
|
||||
(*(int (*)(PyGreenlet * greenlet, PyGreenlet * nparent)) \
|
||||
_PyGreenlet_API[PyGreenlet_SetParent_NUM])
|
||||
|
||||
/*
|
||||
* PyGreenlet_GetParent(PyObject* greenlet)
|
||||
*
|
||||
* return greenlet.parent;
|
||||
*
|
||||
* This could return NULL even if there is no exception active.
|
||||
* If it does not return NULL, you are responsible for decrementing the
|
||||
* reference count.
|
||||
*/
|
||||
# define PyGreenlet_GetParent \
|
||||
(*(PyGreenlet* (*)(PyGreenlet*)) \
|
||||
_PyGreenlet_API[PyGreenlet_GET_PARENT_NUM])
|
||||
|
||||
/*
|
||||
* deprecated, undocumented alias.
|
||||
*/
|
||||
# define PyGreenlet_GET_PARENT PyGreenlet_GetParent
|
||||
|
||||
# define PyGreenlet_MAIN \
|
||||
(*(int (*)(PyGreenlet*)) \
|
||||
_PyGreenlet_API[PyGreenlet_MAIN_NUM])
|
||||
|
||||
# define PyGreenlet_STARTED \
|
||||
(*(int (*)(PyGreenlet*)) \
|
||||
_PyGreenlet_API[PyGreenlet_STARTED_NUM])
|
||||
|
||||
# define PyGreenlet_ACTIVE \
|
||||
(*(int (*)(PyGreenlet*)) \
|
||||
_PyGreenlet_API[PyGreenlet_ACTIVE_NUM])
|
||||
|
||||
|
||||
|
||||
|
||||
/* Macro that imports greenlet and initializes C API */
|
||||
/* NOTE: This has actually moved to ``greenlet._greenlet._C_API``, but we
|
||||
keep the older definition to be sure older code that might have a copy of
|
||||
the header still works. */
|
||||
# define PyGreenlet_Import() \
|
||||
{ \
|
||||
_PyGreenlet_API = (void**)PyCapsule_Import("greenlet._C_API", 0); \
|
||||
}
|
||||
|
||||
#endif /* GREENLET_MODULE */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* !Py_GREENLETOBJECT_H */
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,33 @@
|
||||
# This is a stub package designed to roughly emulate the _yaml
|
||||
# extension module, which previously existed as a standalone module
|
||||
# and has been moved into the `yaml` package namespace.
|
||||
# It does not perfectly mimic its old counterpart, but should get
|
||||
# close enough for anyone who's relying on it even when they shouldn't.
|
||||
import yaml
|
||||
|
||||
# in some circumstances, the yaml module we imoprted may be from a different version, so we need
|
||||
# to tread carefully when poking at it here (it may not have the attributes we expect)
|
||||
if not getattr(yaml, '__with_libyaml__', False):
|
||||
from sys import version_info
|
||||
|
||||
exc = ModuleNotFoundError if version_info >= (3, 6) else ImportError
|
||||
raise exc("No module named '_yaml'")
|
||||
else:
|
||||
from yaml._yaml import *
|
||||
import warnings
|
||||
warnings.warn(
|
||||
'The _yaml extension module is now located at yaml._yaml'
|
||||
' and its location is subject to change. To use the'
|
||||
' LibYAML-based parser and emitter, import from `yaml`:'
|
||||
' `from yaml import CLoader as Loader, CDumper as Dumper`.',
|
||||
DeprecationWarning
|
||||
)
|
||||
del warnings
|
||||
# Don't `del yaml` here because yaml is actually an existing
|
||||
# namespace member of _yaml.
|
||||
|
||||
__name__ = '_yaml'
|
||||
# If the module is top-level (i.e. not a part of any specific package)
|
||||
# then the attribute should be set to ''.
|
||||
# https://docs.python.org/3.8/library/types.html
|
||||
__package__ = ''
|
||||
Binary file not shown.
@@ -0,0 +1,82 @@
|
||||
# nopycln: file # undecidable cases due to explicit re-exports https://github.com/hadialqattan/pycln/issues/205
|
||||
"""adodbapi - A python DB API 2.0 (PEP 249) interface to Microsoft ADO
|
||||
|
||||
Copyright (C) 2002 Henrik Ekelund, version 2.1 by Vernon Cole
|
||||
* https://sourceforge.net/projects/adodbapi
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
# Re-exports to keep backward compatibility with existing code
|
||||
from .adodbapi import (
|
||||
Connection as Connection,
|
||||
Cursor as Cursor,
|
||||
__version__,
|
||||
connect as connect,
|
||||
dateconverter,
|
||||
)
|
||||
from .apibase import (
|
||||
BINARY as BINARY,
|
||||
DATETIME as DATETIME,
|
||||
NUMBER as NUMBER,
|
||||
ROWID as ROWID,
|
||||
STRING as STRING,
|
||||
DatabaseError as DatabaseError,
|
||||
DataError as DataError,
|
||||
Error as Error,
|
||||
FetchFailedError as FetchFailedError,
|
||||
IntegrityError as IntegrityError,
|
||||
InterfaceError as InterfaceError,
|
||||
InternalError as InternalError,
|
||||
NotSupportedError as NotSupportedError,
|
||||
OperationalError as OperationalError,
|
||||
ProgrammingError as ProgrammingError,
|
||||
Warning as Warning,
|
||||
apilevel as apilevel,
|
||||
paramstyle as paramstyle,
|
||||
threadsafety as threadsafety,
|
||||
)
|
||||
|
||||
|
||||
def Binary(aString):
|
||||
"""This function constructs an object capable of holding a binary (long) string value."""
|
||||
return bytes(aString)
|
||||
|
||||
|
||||
def Date(year, month, day):
|
||||
"This function constructs an object holding a date value."
|
||||
return dateconverter.Date(year, month, day)
|
||||
|
||||
|
||||
def Time(hour, minute, second):
|
||||
"This function constructs an object holding a time value."
|
||||
return dateconverter.Time(hour, minute, second)
|
||||
|
||||
|
||||
def Timestamp(year, month, day, hour, minute, second):
|
||||
"This function constructs an object holding a time stamp value."
|
||||
return dateconverter.Timestamp(year, month, day, hour, minute, second)
|
||||
|
||||
|
||||
def DateFromTicks(ticks):
|
||||
"""This function constructs an object holding a date value from the given ticks value
|
||||
(number of seconds since the epoch; see the documentation of the standard Python time module for details).
|
||||
"""
|
||||
return Date(*time.gmtime(ticks)[:3])
|
||||
|
||||
|
||||
def TimeFromTicks(ticks):
|
||||
"""This function constructs an object holding a time value from the given ticks value
|
||||
(number of seconds since the epoch; see the documentation of the standard Python time module for details).
|
||||
"""
|
||||
return Time(*time.gmtime(ticks)[3:6])
|
||||
|
||||
|
||||
def TimestampFromTicks(ticks):
|
||||
"""This function constructs an object holding a time stamp value from the given
|
||||
ticks value (number of seconds since the epoch;
|
||||
see the documentation of the standard Python time module for details)."""
|
||||
return Timestamp(*time.gmtime(ticks)[:6])
|
||||
|
||||
|
||||
version = "adodbapi v" + __version__
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,283 @@
|
||||
# ADO enumerated constants documented on MSDN:
|
||||
# https://learn.microsoft.com/en-us/sql/ado/reference/ado-api/ado-enumerated-constants
|
||||
# TODO: Update to https://learn.microsoft.com/en-us/sql/ado/reference/ado-api/ado-enumerated-constants
|
||||
|
||||
# IsolationLevelEnum
|
||||
adXactUnspecified = -1
|
||||
adXactBrowse = 0x100
|
||||
adXactChaos = 0x10
|
||||
adXactCursorStability = 0x1000
|
||||
adXactIsolated = 0x100000
|
||||
adXactReadCommitted = 0x1000
|
||||
adXactReadUncommitted = 0x100
|
||||
adXactRepeatableRead = 0x10000
|
||||
adXactSerializable = 0x100000
|
||||
|
||||
# CursorLocationEnum
|
||||
adUseClient = 3
|
||||
adUseServer = 2
|
||||
|
||||
# CursorTypeEnum
|
||||
adOpenDynamic = 2
|
||||
adOpenForwardOnly = 0
|
||||
adOpenKeyset = 1
|
||||
adOpenStatic = 3
|
||||
adOpenUnspecified = -1
|
||||
|
||||
# CommandTypeEnum
|
||||
adCmdText = 1
|
||||
adCmdStoredProc = 4
|
||||
adSchemaTables = 20
|
||||
|
||||
# ParameterDirectionEnum
|
||||
adParamInput = 1
|
||||
adParamInputOutput = 3
|
||||
adParamOutput = 2
|
||||
adParamReturnValue = 4
|
||||
adParamUnknown = 0
|
||||
directions = {
|
||||
0: "Unknown",
|
||||
1: "Input",
|
||||
2: "Output",
|
||||
3: "InputOutput",
|
||||
4: "Return",
|
||||
}
|
||||
|
||||
|
||||
def ado_direction_name(ado_dir):
|
||||
try:
|
||||
return "adParam" + directions[ado_dir]
|
||||
except:
|
||||
return f"unknown direction ({ado_dir})"
|
||||
|
||||
|
||||
# ObjectStateEnum
|
||||
adStateClosed = 0
|
||||
adStateOpen = 1
|
||||
adStateConnecting = 2
|
||||
adStateExecuting = 4
|
||||
adStateFetching = 8
|
||||
|
||||
# FieldAttributeEnum
|
||||
adFldMayBeNull = 0x40
|
||||
|
||||
# ConnectModeEnum
|
||||
adModeUnknown = 0
|
||||
adModeRead = 1
|
||||
adModeWrite = 2
|
||||
adModeReadWrite = 3
|
||||
adModeShareDenyRead = 4
|
||||
adModeShareDenyWrite = 8
|
||||
adModeShareExclusive = 12
|
||||
adModeShareDenyNone = 16
|
||||
adModeRecursive = 0x400000
|
||||
|
||||
# XactAttributeEnum
|
||||
adXactCommitRetaining = 131072
|
||||
adXactAbortRetaining = 262144
|
||||
|
||||
ado_error_TIMEOUT = -2147217871
|
||||
|
||||
# DataTypeEnum - ADO Data types documented at:
|
||||
# http://msdn2.microsoft.com/en-us/library/ms675318.aspx
|
||||
# TODO: Update to https://learn.microsoft.com/en-us/sql/ado/reference/ado-api/datatypeenum
|
||||
adArray = 0x2000
|
||||
adEmpty = 0x0
|
||||
adBSTR = 0x8
|
||||
adBigInt = 0x14
|
||||
adBinary = 0x80
|
||||
adBoolean = 0xB
|
||||
adChapter = 0x88
|
||||
adChar = 0x81
|
||||
adCurrency = 0x6
|
||||
adDBDate = 0x85
|
||||
adDBTime = 0x86
|
||||
adDBTimeStamp = 0x87
|
||||
adDate = 0x7
|
||||
adDecimal = 0xE
|
||||
adDouble = 0x5
|
||||
adError = 0xA
|
||||
adFileTime = 0x40
|
||||
adGUID = 0x48
|
||||
adIDispatch = 0x9
|
||||
adIUnknown = 0xD
|
||||
adInteger = 0x3
|
||||
adLongVarBinary = 0xCD
|
||||
adLongVarChar = 0xC9
|
||||
adLongVarWChar = 0xCB
|
||||
adNumeric = 0x83
|
||||
adPropVariant = 0x8A
|
||||
adSingle = 0x4
|
||||
adSmallInt = 0x2
|
||||
adTinyInt = 0x10
|
||||
adUnsignedBigInt = 0x15
|
||||
adUnsignedInt = 0x13
|
||||
adUnsignedSmallInt = 0x12
|
||||
adUnsignedTinyInt = 0x11
|
||||
adUserDefined = 0x84
|
||||
adVarBinary = 0xCC
|
||||
adVarChar = 0xC8
|
||||
adVarNumeric = 0x8B
|
||||
adVarWChar = 0xCA
|
||||
adVariant = 0xC
|
||||
adWChar = 0x82
|
||||
# Additional constants used by introspection but not ADO itself
|
||||
AUTO_FIELD_MARKER = -1000
|
||||
|
||||
adTypeNames = {
|
||||
adBSTR: "adBSTR",
|
||||
adBigInt: "adBigInt",
|
||||
adBinary: "adBinary",
|
||||
adBoolean: "adBoolean",
|
||||
adChapter: "adChapter",
|
||||
adChar: "adChar",
|
||||
adCurrency: "adCurrency",
|
||||
adDBDate: "adDBDate",
|
||||
adDBTime: "adDBTime",
|
||||
adDBTimeStamp: "adDBTimeStamp",
|
||||
adDate: "adDate",
|
||||
adDecimal: "adDecimal",
|
||||
adDouble: "adDouble",
|
||||
adEmpty: "adEmpty",
|
||||
adError: "adError",
|
||||
adFileTime: "adFileTime",
|
||||
adGUID: "adGUID",
|
||||
adIDispatch: "adIDispatch",
|
||||
adIUnknown: "adIUnknown",
|
||||
adInteger: "adInteger",
|
||||
adLongVarBinary: "adLongVarBinary",
|
||||
adLongVarChar: "adLongVarChar",
|
||||
adLongVarWChar: "adLongVarWChar",
|
||||
adNumeric: "adNumeric",
|
||||
adPropVariant: "adPropVariant",
|
||||
adSingle: "adSingle",
|
||||
adSmallInt: "adSmallInt",
|
||||
adTinyInt: "adTinyInt",
|
||||
adUnsignedBigInt: "adUnsignedBigInt",
|
||||
adUnsignedInt: "adUnsignedInt",
|
||||
adUnsignedSmallInt: "adUnsignedSmallInt",
|
||||
adUnsignedTinyInt: "adUnsignedTinyInt",
|
||||
adUserDefined: "adUserDefined",
|
||||
adVarBinary: "adVarBinary",
|
||||
adVarChar: "adVarChar",
|
||||
adVarNumeric: "adVarNumeric",
|
||||
adVarWChar: "adVarWChar",
|
||||
adVariant: "adVariant",
|
||||
adWChar: "adWChar",
|
||||
}
|
||||
|
||||
|
||||
def ado_type_name(ado_type):
|
||||
return adTypeNames.get(ado_type, f"unknown type ({ado_type})")
|
||||
|
||||
|
||||
# here in decimal, sorted by value
|
||||
# adEmpty 0 Specifies no value (DBTYPE_EMPTY).
|
||||
# adSmallInt 2 Indicates a two-byte signed integer (DBTYPE_I2).
|
||||
# adInteger 3 Indicates a four-byte signed integer (DBTYPE_I4).
|
||||
# adSingle 4 Indicates a single-precision floating-point value (DBTYPE_R4).
|
||||
# adDouble 5 Indicates a double-precision floating-point value (DBTYPE_R8).
|
||||
# adCurrency 6 Indicates a currency value (DBTYPE_CY). Currency is a fixed-point number
|
||||
# with four digits to the right of the decimal point. It is stored in an eight-byte signed integer scaled by 10,000.
|
||||
# adDate 7 Indicates a date value (DBTYPE_DATE). A date is stored as a double, the whole part of which is
|
||||
# the number of days since December 30, 1899, and the fractional part of which is the fraction of a day.
|
||||
# adBSTR 8 Indicates a null-terminated character string (Unicode) (DBTYPE_BSTR).
|
||||
# adIDispatch 9 Indicates a pointer to an IDispatch interface on a COM object (DBTYPE_IDISPATCH).
|
||||
# adError 10 Indicates a 32-bit error code (DBTYPE_ERROR).
|
||||
# adBoolean 11 Indicates a boolean value (DBTYPE_BOOL).
|
||||
# adVariant 12 Indicates an Automation Variant (DBTYPE_VARIANT).
|
||||
# adIUnknown 13 Indicates a pointer to an IUnknown interface on a COM object (DBTYPE_IUNKNOWN).
|
||||
# adDecimal 14 Indicates an exact numeric value with a fixed precision and scale (DBTYPE_DECIMAL).
|
||||
# adTinyInt 16 Indicates a one-byte signed integer (DBTYPE_I1).
|
||||
# adUnsignedTinyInt 17 Indicates a one-byte unsigned integer (DBTYPE_UI1).
|
||||
# adUnsignedSmallInt 18 Indicates a two-byte unsigned integer (DBTYPE_UI2).
|
||||
# adUnsignedInt 19 Indicates a four-byte unsigned integer (DBTYPE_UI4).
|
||||
# adBigInt 20 Indicates an eight-byte signed integer (DBTYPE_I8).
|
||||
# adUnsignedBigInt 21 Indicates an eight-byte unsigned integer (DBTYPE_UI8).
|
||||
# adFileTime 64 Indicates a 64-bit value representing the number of 100-nanosecond intervals since
|
||||
# January 1, 1601 (DBTYPE_FILETIME).
|
||||
# adGUID 72 Indicates a globally unique identifier (GUID) (DBTYPE_GUID).
|
||||
# adBinary 128 Indicates a binary value (DBTYPE_BYTES).
|
||||
# adChar 129 Indicates a string value (DBTYPE_STR).
|
||||
# adWChar 130 Indicates a null-terminated Unicode character string (DBTYPE_WSTR).
|
||||
# adNumeric 131 Indicates an exact numeric value with a fixed precision and scale (DBTYPE_NUMERIC).
|
||||
# adUserDefined 132 Indicates a user-defined variable (DBTYPE_UDT).
|
||||
# adUserDefined 132 Indicates a user-defined variable (DBTYPE_UDT).
|
||||
# adDBDate 133 Indicates a date value (yyyymmdd) (DBTYPE_DBDATE).
|
||||
# adDBTime 134 Indicates a time value (hhmmss) (DBTYPE_DBTIME).
|
||||
# adDBTimeStamp 135 Indicates a date/time stamp (yyyymmddhhmmss plus a fraction in billionths) (DBTYPE_DBTIMESTAMP).
|
||||
# adChapter 136 Indicates a four-byte chapter value that identifies rows in a child rowset (DBTYPE_HCHAPTER).
|
||||
# adPropVariant 138 Indicates an Automation PROPVARIANT (DBTYPE_PROP_VARIANT).
|
||||
# adVarNumeric 139 Indicates a numeric value (Parameter object only).
|
||||
# adVarChar 200 Indicates a string value (Parameter object only).
|
||||
# adLongVarChar 201 Indicates a long string value (Parameter object only).
|
||||
# adVarWChar 202 Indicates a null-terminated Unicode character string (Parameter object only).
|
||||
# adLongVarWChar 203 Indicates a long null-terminated Unicode string value (Parameter object only).
|
||||
# adVarBinary 204 Indicates a binary value (Parameter object only).
|
||||
# adLongVarBinary 205 Indicates a long binary value (Parameter object only).
|
||||
# adArray (Does not apply to ADOX.) 0x2000 A flag value, always combined with another data type constant,
|
||||
# that indicates an array of that other data type.
|
||||
|
||||
# Error codes to names
|
||||
adoErrors = {
|
||||
0xE7B: "adErrBoundToCommand",
|
||||
0xE94: "adErrCannotComplete",
|
||||
0xEA4: "adErrCantChangeConnection",
|
||||
0xC94: "adErrCantChangeProvider",
|
||||
0xE8C: "adErrCantConvertvalue",
|
||||
0xE8D: "adErrCantCreate",
|
||||
0xEA3: "adErrCatalogNotSet",
|
||||
0xE8E: "adErrColumnNotOnThisRow",
|
||||
0xD5D: "adErrDataConversion",
|
||||
0xE89: "adErrDataOverflow",
|
||||
0xE9A: "adErrDelResOutOfScope",
|
||||
0xEA6: "adErrDenyNotSupported",
|
||||
0xEA7: "adErrDenyTypeNotSupported",
|
||||
0xCB3: "adErrFeatureNotAvailable",
|
||||
0xEA5: "adErrFieldsUpdateFailed",
|
||||
0xC93: "adErrIllegalOperation",
|
||||
0xCAE: "adErrInTransaction",
|
||||
0xE87: "adErrIntegrityViolation",
|
||||
0xBB9: "adErrInvalidArgument",
|
||||
0xE7D: "adErrInvalidConnection",
|
||||
0xE7C: "adErrInvalidParamInfo",
|
||||
0xE82: "adErrInvalidTransaction",
|
||||
0xE91: "adErrInvalidURL",
|
||||
0xCC1: "adErrItemNotFound",
|
||||
0xBCD: "adErrNoCurrentRecord",
|
||||
0xE83: "adErrNotExecuting",
|
||||
0xE7E: "adErrNotReentrant",
|
||||
0xE78: "adErrObjectClosed",
|
||||
0xD27: "adErrObjectInCollection",
|
||||
0xD5C: "adErrObjectNotSet",
|
||||
0xE79: "adErrObjectOpen",
|
||||
0xBBA: "adErrOpeningFile",
|
||||
0xE80: "adErrOperationCancelled",
|
||||
0xE96: "adErrOutOfSpace",
|
||||
0xE88: "adErrPermissionDenied",
|
||||
0xE9E: "adErrPropConflicting",
|
||||
0xE9B: "adErrPropInvalidColumn",
|
||||
0xE9C: "adErrPropInvalidOption",
|
||||
0xE9D: "adErrPropInvalidValue",
|
||||
0xE9F: "adErrPropNotAllSettable",
|
||||
0xEA0: "adErrPropNotSet",
|
||||
0xEA1: "adErrPropNotSettable",
|
||||
0xEA2: "adErrPropNotSupported",
|
||||
0xBB8: "adErrProviderFailed",
|
||||
0xE7A: "adErrProviderNotFound",
|
||||
0xBBB: "adErrReadFile",
|
||||
0xE93: "adErrResourceExists",
|
||||
0xE92: "adErrResourceLocked",
|
||||
0xE97: "adErrResourceOutOfScope",
|
||||
0xE8A: "adErrSchemaViolation",
|
||||
0xE8B: "adErrSignMismatch",
|
||||
0xE81: "adErrStillConnecting",
|
||||
0xE7F: "adErrStillExecuting",
|
||||
0xE90: "adErrTreePermissionDenied",
|
||||
0xE8F: "adErrURLDoesNotExist",
|
||||
0xE99: "adErrURLNamedRowDoesNotExist",
|
||||
0xE98: "adErrUnavailable",
|
||||
0xE84: "adErrUnsafeOperation",
|
||||
0xE95: "adErrVolumeNotFound",
|
||||
0xBBC: "adErrWriteFile",
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,723 @@
|
||||
"""adodbapi.apibase - A python DB API 2.0 (PEP 249) interface to Microsoft ADO
|
||||
|
||||
Copyright (C) 2002 Henrik Ekelund, version 2.1 by Vernon Cole
|
||||
* https://sourceforge.net/projects/pywin32
|
||||
* https://sourceforge.net/projects/adodbapi
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import decimal
|
||||
import numbers
|
||||
import sys
|
||||
import time
|
||||
from collections.abc import Callable, Iterable, Mapping
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
from . import ado_consts as adc
|
||||
|
||||
verbose = False # debugging flag
|
||||
|
||||
|
||||
# ------- Error handlers ------
|
||||
def standardErrorHandler(connection, cursor, errorclass, errorvalue):
|
||||
err = (errorclass, errorvalue)
|
||||
try:
|
||||
connection.messages.append(err)
|
||||
except:
|
||||
pass
|
||||
if cursor is not None:
|
||||
try:
|
||||
cursor.messages.append(err)
|
||||
except:
|
||||
pass
|
||||
raise errorclass(errorvalue)
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
pass # Exception that is the base class of all other error
|
||||
# exceptions. You can use this to catch all errors with one
|
||||
# single 'except' statement. Warnings are not considered
|
||||
# errors and thus should not use this class as base. It must
|
||||
# be a subclass of the Python StandardError (defined in the
|
||||
# module exceptions).
|
||||
|
||||
|
||||
class Warning(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class InterfaceError(Error):
|
||||
pass
|
||||
|
||||
|
||||
class DatabaseError(Error):
|
||||
pass
|
||||
|
||||
|
||||
class InternalError(DatabaseError):
|
||||
pass
|
||||
|
||||
|
||||
class OperationalError(DatabaseError):
|
||||
pass
|
||||
|
||||
|
||||
class ProgrammingError(DatabaseError):
|
||||
pass
|
||||
|
||||
|
||||
class IntegrityError(DatabaseError):
|
||||
pass
|
||||
|
||||
|
||||
class DataError(DatabaseError):
|
||||
pass
|
||||
|
||||
|
||||
class NotSupportedError(DatabaseError):
|
||||
pass
|
||||
|
||||
|
||||
class FetchFailedError(OperationalError):
|
||||
"""
|
||||
Error is used by RawStoredProcedureQuerySet to determine when a fetch
|
||||
failed due to a connection being closed or there is no record set
|
||||
returned. (Non-standard, added especially for django)
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
# # # # # ----- Type Objects and Constructors ----- # # # # #
|
||||
# Many databases need to have the input in a particular format for binding to an operation's input parameters.
|
||||
# For example, if an input is destined for a DATE column, then it must be bound to the database in a particular
|
||||
# string format. Similar problems exist for "Row ID" columns or large binary items (e.g. blobs or RAW columns).
|
||||
# This presents problems for Python since the parameters to the executeXXX() method are untyped.
|
||||
# When the database module sees a Python string object, it doesn't know if it should be bound as a simple CHAR
|
||||
# column, as a raw BINARY item, or as a DATE.
|
||||
#
|
||||
# To overcome this problem, a module must provide the constructors defined below to create objects that can
|
||||
# hold special values. When passed to the cursor methods, the module can then detect the proper type of
|
||||
# the input parameter and bind it accordingly.
|
||||
|
||||
# A Cursor Object's description attribute returns information about each of the result columns of a query.
|
||||
# The type_code must compare equal to one of Type Objects defined below. Type Objects may be equal to more than
|
||||
# one type code (e.g. DATETIME could be equal to the type codes for date, time and timestamp columns;
|
||||
# see the Implementation Hints below for details).
|
||||
|
||||
# SQL NULL values are represented by the Python None singleton on input and output.
|
||||
|
||||
# Note: Usage of Unix ticks for database interfacing can cause troubles because of the limited date range they cover.
|
||||
|
||||
|
||||
# def Date(year,month,day):
|
||||
# "This function constructs an object holding a date value. "
|
||||
# return dateconverter.date(year,month,day) #dateconverter.Date(year,month,day)
|
||||
#
|
||||
# def Time(hour,minute,second):
|
||||
# "This function constructs an object holding a time value. "
|
||||
# return dateconverter.time(hour, minute, second) # dateconverter.Time(hour,minute,second)
|
||||
#
|
||||
# def Timestamp(year,month,day,hour,minute,second):
|
||||
# "This function constructs an object holding a time stamp value. "
|
||||
# return dateconverter.datetime(year,month,day,hour,minute,second)
|
||||
#
|
||||
# def DateFromTicks(ticks):
|
||||
# """This function constructs an object holding a date value from the given ticks value
|
||||
# (number of seconds since the epoch; see the documentation of the standard Python time module for details). """
|
||||
# return Date(*time.gmtime(ticks)[:3])
|
||||
#
|
||||
# def TimeFromTicks(ticks):
|
||||
# """This function constructs an object holding a time value from the given ticks value
|
||||
# (number of seconds since the epoch; see the documentation of the standard Python time module for details). """
|
||||
# return Time(*time.gmtime(ticks)[3:6])
|
||||
#
|
||||
# def TimestampFromTicks(ticks):
|
||||
# """This function constructs an object holding a time stamp value from the given
|
||||
# ticks value (number of seconds since the epoch;
|
||||
# see the documentation of the standard Python time module for details). """
|
||||
# return Timestamp(*time.gmtime(ticks)[:6])
|
||||
#
|
||||
# def Binary(aString):
|
||||
# """This function constructs an object capable of holding a binary (long) string value. """
|
||||
# b = bytes(aString)
|
||||
# return b
|
||||
# ----- Time converters ----------------------------------------------
|
||||
class TimeConverter: # this is a generic time converter skeleton
|
||||
def __init__(self): # the details will be filled in by instances
|
||||
self._ordinal_1899_12_31 = datetime.date(1899, 12, 31).toordinal() - 1
|
||||
# Use cls.types to compare if an input parameter is a datetime
|
||||
self.types = {
|
||||
# Dynamically get the types as the methods may be overriden
|
||||
type(self.Date(2000, 1, 1)),
|
||||
type(self.Time(12, 1, 1)),
|
||||
type(self.Timestamp(2000, 1, 1, 12, 1, 1)),
|
||||
datetime.datetime,
|
||||
datetime.time,
|
||||
datetime.date,
|
||||
}
|
||||
|
||||
def COMDate(self, obj):
|
||||
"""Returns a ComDate from a date-time"""
|
||||
try: # most likely a datetime
|
||||
tt = obj.timetuple()
|
||||
|
||||
try:
|
||||
ms = obj.microsecond
|
||||
except:
|
||||
ms = 0
|
||||
return self.ComDateFromTuple(tt, ms)
|
||||
except: # might be a tuple
|
||||
try:
|
||||
return self.ComDateFromTuple(obj)
|
||||
except:
|
||||
raise ValueError(f'Cannot convert "{obj!r}" to COMdate.')
|
||||
|
||||
def ComDateFromTuple(self, t, microseconds=0):
|
||||
d = datetime.date(t[0], t[1], t[2])
|
||||
integerPart = d.toordinal() - self._ordinal_1899_12_31
|
||||
ms = (t[3] * 3600 + t[4] * 60 + t[5]) * 1000000 + microseconds
|
||||
fractPart = float(ms) / 86400000000.0
|
||||
return integerPart + fractPart
|
||||
|
||||
def DateObjectFromCOMDate(self, comDate):
|
||||
"Returns an object of the wanted type from a ComDate"
|
||||
raise NotImplementedError # "Abstract class"
|
||||
|
||||
def Date(self, year, month, day):
|
||||
"This function constructs an object holding a date value."
|
||||
raise NotImplementedError # "Abstract class"
|
||||
|
||||
def Time(self, hour, minute, second):
|
||||
"This function constructs an object holding a time value."
|
||||
raise NotImplementedError # "Abstract class"
|
||||
|
||||
def Timestamp(self, year, month, day, hour, minute, second):
|
||||
"This function constructs an object holding a time stamp value."
|
||||
raise NotImplementedError # "Abstract class"
|
||||
# all purpose date to ISO format converter
|
||||
|
||||
def DateObjectToIsoFormatString(self, obj):
|
||||
"This function should return a string in the format 'YYYY-MM-dd HH:MM:SS:ms' (ms optional)"
|
||||
try: # most likely, a datetime.datetime
|
||||
s = obj.isoformat(" ")
|
||||
except (TypeError, AttributeError):
|
||||
if isinstance(obj, datetime.date):
|
||||
s = obj.isoformat() + " 00:00:00" # return exact midnight
|
||||
else:
|
||||
try: # but may be time.struct_time
|
||||
s = time.strftime("%Y-%m-%d %H:%M:%S", obj)
|
||||
except:
|
||||
raise ValueError(f'Cannot convert "{obj!r}" to isoformat')
|
||||
return s
|
||||
|
||||
|
||||
class pythonDateTimeConverter(TimeConverter): # standard since Python 2.3
|
||||
def __init__(self):
|
||||
TimeConverter.__init__(self)
|
||||
|
||||
def DateObjectFromCOMDate(self, comDate):
|
||||
if isinstance(comDate, datetime.datetime):
|
||||
odn = comDate.toordinal()
|
||||
tim = comDate.time()
|
||||
new = datetime.datetime.combine(datetime.datetime.fromordinal(odn), tim)
|
||||
return new
|
||||
# return comDate.replace(tzinfo=None) # make non aware
|
||||
else:
|
||||
fComDate = float(comDate) # ComDate is number of days since 1899-12-31
|
||||
integerPart = int(fComDate)
|
||||
floatpart = fComDate - integerPart
|
||||
##if floatpart == 0.0:
|
||||
## return datetime.date.fromordinal(integerPart + self._ordinal_1899_12_31)
|
||||
dte = datetime.datetime.fromordinal(
|
||||
integerPart + self._ordinal_1899_12_31
|
||||
) + datetime.timedelta(milliseconds=floatpart * 86400000)
|
||||
# millisecondsperday=86400000 # 24*60*60*1000
|
||||
return dte
|
||||
|
||||
def Date(self, year, month, day):
|
||||
return datetime.date(year, month, day)
|
||||
|
||||
def Time(self, hour, minute, second):
|
||||
return datetime.time(hour, minute, second)
|
||||
|
||||
def Timestamp(self, year, month, day, hour, minute, second):
|
||||
return datetime.datetime(year, month, day, hour, minute, second)
|
||||
|
||||
|
||||
class pythonTimeConverter(TimeConverter): # the old, ?nix type date and time
|
||||
def __init__(self): # caution: this Class gets confised by timezones and DST
|
||||
TimeConverter.__init__(self)
|
||||
self.types.add(time.struct_time)
|
||||
|
||||
def DateObjectFromCOMDate(self, comDate):
|
||||
"Returns ticks since 1970"
|
||||
if isinstance(comDate, datetime.datetime):
|
||||
return comDate.timetuple()
|
||||
else:
|
||||
fcomDate = float(comDate)
|
||||
secondsperday = 86400 # 24*60*60
|
||||
# ComDate is number of days since 1899-12-31, gmtime epoch is 1970-1-1 = 25569 days
|
||||
t = time.gmtime(secondsperday * (fcomDate - 25569.0))
|
||||
return t # year,month,day,hour,minute,second,weekday,julianday,daylightsaving=t
|
||||
|
||||
def Date(self, year, month, day):
|
||||
return self.Timestamp(year, month, day, 0, 0, 0)
|
||||
|
||||
def Time(self, hour, minute, second):
|
||||
return time.gmtime((hour * 60 + minute) * 60 + second)
|
||||
|
||||
def Timestamp(self, year, month, day, hour, minute, second):
|
||||
return time.localtime(
|
||||
time.mktime((year, month, day, hour, minute, second, 0, 0, -1))
|
||||
)
|
||||
|
||||
|
||||
base_dateconverter = pythonDateTimeConverter()
|
||||
|
||||
# ------ DB API required module attributes ---------------------
|
||||
threadsafety = 1 # TODO -- find out whether this module is actually BETTER than 1.
|
||||
|
||||
apilevel = "2.0" # String constant stating the supported DB API level.
|
||||
|
||||
paramstyle = "qmark" # the default parameter style
|
||||
|
||||
# ------ control for an extension which may become part of DB API 3.0 ---
|
||||
accepted_paramstyles = ("qmark", "named", "format", "pyformat", "dynamic")
|
||||
|
||||
# ------------------------------------------------------------------------------------------
|
||||
# define similar types for generic conversion routines
|
||||
adoIntegerTypes = (
|
||||
adc.adInteger,
|
||||
adc.adSmallInt,
|
||||
adc.adTinyInt,
|
||||
adc.adUnsignedInt,
|
||||
adc.adUnsignedSmallInt,
|
||||
adc.adUnsignedTinyInt,
|
||||
adc.adBoolean,
|
||||
adc.adError,
|
||||
) # max 32 bits
|
||||
adoRowIdTypes = (adc.adChapter,) # v2.1 Rose
|
||||
adoLongTypes = (adc.adBigInt, adc.adFileTime, adc.adUnsignedBigInt)
|
||||
adoExactNumericTypes = (
|
||||
adc.adDecimal,
|
||||
adc.adNumeric,
|
||||
adc.adVarNumeric,
|
||||
adc.adCurrency,
|
||||
) # v2.3 Cole
|
||||
adoApproximateNumericTypes = (adc.adDouble, adc.adSingle) # v2.1 Cole
|
||||
adoStringTypes = (
|
||||
adc.adBSTR,
|
||||
adc.adChar,
|
||||
adc.adLongVarChar,
|
||||
adc.adLongVarWChar,
|
||||
adc.adVarChar,
|
||||
adc.adVarWChar,
|
||||
adc.adWChar,
|
||||
)
|
||||
adoBinaryTypes = (adc.adBinary, adc.adLongVarBinary, adc.adVarBinary)
|
||||
adoDateTimeTypes = (adc.adDBTime, adc.adDBTimeStamp, adc.adDate, adc.adDBDate)
|
||||
adoRemainingTypes = (
|
||||
adc.adEmpty,
|
||||
adc.adIDispatch,
|
||||
adc.adIUnknown,
|
||||
adc.adPropVariant,
|
||||
adc.adArray,
|
||||
adc.adUserDefined,
|
||||
adc.adVariant,
|
||||
adc.adGUID,
|
||||
)
|
||||
|
||||
|
||||
# this class is a trick to determine whether a type is a member of a related group of types. see PEP notes
|
||||
class DBAPITypeObject:
|
||||
def __init__(self, valuesTuple):
|
||||
self.values = frozenset(valuesTuple)
|
||||
|
||||
def __eq__(self, other):
|
||||
return other in self.values
|
||||
|
||||
def __ne__(self, other):
|
||||
return other not in self.values
|
||||
|
||||
|
||||
"""This type object is used to describe columns in a database that are string-based (e.g. CHAR). """
|
||||
STRING = DBAPITypeObject(adoStringTypes)
|
||||
|
||||
"""This type object is used to describe (long) binary columns in a database (e.g. LONG, RAW, BLOBs). """
|
||||
BINARY = DBAPITypeObject(adoBinaryTypes)
|
||||
|
||||
"""This type object is used to describe numeric columns in a database. """
|
||||
NUMBER = DBAPITypeObject(
|
||||
adoIntegerTypes + adoLongTypes + adoExactNumericTypes + adoApproximateNumericTypes
|
||||
)
|
||||
|
||||
"""This type object is used to describe date/time columns in a database. """
|
||||
|
||||
DATETIME = DBAPITypeObject(adoDateTimeTypes)
|
||||
"""This type object is used to describe the "Row ID" column in a database. """
|
||||
ROWID = DBAPITypeObject(adoRowIdTypes)
|
||||
|
||||
OTHER = DBAPITypeObject(adoRemainingTypes)
|
||||
|
||||
# ------- utilities for translating python data types to ADO data types ---------------------------------
|
||||
typeMap = {
|
||||
memoryview: adc.adVarBinary,
|
||||
float: adc.adDouble,
|
||||
type(None): adc.adEmpty,
|
||||
str: adc.adBSTR,
|
||||
bool: adc.adBoolean, # v2.1 Cole
|
||||
decimal.Decimal: adc.adDecimal,
|
||||
int: adc.adBigInt,
|
||||
bytes: adc.adVarBinary,
|
||||
}
|
||||
|
||||
|
||||
def pyTypeToADOType(d):
|
||||
tp = type(d)
|
||||
try:
|
||||
return typeMap[tp]
|
||||
except KeyError: # The type was not defined in the pre-computed Type table
|
||||
from . import dateconverter
|
||||
|
||||
# maybe it is one of our supported Date/Time types
|
||||
if tp in dateconverter.types:
|
||||
return adc.adDate
|
||||
# otherwise, attempt to discern the type by probing the data object itself -- to handle duck typing
|
||||
if isinstance(d, str):
|
||||
return adc.adBSTR
|
||||
if isinstance(d, numbers.Integral):
|
||||
return adc.adBigInt
|
||||
if isinstance(d, numbers.Real):
|
||||
return adc.adDouble
|
||||
raise DataError(f'cannot convert "{d!r}" (type={tp}) to ADO')
|
||||
|
||||
|
||||
# # # # # # # # # # # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# functions to convert database values to Python objects
|
||||
# ------------------------------------------------------------------------
|
||||
# variant type : function converting variant to Python value
|
||||
def variantConvertDate(v):
|
||||
from . import dateconverter # this function only called when adodbapi is running
|
||||
|
||||
return dateconverter.DateObjectFromCOMDate(v)
|
||||
|
||||
|
||||
def cvtString(variant): # use to get old action of adodbapi v1 if desired
|
||||
return str(variant)
|
||||
|
||||
|
||||
def cvtDecimal(variant): # better name
|
||||
return _convertNumberWithCulture(variant, decimal.Decimal)
|
||||
|
||||
|
||||
def cvtNumeric(variant): # older name - don't break old code
|
||||
return cvtDecimal(variant)
|
||||
|
||||
|
||||
def cvtFloat(variant):
|
||||
return _convertNumberWithCulture(variant, float)
|
||||
|
||||
|
||||
def _convertNumberWithCulture(variant, f):
|
||||
try:
|
||||
return f(variant)
|
||||
except (ValueError, TypeError, decimal.InvalidOperation):
|
||||
try:
|
||||
europeVsUS = str(variant).replace(",", ".")
|
||||
return f(europeVsUS)
|
||||
except (ValueError, TypeError, decimal.InvalidOperation):
|
||||
pass
|
||||
|
||||
|
||||
def cvtInt(variant):
|
||||
return int(variant)
|
||||
|
||||
|
||||
def cvtLong(variant): # only important in old versions where long and int differ
|
||||
return int(variant)
|
||||
|
||||
|
||||
def cvtBuffer(variant):
|
||||
return bytes(variant)
|
||||
|
||||
|
||||
def cvtUnicode(variant):
|
||||
return str(variant)
|
||||
|
||||
|
||||
def identity(x):
|
||||
return x
|
||||
|
||||
|
||||
def cvtUnusual(variant):
|
||||
if verbose > 1:
|
||||
sys.stderr.write(f"Conversion called for Unusual data={variant!r}\n")
|
||||
return variant # cannot find conversion function -- just give the data to the user
|
||||
|
||||
|
||||
def convert_to_python(variant, func): # convert DB value into Python value
|
||||
if variant is None:
|
||||
return None
|
||||
return func(variant) # call the appropriate conversion function
|
||||
|
||||
|
||||
class MultiMap(dict[int, Callable[[object], object]]):
|
||||
# builds a dictionary from {(iterable,of,keys) : function}
|
||||
"""A dictionary of ado.type : function
|
||||
-- but you can set multiple items by passing an iterable of keys"""
|
||||
|
||||
# useful for defining conversion functions for groups of similar data types.
|
||||
def __init__(self, aDict: Mapping[Iterable[int] | int, Callable[[object], object]]):
|
||||
for k, v in aDict.items():
|
||||
self[k] = v # we must call __setitem__
|
||||
|
||||
def __setitem__(
|
||||
self, adoType: Iterable[int] | int, cvtFn: Callable[[object], object]
|
||||
):
|
||||
"set a single item, or a whole iterable of items"
|
||||
if isinstance(adoType, Iterable):
|
||||
# user passed us an iterable, set them individually
|
||||
for type in adoType:
|
||||
dict.__setitem__(self, type, cvtFn)
|
||||
else:
|
||||
dict.__setitem__(self, adoType, cvtFn)
|
||||
|
||||
|
||||
# initialize variantConversions dictionary used to convert SQL to Python
|
||||
# this is the dictionary of default conversion functions, built by the class above.
|
||||
# this becomes a class attribute for the Connection, and that attribute is used
|
||||
# to build the list of column conversion functions for the Cursor
|
||||
variantConversions = MultiMap(
|
||||
{
|
||||
adoDateTimeTypes: variantConvertDate,
|
||||
adoApproximateNumericTypes: cvtFloat,
|
||||
adoExactNumericTypes: cvtDecimal, # use to force decimal rather than unicode
|
||||
adoLongTypes: cvtLong,
|
||||
adoIntegerTypes: cvtInt,
|
||||
adoRowIdTypes: cvtInt,
|
||||
adoStringTypes: identity,
|
||||
adoBinaryTypes: cvtBuffer,
|
||||
adoRemainingTypes: cvtUnusual,
|
||||
}
|
||||
)
|
||||
|
||||
# # # # # classes to emulate the result of cursor.fetchxxx() as a sequence of sequences # # # # #
|
||||
# "an ENUM of how my low level records are laid out"
|
||||
RS_WIN_32, RS_ARRAY, RS_REMOTE = list(range(1, 4))
|
||||
|
||||
|
||||
class SQLrow: # a single database row
|
||||
# class to emulate a sequence, so that a column may be retrieved by either number or name
|
||||
def __init__(self, rows, index): # "rows" is an _SQLrows object, index is which row
|
||||
self.rows = rows # parent 'fetch' container object
|
||||
self.index = index # my row number within parent
|
||||
|
||||
def __getattr__(self, name): # used for row.columnName type of value access
|
||||
try:
|
||||
return self._getValue(self.rows.columnNames[name.lower()])
|
||||
except KeyError:
|
||||
raise AttributeError('Unknown column name "{}"'.format(name))
|
||||
|
||||
def _getValue(self, key): # key must be an integer
|
||||
if (
|
||||
self.rows.recordset_format == RS_ARRAY
|
||||
): # retrieve from two-dimensional array
|
||||
v = self.rows.ado_results[key, self.index]
|
||||
elif self.rows.recordset_format == RS_REMOTE:
|
||||
v = self.rows.ado_results[self.index][key]
|
||||
else: # pywin32 - retrieve from tuple of tuples
|
||||
v = self.rows.ado_results[key][self.index]
|
||||
if self.rows.converters is NotImplemented:
|
||||
return v
|
||||
return convert_to_python(v, self.rows.converters[key])
|
||||
|
||||
def __len__(self):
|
||||
return self.rows.numberOfColumns
|
||||
|
||||
def __getitem__(self, key): # used for row[key] type of value access
|
||||
if isinstance(key, int): # normal row[1] designation
|
||||
try:
|
||||
return self._getValue(key)
|
||||
except IndexError:
|
||||
raise
|
||||
if isinstance(key, slice):
|
||||
indices = key.indices(self.rows.numberOfColumns)
|
||||
vl = [self._getValue(i) for i in range(*indices)]
|
||||
return tuple(vl)
|
||||
try:
|
||||
return self._getValue(
|
||||
self.rows.columnNames[key.lower()]
|
||||
) # extension row[columnName] designation
|
||||
except (KeyError, TypeError):
|
||||
er, st, tr = sys.exc_info()
|
||||
raise er(f'No such key as "{key!r}" in {self!r}').with_traceback(tr)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.__next__())
|
||||
|
||||
def __next__(self):
|
||||
for n in range(self.rows.numberOfColumns):
|
||||
yield self._getValue(n)
|
||||
|
||||
def __repr__(self): # create a human readable representation
|
||||
taglist = sorted(list(self.rows.columnNames.items()), key=lambda x: x[1])
|
||||
s = "<SQLrow={"
|
||||
for name, i in taglist:
|
||||
s += f"{name}:{self._getValue(i)!r}, "
|
||||
return s[:-2] + "}>"
|
||||
|
||||
def __str__(self): # create a pretty human readable representation
|
||||
return str(
|
||||
tuple(str(self._getValue(i)) for i in range(self.rows.numberOfColumns))
|
||||
)
|
||||
|
||||
# TO-DO implement pickling an SQLrow directly
|
||||
# def __getstate__(self): return self.__dict__
|
||||
# def __setstate__(self, d): self.__dict__.update(d)
|
||||
# which basically tell pickle to treat your class just like a normal one,
|
||||
# taking self.__dict__ as representing the whole of the instance state,
|
||||
# despite the existence of the __getattr__.
|
||||
# # # #
|
||||
|
||||
|
||||
class SQLrows:
|
||||
# class to emulate a sequence for multiple rows using a container object
|
||||
def __init__(self, ado_results, numberOfRows, cursor):
|
||||
self.ado_results = ado_results # raw result of SQL get
|
||||
try:
|
||||
self.recordset_format = cursor.recordset_format
|
||||
self.numberOfColumns = cursor.numberOfColumns
|
||||
self.converters = cursor.converters
|
||||
self.columnNames = cursor.columnNames
|
||||
except AttributeError:
|
||||
self.recordset_format = RS_ARRAY
|
||||
self.numberOfColumns = 0
|
||||
self.converters = []
|
||||
self.columnNames = {}
|
||||
self.numberOfRows = numberOfRows
|
||||
|
||||
def __len__(self):
|
||||
return self.numberOfRows
|
||||
|
||||
def __getitem__(self, item): # used for row or row,column access
|
||||
if not self.ado_results:
|
||||
return []
|
||||
if isinstance(item, slice): # will return a list of row objects
|
||||
indices = item.indices(self.numberOfRows)
|
||||
return [SQLrow(self, k) for k in range(*indices)]
|
||||
elif isinstance(item, tuple) and len(item) == 2:
|
||||
# d = some_rowsObject[i,j] will return a datum from a two-dimension address
|
||||
i, j = item
|
||||
if not isinstance(j, int):
|
||||
try:
|
||||
j = self.columnNames[j.lower()] # convert named column to numeric
|
||||
except KeyError:
|
||||
raise KeyError(f"adodbapi: no such column name as {j!r}")
|
||||
if self.recordset_format == RS_ARRAY: # retrieve from two-dimensional array
|
||||
v = self.ado_results[j, i]
|
||||
elif self.recordset_format == RS_REMOTE:
|
||||
v = self.ado_results[i][j]
|
||||
else: # pywin32 - retrieve from tuple of tuples
|
||||
v = self.ado_results[j][i]
|
||||
if self.converters is NotImplemented:
|
||||
return v
|
||||
return convert_to_python(v, self.converters[j])
|
||||
else:
|
||||
row = SQLrow(self, item) # new row descriptor
|
||||
return row
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.__next__())
|
||||
|
||||
def __next__(self):
|
||||
for n in range(self.numberOfRows):
|
||||
row = SQLrow(self, n)
|
||||
yield row
|
||||
# # # # #
|
||||
|
||||
# # # # # functions to re-format SQL requests to other paramstyle requirements # # # # # # # # # #
|
||||
|
||||
|
||||
def changeNamedToQmark(
|
||||
op,
|
||||
): # convert from 'named' paramstyle to ADO required '?'mark parameters
|
||||
outOp = ""
|
||||
outparms = []
|
||||
chunks = op.split(
|
||||
"'"
|
||||
) # quote all literals -- odd numbered list results are literals.
|
||||
inQuotes = False
|
||||
for chunk in chunks:
|
||||
if inQuotes: # this is inside a quote
|
||||
if chunk == "": # double apostrophe to quote one apostrophe
|
||||
outOp = outOp[:-1] # so take one away
|
||||
else:
|
||||
outOp += "'" + chunk + "'" # else pass the quoted string as is.
|
||||
else: # is SQL code -- look for a :namedParameter
|
||||
while chunk: # some SQL string remains
|
||||
sp = chunk.split(":", 1)
|
||||
outOp += sp[0] # concat the part up to the :
|
||||
s = ""
|
||||
try:
|
||||
chunk = sp[1]
|
||||
except IndexError:
|
||||
chunk = None
|
||||
if chunk: # there was a parameter - parse it out
|
||||
i = 0
|
||||
c = chunk[0]
|
||||
while c.isalnum() or c == "_":
|
||||
i += 1
|
||||
try:
|
||||
c = chunk[i]
|
||||
except IndexError:
|
||||
break
|
||||
s = chunk[:i]
|
||||
chunk = chunk[i:]
|
||||
if s:
|
||||
outparms.append(s) # list the parameters in order
|
||||
outOp += "?" # put in the Qmark
|
||||
inQuotes = not inQuotes
|
||||
return outOp, outparms
|
||||
|
||||
|
||||
def changeFormatToQmark(
|
||||
op,
|
||||
): # convert from 'format' paramstyle to ADO required '?'mark parameters
|
||||
outOp = ""
|
||||
outparams = []
|
||||
chunks = op.split(
|
||||
"'"
|
||||
) # quote all literals -- odd numbered list results are literals.
|
||||
inQuotes = False
|
||||
for chunk in chunks:
|
||||
if inQuotes:
|
||||
if (
|
||||
outOp != "" and chunk == ""
|
||||
): # he used a double apostrophe to quote one apostrophe
|
||||
outOp = outOp[:-1] # so take one away
|
||||
else:
|
||||
outOp += "'" + chunk + "'" # else pass the quoted string as is.
|
||||
else: # is SQL code -- look for a %s parameter
|
||||
if "%(" in chunk: # ugh! pyformat!
|
||||
while chunk: # some SQL string remains
|
||||
sp = chunk.split("%(", 1)
|
||||
outOp += sp[0] # concat the part up to the %
|
||||
if len(sp) > 1:
|
||||
try:
|
||||
s, chunk = sp[1].split(")s", 1) # find the ')s'
|
||||
except ValueError:
|
||||
raise ProgrammingError(
|
||||
'Pyformat SQL has incorrect format near "%s"' % chunk
|
||||
)
|
||||
outparams.append(s)
|
||||
outOp += "?" # put in the Qmark
|
||||
else:
|
||||
chunk = None
|
||||
else: # proper '%s' format
|
||||
sp = chunk.split("%s") # make each %s
|
||||
outOp += "?".join(sp) # into ?
|
||||
inQuotes = not inQuotes # every other chunk is a quoted string
|
||||
return outOp, outparams
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,72 @@
|
||||
"""db_print.py -- a simple demo for ADO database reads."""
|
||||
|
||||
import sys
|
||||
|
||||
import adodbapi.ado_consts as adc
|
||||
|
||||
cmd_args = ("filename", "table_name")
|
||||
if "help" in sys.argv:
|
||||
print("possible settings keywords are:", cmd_args)
|
||||
sys.exit()
|
||||
|
||||
kw_args = {} # pick up filename and proxy address from command line (optionally)
|
||||
for arg in sys.argv:
|
||||
s = arg.split("=")
|
||||
if len(s) > 1:
|
||||
if s[0] in cmd_args:
|
||||
kw_args[s[0]] = s[1]
|
||||
|
||||
kw_args.setdefault(
|
||||
"filename", "test.mdb"
|
||||
) # assumes server is running from examples folder
|
||||
kw_args.setdefault("table_name", "Products") # the name of the demo table
|
||||
|
||||
# the server needs to select the provider based on his Python installation
|
||||
provider_switch = ["provider", "Microsoft.ACE.OLEDB.12.0", "Microsoft.Jet.OLEDB.4.0"]
|
||||
|
||||
# ------------------------ START HERE -------------------------------------
|
||||
# create the connection
|
||||
constr = "Provider=%(provider)s;Data Source=%(filename)s"
|
||||
import adodbapi as db
|
||||
|
||||
con = db.connect(constr, kw_args, macro_is64bit=provider_switch)
|
||||
|
||||
if kw_args["table_name"] == "?":
|
||||
print("The tables in your database are:")
|
||||
for name in con.get_table_names():
|
||||
print(name)
|
||||
else:
|
||||
# make a cursor on the connection
|
||||
with con.cursor() as c:
|
||||
# run an SQL statement on the cursor
|
||||
sql = "select * from %s" % kw_args["table_name"]
|
||||
print('performing query="%s"' % sql)
|
||||
c.execute(sql)
|
||||
|
||||
# check the results
|
||||
print(
|
||||
'result rowcount shows as= %d. (Note: -1 means "not known")' % (c.rowcount,)
|
||||
)
|
||||
print("")
|
||||
print("result data description is:")
|
||||
print(" NAME Type DispSize IntrnlSz Prec Scale Null?")
|
||||
for d in c.description:
|
||||
print(
|
||||
("%16s %-12s %8s %8d %4d %5d %s")
|
||||
% (d[0], adc.adTypeNames[d[1]], d[2], d[3], d[4], d[5], bool(d[6]))
|
||||
)
|
||||
print("")
|
||||
print("str() of first five records are...")
|
||||
|
||||
# get the results
|
||||
db = c.fetchmany(5)
|
||||
|
||||
# print them
|
||||
for rec in db:
|
||||
print(rec)
|
||||
|
||||
print("")
|
||||
print("repr() of next row is...")
|
||||
print(repr(c.fetchone()))
|
||||
print("")
|
||||
con.close()
|
||||
@@ -0,0 +1,21 @@
|
||||
"""db_table_names.py -- a simple demo for ADO database table listing."""
|
||||
|
||||
import sys
|
||||
|
||||
import adodbapi
|
||||
|
||||
try:
|
||||
databasename = sys.argv[1]
|
||||
except IndexError:
|
||||
databasename = "test.mdb"
|
||||
|
||||
provider = ["prv", "Microsoft.ACE.OLEDB.12.0", "Microsoft.Jet.OLEDB.4.0"]
|
||||
constr = "Provider=%(prv)s;Data Source=%(db)s"
|
||||
|
||||
# create the connection
|
||||
con = adodbapi.connect(constr, db=databasename, macro_is64bit=provider)
|
||||
|
||||
print("Table names in= %s" % databasename)
|
||||
|
||||
for table in con.get_table_names():
|
||||
print(table)
|
||||
@@ -0,0 +1,41 @@
|
||||
import sys
|
||||
|
||||
import adodbapi
|
||||
|
||||
try:
|
||||
import adodbapi.is64bit as is64bit
|
||||
|
||||
is64 = is64bit.Python()
|
||||
except ImportError:
|
||||
is64 = False
|
||||
|
||||
if is64:
|
||||
driver = "Microsoft.ACE.OLEDB.12.0"
|
||||
else:
|
||||
driver = "Microsoft.Jet.OLEDB.4.0"
|
||||
extended = 'Extended Properties="Excel 8.0;HDR=Yes;IMEX=1;"'
|
||||
|
||||
try: # first command line argument will be xls file name -- default to the one written by xls_write.py
|
||||
filename = sys.argv[1]
|
||||
except IndexError:
|
||||
filename = "xx.xls"
|
||||
|
||||
constr = "Provider=%s;Data Source=%s;%s" % (driver, filename, extended)
|
||||
|
||||
conn = adodbapi.connect(constr)
|
||||
|
||||
try: # second command line argument will be worksheet name -- default to first worksheet
|
||||
sheet = sys.argv[2]
|
||||
except IndexError:
|
||||
# use ADO feature to get the name of the first worksheet
|
||||
sheet = conn.get_table_names()[0]
|
||||
|
||||
print("Shreadsheet=%s Worksheet=%s" % (filename, sheet))
|
||||
print("------------------------------------------------------------")
|
||||
crsr = conn.cursor()
|
||||
sql = "SELECT * from [%s]" % sheet
|
||||
crsr.execute(sql)
|
||||
for row in crsr.fetchmany(10):
|
||||
print(repr(row))
|
||||
crsr.close()
|
||||
conn.close()
|
||||
@@ -0,0 +1,41 @@
|
||||
import datetime
|
||||
|
||||
import adodbapi
|
||||
|
||||
try:
|
||||
import adodbapi.is64bit as is64bit
|
||||
|
||||
is64 = is64bit.Python()
|
||||
except ImportError:
|
||||
is64 = False # in case the user has an old version of adodbapi
|
||||
if is64:
|
||||
driver = "Microsoft.ACE.OLEDB.12.0"
|
||||
else:
|
||||
driver = "Microsoft.Jet.OLEDB.4.0"
|
||||
filename = "xx.xls" # file will be created if it does not exist
|
||||
extended = 'Extended Properties="Excel 8.0;Readonly=False;"'
|
||||
|
||||
constr = "Provider=%s;Data Source=%s;%s" % (driver, filename, extended)
|
||||
|
||||
conn = adodbapi.connect(constr)
|
||||
with conn: # will auto commit if no errors
|
||||
with conn.cursor() as crsr:
|
||||
try:
|
||||
crsr.execute("drop table SheetOne")
|
||||
except:
|
||||
pass # just is case there is one already there
|
||||
|
||||
# create the sheet and the header row and set the types for the columns
|
||||
crsr.execute(
|
||||
"create table SheetOne (Name varchar, Rank varchar, SrvcNum integer, Weight float, Birth date)"
|
||||
)
|
||||
|
||||
sql = "INSERT INTO SheetOne (name, rank , srvcnum, weight, birth) values (?,?,?,?,?)"
|
||||
|
||||
data = ("Mike Murphy", "SSG", 123456789, 167.8, datetime.date(1922, 12, 27))
|
||||
crsr.execute(sql, data) # write the first row of data
|
||||
crsr.execute(
|
||||
sql, ["John Jones", "Pvt", 987654321, 140.0, datetime.date(1921, 7, 4)]
|
||||
) # another row of data
|
||||
conn.close()
|
||||
print("Created spreadsheet=%s worksheet=%s" % (filename, "SheetOne"))
|
||||
@@ -0,0 +1,34 @@
|
||||
"""is64bit.Python() --> boolean value of detected Python word size. is64bit.os() --> os build version"""
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
def Python():
|
||||
return sys.maxsize > 2147483647
|
||||
|
||||
|
||||
def os():
|
||||
import platform
|
||||
|
||||
pm = platform.machine()
|
||||
if pm != ".." and pm.endswith("64"): # recent 64 bit Python
|
||||
return True
|
||||
else:
|
||||
import os
|
||||
|
||||
if "PROCESSOR_ARCHITEW6432" in os.environ:
|
||||
return True # 32 bit program running on 64 bit Windows
|
||||
try:
|
||||
return os.environ["PROCESSOR_ARCHITECTURE"].endswith(
|
||||
"64"
|
||||
) # 64 bit Windows 64 bit program
|
||||
except (IndexError, KeyError):
|
||||
pass # not Windows
|
||||
try:
|
||||
return "64" in platform.architecture()[0] # this often works in Linux
|
||||
except:
|
||||
return False # is an older version of Python, assume also an older os (best we can guess)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("is64bit.Python() =", Python(), "is64bit.os() =", os())
|
||||
@@ -0,0 +1,505 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms of the
|
||||
ordinary General Public License).
|
||||
|
||||
To apply these terms, attach the following notices to the library. It is
|
||||
safest to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
||||
@@ -0,0 +1,137 @@
|
||||
"""a clumsy attempt at a macro language to let the programmer execute code on the server (ex: determine 64bit)"""
|
||||
|
||||
from . import is64bit
|
||||
|
||||
|
||||
def macro_call(macro_name, args, kwargs):
|
||||
"""allow the programmer to perform limited processing on the server by passing macro names and args
|
||||
|
||||
:new_key - the key name the macro will create
|
||||
:args[0] - macro name
|
||||
:args[1:] - any arguments
|
||||
:code - the value of the keyword item
|
||||
:kwargs - the connection keyword dictionary. ??key has been removed
|
||||
--> the value to put in for kwargs['name'] = value
|
||||
"""
|
||||
if isinstance(args, (str, str)):
|
||||
args = [
|
||||
args
|
||||
] # the user forgot to pass a sequence, so make a string into args[0]
|
||||
new_key = args[0]
|
||||
try:
|
||||
if macro_name == "is64bit":
|
||||
if is64bit.Python(): # if on 64 bit Python
|
||||
return new_key, args[1] # return first argument
|
||||
else:
|
||||
try:
|
||||
return new_key, args[2] # else return second argument (if defined)
|
||||
except IndexError:
|
||||
return new_key, "" # else return blank
|
||||
|
||||
elif (
|
||||
macro_name == "getuser"
|
||||
): # get the name of the user the server is logged in under
|
||||
if not new_key in kwargs:
|
||||
import getpass
|
||||
|
||||
return new_key, getpass.getuser()
|
||||
|
||||
elif macro_name == "getnode": # get the name of the computer running the server
|
||||
import platform
|
||||
|
||||
try:
|
||||
return new_key, args[1] % platform.node()
|
||||
except IndexError:
|
||||
return new_key, platform.node()
|
||||
|
||||
elif macro_name == "getenv": # expand the server's environment variable args[1]
|
||||
import os
|
||||
|
||||
try:
|
||||
dflt = args[2] # if not found, default from args[2]
|
||||
except IndexError: # or blank
|
||||
dflt = ""
|
||||
return new_key, os.environ.get(args[1], dflt)
|
||||
|
||||
elif macro_name == "auto_security":
|
||||
if (
|
||||
not "user" in kwargs or not kwargs["user"]
|
||||
): # missing, blank, or Null username
|
||||
return new_key, "Integrated Security=SSPI"
|
||||
return new_key, "User ID=%(user)s; Password=%(password)s" % kwargs
|
||||
|
||||
elif (
|
||||
macro_name == "find_temp_test_path"
|
||||
): # helper function for testing ado operation -- undocumented
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
return new_key, os.path.join(
|
||||
tempfile.gettempdir(), "adodbapi_test", args[1]
|
||||
)
|
||||
|
||||
raise ValueError(f"Unknown connect string macro={macro_name}")
|
||||
except:
|
||||
raise ValueError(f"Error in macro processing {macro_name} {args!r}")
|
||||
|
||||
|
||||
def process(
|
||||
args, kwargs, expand_macros=False
|
||||
): # --> connection string with keyword arguments processed.
|
||||
"""attempts to inject arguments into a connection string using Python "%" operator for strings
|
||||
|
||||
co: adodbapi connection object
|
||||
args: positional parameters from the .connect() call
|
||||
kvargs: keyword arguments from the .connect() call
|
||||
"""
|
||||
try:
|
||||
dsn = args[0]
|
||||
except IndexError:
|
||||
dsn = None
|
||||
# as a convenience the first argument may be django settings
|
||||
if isinstance(dsn, dict):
|
||||
kwargs.update(dsn)
|
||||
# the connection string is passed to the connection as part of the keyword dictionary
|
||||
elif dsn:
|
||||
kwargs["connection_string"] = dsn
|
||||
try:
|
||||
a1 = args[1]
|
||||
except IndexError:
|
||||
a1 = None
|
||||
# historically, the second positional argument might be a timeout value
|
||||
if isinstance(a1, int):
|
||||
kwargs["timeout"] = a1
|
||||
# if the second positional argument is a string, then it is user
|
||||
elif isinstance(a1, str):
|
||||
kwargs["user"] = a1
|
||||
# if the second positional argument is a dictionary, use it as keyword arguments, too
|
||||
elif isinstance(a1, dict):
|
||||
kwargs.update(a1)
|
||||
try:
|
||||
kwargs["password"] = args[2] # the third positional argument is password
|
||||
kwargs["host"] = args[3] # the fourth positional argument is host name
|
||||
kwargs["database"] = args[4] # the fifth positional argument is database name
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
# make sure connection string is defined somehow
|
||||
if not "connection_string" in kwargs:
|
||||
try: # perhaps 'dsn' was defined
|
||||
kwargs["connection_string"] = kwargs["dsn"]
|
||||
except KeyError:
|
||||
try: # as a last effort, use the "host" keyword
|
||||
kwargs["connection_string"] = kwargs["host"]
|
||||
except KeyError:
|
||||
raise TypeError("Must define 'connection_string' for ado connections")
|
||||
if expand_macros:
|
||||
for kwarg in list(kwargs.keys()):
|
||||
if kwarg.startswith("macro_"): # If a key defines a macro
|
||||
macro_name = kwarg[6:] # name without the "macro_"
|
||||
macro_code = kwargs.pop(
|
||||
kwarg
|
||||
) # we remove the macro_key and get the code to execute
|
||||
new_key, rslt = macro_call(
|
||||
macro_name, macro_code, kwargs
|
||||
) # run the code in the local context
|
||||
kwargs[new_key] = rslt # put the result back in the keywords dict
|
||||
return kwargs
|
||||
@@ -0,0 +1,88 @@
|
||||
Project
|
||||
-------
|
||||
adodbapi
|
||||
|
||||
A Python DB-API 2.0 (PEP-249) module that makes it easy to use Microsoft ADO
|
||||
for connecting with databases and other data sources using CPython.
|
||||
|
||||
Home page: <https://sourceforge.net/projects/adodbapi>
|
||||
|
||||
Features:
|
||||
* 100% DB-API 2.0 (PEP-249) compliant (including most extensions and recommendations).
|
||||
* Includes pyunit testcases that describe how to use the module.
|
||||
* Fully implemented in Python. -- runs in current versions of Python 3
|
||||
* Licensed under the LGPL license, which means that it can be used freely even in commercial programs subject to certain restrictions.
|
||||
* The user can choose between paramstyles: 'qmark' 'named' 'format' 'pyformat' 'dynamic'
|
||||
* Supports data retrieval by column name e.g.:
|
||||
for row in myCurser.execute("select name,age from students"):
|
||||
print("Student", row.name, "is", row.age, "years old.")
|
||||
* Supports user-definable system-to-Python data conversion functions (selected by ADO data type, or by column)
|
||||
|
||||
Prerequisites:
|
||||
* C Python 3.6 or higher
|
||||
and pywin32 (Mark Hammond's python for windows extensions.)
|
||||
|
||||
Installation:
|
||||
* (C-Python on Windows): Install pywin32 (`python -m pip install pywin32`) which includes adodbapi.
|
||||
* (IronPython on Windows): Download adodbapi from https://sourceforge.net/projects/adodbapi/ . Unpack the zip.
|
||||
|
||||
NOTE: ...........
|
||||
If you do not like the new default operation of returning Numeric columns as decimal.Decimal,
|
||||
you can select other options by the user defined conversion feature.
|
||||
Try:
|
||||
adodbapi.apibase.variantConversions[adodbapi.ado_consts.adNumeric] = adodbapi.apibase.cvtString
|
||||
or:
|
||||
adodbapi.apibase.variantConversions[adodbapi.ado_consts.adNumeric] = adodbapi.apibase.cvtFloat
|
||||
or:
|
||||
adodbapi.apibase.variantConversions[adodbapi.ado_consts.adNumeric] = write_your_own_conversion_function
|
||||
............
|
||||
notes for 2.6.2:
|
||||
The definitive source has been moved to https://github.com/mhammond/pywin32/tree/main/adodbapi.
|
||||
Remote has proven too hard to configure and test with Pyro4. I am moving it to unsupported status
|
||||
until I can change to a different connection method.
|
||||
what's new in version 2.6
|
||||
A cursor.prepare() method and support for prepared SQL statements.
|
||||
Lots of refactoring, especially of the Remote and Server modules (still to be treated as Beta code).
|
||||
The quick start document 'quick_reference.odt' will export as a nice-looking pdf.
|
||||
Added paramstyles 'pyformat' and 'dynamic'. If your 'paramstyle' is 'named' you _must_ pass a dictionary of
|
||||
parameters to your .execute() method. If your 'paramstyle' is 'format' 'pyformat' or 'dynamic', you _may_
|
||||
pass a dictionary of parameters -- provided your SQL operation string is formatted correctly.
|
||||
|
||||
what's new in version 2.5
|
||||
Remote module: (works on Linux!) allows a Windows computer to serve ADO databases via PyRO
|
||||
Server module: PyRO server for ADO. Run using a command like= C:>python -m adodbapi.server
|
||||
(server has simple connection string macros: is64bit, getuser, sql_provider, auto_security)
|
||||
Brief documentation included. See adodbapi/examples folder adodbapi.rtf
|
||||
New connection method conn.get_table_names() --> list of names of tables in database
|
||||
|
||||
Vastly refactored. Data conversion things have been moved to the new adodbapi.apibase module.
|
||||
Many former module-level attributes are now class attributes. (Should be more thread-safe)
|
||||
Connection objects are now context managers for transactions and will commit or rollback.
|
||||
Cursor objects are context managers and will automatically close themselves.
|
||||
Autocommit can be switched on and off.
|
||||
Keyword and positional arguments on the connect() method work as documented in PEP 249.
|
||||
Keyword arguments from the connect call can be formatted into the connection string.
|
||||
New keyword arguments defined, such as: autocommit, paramstyle, remote_proxy, remote_port.
|
||||
*** Breaking change: variantConversion lookups are simplified: the following will raise KeyError:
|
||||
oldconverter=adodbapi.variantConversions[adodbapi.adoStringTypes]
|
||||
Refactor as: oldconverter=adodbapi.variantConversions[adodbapi.adoStringTypes[0]]
|
||||
|
||||
License
|
||||
-------
|
||||
LGPL, see https://opensource.org/license/lgpl-2-1
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
Look at:
|
||||
- `adodbapi/quick_reference.md`
|
||||
- https://wiki.python.org/moin/DatabaseProgramming#The_DB-API
|
||||
- read the examples in adodbapi/examples
|
||||
- and the test cases in `adodbapi/test directory`
|
||||
|
||||
Mailing lists
|
||||
-------------
|
||||
The adodbapi mailing lists have been deactivated. Submit comments to the
|
||||
pywin32 mailing lists.
|
||||
-- the bug tracker on sourceforge.net/projects/adodbapi may be checked, (infrequently).
|
||||
-- please use: https://github.com/mhammond/pywin32/issues
|
||||
@@ -0,0 +1,16 @@
|
||||
"""call using an open ADO connection --> list of table names"""
|
||||
|
||||
from . import adodbapi
|
||||
|
||||
|
||||
def names(connection_object):
|
||||
ado = connection_object.adoConn
|
||||
schema = ado.OpenSchema(20) # constant = adSchemaTables
|
||||
|
||||
tables = []
|
||||
while not schema.EOF:
|
||||
name = adodbapi.getIndexedValue(schema.Fields, "TABLE_NAME").Value
|
||||
tables.append(name)
|
||||
schema.MoveNext()
|
||||
del schema
|
||||
return tables
|
||||
@@ -0,0 +1,68 @@
|
||||
"""adodbapi -- a pure Python PEP 249 DB-API package using Microsoft ADO
|
||||
|
||||
Adodbapi can be run on CPython 3.5 and later.
|
||||
"""
|
||||
|
||||
NAME = "adodbapi"
|
||||
MAINTAINER = "Vernon Cole"
|
||||
MAINTAINER_EMAIL = "vernondcole@gmail.com"
|
||||
DESCRIPTION = (
|
||||
"""A pure Python package implementing PEP 249 DB-API using Microsoft ADO."""
|
||||
)
|
||||
URL = "https://sourceforge.net/projects/adodbapi"
|
||||
LICENSE = "LGPL"
|
||||
CLASSIFIERS = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
|
||||
"Operating System :: Microsoft :: Windows",
|
||||
"Operating System :: POSIX :: Linux",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: SQL",
|
||||
"Topic :: Software Development",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
"Topic :: Database",
|
||||
]
|
||||
AUTHOR = "Henrik Ekelund, Vernon Cole, et.al."
|
||||
AUTHOR_EMAIL = "vernondcole@gmail.com"
|
||||
PLATFORMS = ["Windows", "Linux"]
|
||||
|
||||
VERSION = None # in case searching for version fails
|
||||
a = open("adodbapi.py") # find the version string in the source code
|
||||
for line in a:
|
||||
if "__version__" in line:
|
||||
VERSION = line.split("'")[1] # pyright: ignore[reportConstantRedefinition]
|
||||
print('adodbapi version="%s"' % VERSION)
|
||||
break
|
||||
a.close()
|
||||
|
||||
|
||||
def setup_package():
|
||||
from setuptools import setup
|
||||
from setuptools.command.build_py import build_py
|
||||
|
||||
setup(
|
||||
cmdclass={"build_py": build_py},
|
||||
name=NAME,
|
||||
maintainer=MAINTAINER,
|
||||
maintainer_email=MAINTAINER_EMAIL,
|
||||
description=DESCRIPTION,
|
||||
url=URL,
|
||||
keywords="database ado odbc dbapi db-api Microsoft SQL",
|
||||
## download_url=DOWNLOAD_URL,
|
||||
long_description=open("README.txt").read(),
|
||||
license=LICENSE,
|
||||
classifiers=CLASSIFIERS,
|
||||
author=AUTHOR,
|
||||
author_email=AUTHOR_EMAIL,
|
||||
platforms=PLATFORMS,
|
||||
version=VERSION,
|
||||
package_dir={"adodbapi": ""},
|
||||
packages=["adodbapi"],
|
||||
)
|
||||
return
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
setup_package()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,184 @@
|
||||
# Configure this to _YOUR_ environment in order to run the testcases.
|
||||
"testADOdbapiConfig.py v 2.6.2.B00"
|
||||
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# #
|
||||
# # TESTERS:
|
||||
# #
|
||||
# # You will need to make numerous modifications to this file
|
||||
# # to adapt it to your own testing environment.
|
||||
# #
|
||||
# # Skip down to the next "# #" line --
|
||||
# # -- the things you need to change are below it.
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
import platform
|
||||
import random
|
||||
import sys
|
||||
|
||||
import is64bit
|
||||
import setuptestframework
|
||||
import tryconnection
|
||||
|
||||
print("\nPython", sys.version)
|
||||
node = platform.node()
|
||||
try:
|
||||
print(
|
||||
"node=%s, is64bit.os()= %s, is64bit.Python()= %s"
|
||||
% (node, is64bit.os(), is64bit.Python())
|
||||
)
|
||||
except:
|
||||
pass
|
||||
|
||||
if "--help" in sys.argv:
|
||||
print(
|
||||
"""Valid command-line switches are:
|
||||
--package - create a temporary test package
|
||||
--all - run all possible tests
|
||||
--time - do time format test
|
||||
--nojet - do not test against an ACCESS database file
|
||||
--mssql - test against Microsoft SQL server
|
||||
--pg - test against PostgreSQL
|
||||
--mysql - test against MariaDB
|
||||
"""
|
||||
)
|
||||
exit()
|
||||
try:
|
||||
onWindows = bool(sys.getwindowsversion()) # seems to work on all versions of Python
|
||||
except:
|
||||
onWindows = False
|
||||
|
||||
# create a random name for temporary table names
|
||||
_alphabet = (
|
||||
"PYFGCRLAOEUIDHTNSQJKXBMWVZ" # why, yes, I do happen to use a dvorak keyboard
|
||||
)
|
||||
tmp = "".join([random.choice(_alphabet) for x in range(9)])
|
||||
mdb_name = "xx_" + tmp + ".mdb" # generate a non-colliding name for the temporary .mdb
|
||||
testfolder = setuptestframework.maketemp()
|
||||
|
||||
if "--package" in sys.argv:
|
||||
# create a new adodbapi module
|
||||
pth = setuptestframework.makeadopackage(testfolder)
|
||||
else:
|
||||
# use the adodbapi module in which this file appears
|
||||
pth = setuptestframework.find_ado_path()
|
||||
if pth not in sys.path:
|
||||
# look here _first_ to find modules
|
||||
sys.path.insert(1, pth)
|
||||
|
||||
# function to clean up the temporary folder -- calling program must run this function before exit.
|
||||
cleanup = setuptestframework.getcleanupfunction()
|
||||
|
||||
import adodbapi # will (hopefully) be imported using the "pth" discovered above
|
||||
|
||||
print(adodbapi.version) # show version
|
||||
print(__doc__)
|
||||
|
||||
verbose = False
|
||||
for a in sys.argv:
|
||||
if a.startswith("--verbose"):
|
||||
arg = True
|
||||
try:
|
||||
arg = int(a.split("=")[1])
|
||||
except IndexError:
|
||||
pass
|
||||
adodbapi.adodbapi.verbose = arg
|
||||
verbose = arg
|
||||
|
||||
doAllTests = "--all" in sys.argv
|
||||
doAccessTest = not ("--nojet" in sys.argv)
|
||||
doSqlServerTest = "--mssql" in sys.argv or doAllTests
|
||||
doMySqlTest = "--mysql" in sys.argv or doAllTests
|
||||
doPostgresTest = "--pg" in sys.argv or doAllTests
|
||||
doTimeTest = ("--time" in sys.argv or doAllTests) and onWindows
|
||||
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # start your environment setup here v v v
|
||||
SQL_HOST_NODE = "testsql.2txt.us,1430"
|
||||
|
||||
if doAccessTest:
|
||||
c = {
|
||||
"mdb": setuptestframework.makemdb(testfolder, mdb_name),
|
||||
# macro definition for keyword "provider" using macro "is64bit" -- see documentation
|
||||
# is64bit will return true for 64 bit versions of Python, so the macro will select the ACE provider
|
||||
"macro_is64bit": [
|
||||
"provider",
|
||||
"Microsoft.ACE.OLEDB.12.0", # 64 bit provider
|
||||
"Microsoft.Jet.OLEDB.4.0", # 32 bit provider
|
||||
],
|
||||
}
|
||||
|
||||
# ;Mode=ReadWrite;Persist Security Info=False;Jet OLEDB:Bypass UserInfo Validation=True"
|
||||
connStrAccess = "Provider=%(provider)s;Data Source=%(mdb)s"
|
||||
print(" ...Testing ACCESS connection to {} file...".format(c["mdb"]))
|
||||
doAccessTest, connStrAccess, dbAccessconnect = tryconnection.try_connection(
|
||||
verbose, connStrAccess, 10, **c
|
||||
)
|
||||
|
||||
if doSqlServerTest:
|
||||
c = {
|
||||
"host": SQL_HOST_NODE, # name of computer with SQL Server
|
||||
"database": "adotest",
|
||||
"user": "adotestuser", # None implies Windows security
|
||||
"password": "Sq1234567",
|
||||
# macro definition for keyword "security" using macro "auto_security"
|
||||
"macro_auto_security": "security",
|
||||
"provider": "MSOLEDBSQL; MARS Connection=True",
|
||||
}
|
||||
connStr = "Provider=%(provider)s; Initial Catalog=%(database)s; Data Source=%(host)s; %(security)s;"
|
||||
print(" ...Testing MS-SQL login to {}...".format(c["host"]))
|
||||
(
|
||||
doSqlServerTest,
|
||||
connStrSQLServer,
|
||||
dbSqlServerconnect,
|
||||
) = tryconnection.try_connection(verbose, connStr, 30, **c)
|
||||
|
||||
if doMySqlTest:
|
||||
c = {
|
||||
"host": "testmysql.2txt.us",
|
||||
"database": "adodbapitest",
|
||||
"user": "adotest",
|
||||
"password": "12345678",
|
||||
"port": "3330", # note the nonstandard port for obfuscation
|
||||
"driver": "MySQL ODBC 5.1 Driver",
|
||||
} # or _driver="MySQL ODBC 3.51 Driver
|
||||
c["macro_is64bit"] = [
|
||||
"provider",
|
||||
"Provider=MSDASQL;",
|
||||
] # turn on the 64 bit ODBC adapter only if needed
|
||||
cs = (
|
||||
"%(provider)sDriver={%(driver)s};Server=%(host)s;Port=3330;"
|
||||
+ "Database=%(database)s;user=%(user)s;password=%(password)s;Option=3;"
|
||||
)
|
||||
print(" ...Testing MySql login to {}...".format(c["host"]))
|
||||
doMySqlTest, connStrMySql, dbMySqlconnect = tryconnection.try_connection(
|
||||
verbose, cs, 5, **c
|
||||
)
|
||||
|
||||
|
||||
if doPostgresTest:
|
||||
_computername = "testpg.2txt.us"
|
||||
_databasename = "adotest"
|
||||
_username = "adotestuser"
|
||||
_password = "12345678"
|
||||
kws = {"timeout": 4}
|
||||
kws["macro_is64bit"] = [
|
||||
"prov_drv",
|
||||
"Provider=MSDASQL;Driver={PostgreSQL Unicode(x64)}",
|
||||
"Driver=PostgreSQL Unicode",
|
||||
]
|
||||
# get driver from https://www.postgresql.org/ftp/odbc/releases/
|
||||
# test using positional and keyword arguments (bad example for real code)
|
||||
print(" ...Testing PostgreSQL login to {}...".format(_computername))
|
||||
doPostgresTest, connStrPostgres, dbPostgresConnect = tryconnection.try_connection(
|
||||
verbose,
|
||||
"%(prov_drv)s;Server=%(host)s;Database=%(database)s;uid=%(user)s;pwd=%(password)s;port=5430;", # note nonstandard port
|
||||
_username,
|
||||
_password,
|
||||
_computername,
|
||||
_databasename,
|
||||
**kws,
|
||||
)
|
||||
|
||||
assert doAccessTest or doSqlServerTest or doMySqlTest or doPostgresTest, (
|
||||
"No database engine found for testing"
|
||||
)
|
||||
@@ -0,0 +1,879 @@
|
||||
#!/usr/bin/env python
|
||||
"""Python DB API 2.0 driver compliance unit test suite.
|
||||
|
||||
This software is Public Domain and may be used without restrictions.
|
||||
|
||||
"Now we have booze and barflies entering the discussion, plus rumours of
|
||||
DBAs on drugs... and I won't tell you what flashes through my mind each
|
||||
time I read the subject line with 'Anal Compliance' in it. All around
|
||||
this is turning out to be a thoroughly unwholesome unit test."
|
||||
|
||||
-- Ian Bicking
|
||||
"""
|
||||
|
||||
__version__ = "$Revision: 1.15.0 $"[11:-2]
|
||||
__author__ = "Stuart Bishop <stuart@stuartbishop.net>"
|
||||
|
||||
import time
|
||||
import unittest
|
||||
|
||||
# set this to "True" to follow API 2.0 to the letter
|
||||
TEST_FOR_NON_IDEMPOTENT_CLOSE = False
|
||||
|
||||
# Revision 1.15 2019/11/22 00:50:00 kf7xm
|
||||
# Make Turn off IDEMPOTENT_CLOSE a proper skipTest
|
||||
|
||||
# Revision 1.14 2013/05/20 11:02:05 kf7xm
|
||||
# Add a literal string to the format insertion test to catch trivial re-format algorithms
|
||||
|
||||
# Revision 1.13 2013/05/08 14:31:50 kf7xm
|
||||
# Quick switch to Turn off IDEMPOTENT_CLOSE test. Also: Silence teardown failure
|
||||
|
||||
|
||||
# Revision 1.12 2009/02/06 03:35:11 kf7xm
|
||||
# Tested okay with Python 3.0, includes last minute patches from Mark H.
|
||||
#
|
||||
# Revision 1.1.1.1.2.1 2008/09/20 19:54:59 rupole
|
||||
# Include latest changes from main branch
|
||||
# Updates for py3k
|
||||
#
|
||||
# Revision 1.11 2005/01/02 02:41:01 zenzen
|
||||
# Update author email address
|
||||
#
|
||||
# Revision 1.10 2003/10/09 03:14:14 zenzen
|
||||
# Add test for DB API 2.0 optional extension, where database exceptions
|
||||
# are exposed as attributes on the Connection object.
|
||||
#
|
||||
# Revision 1.9 2003/08/13 01:16:36 zenzen
|
||||
# Minor tweak from Stefan Fleiter
|
||||
#
|
||||
# Revision 1.8 2003/04/10 00:13:25 zenzen
|
||||
# Changes, as per suggestions by M.-A. Lemburg
|
||||
# - Add a table prefix, to ensure namespace collisions can always be avoided
|
||||
#
|
||||
# Revision 1.7 2003/02/26 23:33:37 zenzen
|
||||
# Break out DDL into helper functions, as per request by David Rushby
|
||||
#
|
||||
# Revision 1.6 2003/02/21 03:04:33 zenzen
|
||||
# Stuff from Henrik Ekelund:
|
||||
# added test_None
|
||||
# added test_nextset & hooks
|
||||
#
|
||||
# Revision 1.5 2003/02/17 22:08:43 zenzen
|
||||
# Implement suggestions and code from Henrik Eklund - test that cursor.arraysize
|
||||
# defaults to 1 & generic cursor.callproc test added
|
||||
#
|
||||
# Revision 1.4 2003/02/15 00:16:33 zenzen
|
||||
# Changes, as per suggestions and bug reports by M.-A. Lemburg,
|
||||
# Matthew T. Kromer, Federico Di Gregorio and Daniel Dittmar
|
||||
# - Class renamed
|
||||
# - Now a subclass of TestCase, to avoid requiring the driver stub
|
||||
# to use multiple inheritance
|
||||
# - Reversed the polarity of buggy test in test_description
|
||||
# - Test exception hierarchy correctly
|
||||
# - self.populate is now self._populate(), so if a driver stub
|
||||
# overrides self.ddl1 this change propogates
|
||||
# - VARCHAR columns now have a width, which will hopefully make the
|
||||
# DDL even more portible (this will be reversed if it causes more problems)
|
||||
# - cursor.rowcount being checked after various execute and fetchXXX methods
|
||||
# - Check for fetchall and fetchmany returning empty lists after results
|
||||
# are exhausted (already checking for empty lists if select retrieved
|
||||
# nothing
|
||||
# - Fix bugs in test_setoutputsize_basic and test_setinputsizes
|
||||
#
|
||||
|
||||
|
||||
class DatabaseAPI20Test(unittest.TestCase):
|
||||
"""Test a database self.driver for DB API 2.0 compatibility.
|
||||
This implementation tests Gadfly, but the TestCase
|
||||
is structured so that other self.drivers can subclass this
|
||||
test case to ensure compiliance with the DB-API. It is
|
||||
expected that this TestCase may be expanded in the future
|
||||
if ambiguities or edge conditions are discovered.
|
||||
|
||||
The 'Optional Extensions' are not yet being tested.
|
||||
|
||||
self.drivers should subclass this test, overriding setUp, tearDown,
|
||||
self.driver, connect_args and connect_kw_args. Class specification
|
||||
should be as follows:
|
||||
|
||||
import dbapi20
|
||||
class mytest(dbapi20.DatabaseAPI20Test):
|
||||
[...]
|
||||
|
||||
Don't 'import DatabaseAPI20Test from dbapi20', or you will
|
||||
confuse the unit tester - just 'import dbapi20'.
|
||||
"""
|
||||
|
||||
# The self.driver module. This should be the module where the 'connect'
|
||||
# method is to be found
|
||||
driver = None
|
||||
connect_args = () # List of arguments to pass to connect
|
||||
connect_kw_args = {} # Keyword arguments for connect
|
||||
table_prefix = "dbapi20test_" # If you need to specify a prefix for tables
|
||||
|
||||
ddl1 = "create table %sbooze (name varchar(20))" % table_prefix
|
||||
ddl2 = "create table %sbarflys (name varchar(20), drink varchar(30))" % table_prefix
|
||||
xddl1 = "drop table %sbooze" % table_prefix
|
||||
xddl2 = "drop table %sbarflys" % table_prefix
|
||||
|
||||
lowerfunc = "lower" # Name of stored procedure to convert string->lowercase
|
||||
|
||||
# Some drivers may need to override these helpers, for example adding
|
||||
# a 'commit' after the execute.
|
||||
def executeDDL1(self, cursor):
|
||||
cursor.execute(self.ddl1)
|
||||
|
||||
def executeDDL2(self, cursor):
|
||||
cursor.execute(self.ddl2)
|
||||
|
||||
def setUp(self):
|
||||
"""self.drivers should override this method to perform required setup
|
||||
if any is necessary, such as creating the database.
|
||||
"""
|
||||
pass
|
||||
|
||||
def tearDown(self):
|
||||
"""self.drivers should override this method to perform required cleanup
|
||||
if any is necessary, such as deleting the test database.
|
||||
The default drops the tables that may be created.
|
||||
"""
|
||||
try:
|
||||
con = self._connect()
|
||||
try:
|
||||
cur = con.cursor()
|
||||
for ddl in (self.xddl1, self.xddl2):
|
||||
try:
|
||||
cur.execute(ddl)
|
||||
con.commit()
|
||||
except self.driver.Error:
|
||||
# Assume table didn't exist. Other tests will check if
|
||||
# execute is busted.
|
||||
pass
|
||||
finally:
|
||||
con.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _connect(self):
|
||||
try:
|
||||
r = self.driver.connect(*self.connect_args, **self.connect_kw_args)
|
||||
except AttributeError:
|
||||
self.fail("No connect method found in self.driver module")
|
||||
return r
|
||||
|
||||
def test_connect(self):
|
||||
con = self._connect()
|
||||
con.close()
|
||||
|
||||
def test_apilevel(self):
|
||||
try:
|
||||
# Must exist
|
||||
apilevel = self.driver.apilevel
|
||||
# Must equal 2.0
|
||||
self.assertEqual(apilevel, "2.0")
|
||||
except AttributeError:
|
||||
self.fail("Driver doesn't define apilevel")
|
||||
|
||||
def test_threadsafety(self):
|
||||
try:
|
||||
# Must exist
|
||||
threadsafety = self.driver.threadsafety
|
||||
# Must be a valid value
|
||||
self.assertTrue(threadsafety in (0, 1, 2, 3))
|
||||
except AttributeError:
|
||||
self.fail("Driver doesn't define threadsafety")
|
||||
|
||||
def test_paramstyle(self):
|
||||
try:
|
||||
# Must exist
|
||||
paramstyle = self.driver.paramstyle
|
||||
# Must be a valid value
|
||||
self.assertTrue(
|
||||
paramstyle in ("qmark", "numeric", "named", "format", "pyformat")
|
||||
)
|
||||
except AttributeError:
|
||||
self.fail("Driver doesn't define paramstyle")
|
||||
|
||||
def test_Exceptions(self):
|
||||
# Make sure required exceptions exist, and are in the defined hierarchy.
|
||||
self.assertTrue(issubclass(self.driver.Warning, Exception))
|
||||
self.assertTrue(issubclass(self.driver.Error, Exception))
|
||||
|
||||
self.assertTrue(issubclass(self.driver.InterfaceError, self.driver.Error))
|
||||
self.assertTrue(issubclass(self.driver.DatabaseError, self.driver.Error))
|
||||
self.assertTrue(issubclass(self.driver.OperationalError, self.driver.Error))
|
||||
self.assertTrue(issubclass(self.driver.IntegrityError, self.driver.Error))
|
||||
self.assertTrue(issubclass(self.driver.InternalError, self.driver.Error))
|
||||
self.assertTrue(issubclass(self.driver.ProgrammingError, self.driver.Error))
|
||||
self.assertTrue(issubclass(self.driver.NotSupportedError, self.driver.Error))
|
||||
|
||||
def test_ExceptionsAsConnectionAttributes(self):
|
||||
# OPTIONAL EXTENSION
|
||||
# Test for the optional DB API 2.0 extension, where the exceptions
|
||||
# are exposed as attributes on the Connection object
|
||||
# I figure this optional extension will be implemented by any
|
||||
# driver author who is using this test suite, so it is enabled
|
||||
# by default.
|
||||
con = self._connect()
|
||||
drv = self.driver
|
||||
self.assertTrue(con.Warning is drv.Warning)
|
||||
self.assertTrue(con.Error is drv.Error)
|
||||
self.assertTrue(con.InterfaceError is drv.InterfaceError)
|
||||
self.assertTrue(con.DatabaseError is drv.DatabaseError)
|
||||
self.assertTrue(con.OperationalError is drv.OperationalError)
|
||||
self.assertTrue(con.IntegrityError is drv.IntegrityError)
|
||||
self.assertTrue(con.InternalError is drv.InternalError)
|
||||
self.assertTrue(con.ProgrammingError is drv.ProgrammingError)
|
||||
self.assertTrue(con.NotSupportedError is drv.NotSupportedError)
|
||||
|
||||
def test_commit(self):
|
||||
con = self._connect()
|
||||
try:
|
||||
# Commit must work, even if it doesn't do anything
|
||||
con.commit()
|
||||
finally:
|
||||
con.close()
|
||||
|
||||
def test_rollback(self):
|
||||
con = self._connect()
|
||||
# If rollback is defined, it should either work or throw
|
||||
# the documented exception
|
||||
if hasattr(con, "rollback"):
|
||||
try:
|
||||
con.rollback()
|
||||
except self.driver.NotSupportedError:
|
||||
pass
|
||||
|
||||
def test_cursor(self):
|
||||
con = self._connect()
|
||||
try:
|
||||
cur = con.cursor()
|
||||
finally:
|
||||
con.close()
|
||||
|
||||
def test_cursor_isolation(self):
|
||||
con = self._connect()
|
||||
try:
|
||||
# Make sure cursors created from the same connection have
|
||||
# the documented transaction isolation level
|
||||
cur1 = con.cursor()
|
||||
cur2 = con.cursor()
|
||||
self.executeDDL1(cur1)
|
||||
cur1.execute(
|
||||
"insert into %sbooze values ('Victoria Bitter')" % (self.table_prefix)
|
||||
)
|
||||
cur2.execute("select name from %sbooze" % self.table_prefix)
|
||||
booze = cur2.fetchall()
|
||||
self.assertEqual(len(booze), 1)
|
||||
self.assertEqual(len(booze[0]), 1)
|
||||
self.assertEqual(booze[0][0], "Victoria Bitter")
|
||||
finally:
|
||||
con.close()
|
||||
|
||||
def test_description(self):
|
||||
con = self._connect()
|
||||
try:
|
||||
cur = con.cursor()
|
||||
self.executeDDL1(cur)
|
||||
self.assertEqual(
|
||||
cur.description,
|
||||
None,
|
||||
"cursor.description should be none after executing a "
|
||||
"statement that can return no rows (such as DDL)",
|
||||
)
|
||||
cur.execute("select name from %sbooze" % self.table_prefix)
|
||||
self.assertEqual(
|
||||
len(cur.description), 1, "cursor.description describes too many columns"
|
||||
)
|
||||
self.assertEqual(
|
||||
len(cur.description[0]),
|
||||
7,
|
||||
"cursor.description[x] tuples must have 7 elements",
|
||||
)
|
||||
self.assertEqual(
|
||||
cur.description[0][0].lower(),
|
||||
"name",
|
||||
"cursor.description[x][0] must return column name",
|
||||
)
|
||||
self.assertEqual(
|
||||
cur.description[0][1],
|
||||
self.driver.STRING,
|
||||
"cursor.description[x][1] must return column type. Got %r"
|
||||
% cur.description[0][1],
|
||||
)
|
||||
|
||||
# Make sure self.description gets reset
|
||||
self.executeDDL2(cur)
|
||||
self.assertEqual(
|
||||
cur.description,
|
||||
None,
|
||||
"cursor.description not being set to None when executing "
|
||||
"no-result statements (eg. DDL)",
|
||||
)
|
||||
finally:
|
||||
con.close()
|
||||
|
||||
def test_rowcount(self):
|
||||
con = self._connect()
|
||||
try:
|
||||
cur = con.cursor()
|
||||
self.executeDDL1(cur)
|
||||
self.assertTrue(
|
||||
cur.rowcount in (-1, 0), # Bug #543885
|
||||
"cursor.rowcount should be -1 or 0 after executing no-result "
|
||||
"statements",
|
||||
)
|
||||
cur.execute(
|
||||
"insert into %sbooze values ('Victoria Bitter')" % (self.table_prefix)
|
||||
)
|
||||
self.assertTrue(
|
||||
cur.rowcount in (-1, 1),
|
||||
"cursor.rowcount should == number or rows inserted, or "
|
||||
"set to -1 after executing an insert statement",
|
||||
)
|
||||
cur.execute("select name from %sbooze" % self.table_prefix)
|
||||
self.assertTrue(
|
||||
cur.rowcount in (-1, 1),
|
||||
"cursor.rowcount should == number of rows returned, or "
|
||||
"set to -1 after executing a select statement",
|
||||
)
|
||||
self.executeDDL2(cur)
|
||||
self.assertEqual(
|
||||
cur.rowcount,
|
||||
-1,
|
||||
"cursor.rowcount not being reset to -1 after executing "
|
||||
"no-result statements",
|
||||
)
|
||||
finally:
|
||||
con.close()
|
||||
|
||||
lower_func = "lower"
|
||||
|
||||
def test_callproc(self):
|
||||
con = self._connect()
|
||||
try:
|
||||
cur = con.cursor()
|
||||
if self.lower_func and hasattr(cur, "callproc"):
|
||||
r = cur.callproc(self.lower_func, ("FOO",))
|
||||
self.assertEqual(len(r), 1)
|
||||
self.assertEqual(r[0], "FOO")
|
||||
r = cur.fetchall()
|
||||
self.assertEqual(len(r), 1, "callproc produced no result set")
|
||||
self.assertEqual(len(r[0]), 1, "callproc produced invalid result set")
|
||||
self.assertEqual(r[0][0], "foo", "callproc produced invalid results")
|
||||
finally:
|
||||
con.close()
|
||||
|
||||
def test_close(self):
|
||||
con = self._connect()
|
||||
try:
|
||||
cur = con.cursor()
|
||||
finally:
|
||||
con.close()
|
||||
|
||||
# cursor.execute should raise an Error if called after connection
|
||||
# closed
|
||||
self.assertRaises(self.driver.Error, self.executeDDL1, cur)
|
||||
|
||||
# connection.commit should raise an Error if called after connection'
|
||||
# closed.'
|
||||
self.assertRaises(self.driver.Error, con.commit)
|
||||
|
||||
# connection.close should raise an Error if called more than once
|
||||
#!!! reasonable persons differ about the usefulness of this test and this feature !!!
|
||||
if TEST_FOR_NON_IDEMPOTENT_CLOSE:
|
||||
self.assertRaises(self.driver.Error, con.close)
|
||||
else:
|
||||
self.skipTest(
|
||||
"Non-idempotent close is considered a bad thing by some people."
|
||||
)
|
||||
|
||||
def test_execute(self):
|
||||
con = self._connect()
|
||||
try:
|
||||
cur = con.cursor()
|
||||
self._paraminsert(cur)
|
||||
finally:
|
||||
con.close()
|
||||
|
||||
def _paraminsert(self, cur):
|
||||
self.executeDDL2(cur)
|
||||
cur.execute(
|
||||
"insert into %sbarflys values ('Victoria Bitter', 'thi%%s :may ca%%(u)se? troub:1e')"
|
||||
% (self.table_prefix)
|
||||
)
|
||||
self.assertTrue(cur.rowcount in (-1, 1))
|
||||
|
||||
if self.driver.paramstyle == "qmark":
|
||||
cur.execute(
|
||||
"insert into %sbarflys values (?, 'thi%%s :may ca%%(u)se? troub:1e')"
|
||||
% self.table_prefix,
|
||||
("Cooper's",),
|
||||
)
|
||||
elif self.driver.paramstyle == "numeric":
|
||||
cur.execute(
|
||||
"insert into %sbarflys values (:1, 'thi%%s :may ca%%(u)se? troub:1e')"
|
||||
% self.table_prefix,
|
||||
("Cooper's",),
|
||||
)
|
||||
elif self.driver.paramstyle == "named":
|
||||
cur.execute(
|
||||
"insert into %sbarflys values (:beer, 'thi%%s :may ca%%(u)se? troub:1e')"
|
||||
% self.table_prefix,
|
||||
{"beer": "Cooper's"},
|
||||
)
|
||||
elif self.driver.paramstyle == "format":
|
||||
cur.execute(
|
||||
"insert into %sbarflys values (%%s, 'thi%%s :may ca%%(u)se? troub:1e')"
|
||||
% self.table_prefix,
|
||||
("Cooper's",),
|
||||
)
|
||||
elif self.driver.paramstyle == "pyformat":
|
||||
cur.execute(
|
||||
"insert into %sbarflys values (%%(beer)s, 'thi%%s :may ca%%(u)se? troub:1e')"
|
||||
% self.table_prefix,
|
||||
{"beer": "Cooper's"},
|
||||
)
|
||||
else:
|
||||
self.fail("Invalid paramstyle")
|
||||
self.assertTrue(cur.rowcount in (-1, 1))
|
||||
|
||||
cur.execute("select name, drink from %sbarflys" % self.table_prefix)
|
||||
res = cur.fetchall()
|
||||
self.assertEqual(len(res), 2, "cursor.fetchall returned too few rows")
|
||||
beers = [res[0][0], res[1][0]]
|
||||
beers.sort()
|
||||
self.assertEqual(
|
||||
beers[0],
|
||||
"Cooper's",
|
||||
"cursor.fetchall retrieved incorrect data, or data inserted incorrectly",
|
||||
)
|
||||
self.assertEqual(
|
||||
beers[1],
|
||||
"Victoria Bitter",
|
||||
"cursor.fetchall retrieved incorrect data, or data inserted incorrectly",
|
||||
)
|
||||
trouble = "thi%s :may ca%(u)se? troub:1e"
|
||||
self.assertEqual(
|
||||
res[0][1],
|
||||
trouble,
|
||||
"cursor.fetchall retrieved incorrect data, or data inserted "
|
||||
f"incorrectly. Got={res[0][1]!r}, Expected={trouble!r}",
|
||||
)
|
||||
self.assertEqual(
|
||||
res[1][1],
|
||||
trouble,
|
||||
"cursor.fetchall retrieved incorrect data, or data inserted "
|
||||
f"incorrectly. Got={res[1][1]!r}, Expected={trouble!r}",
|
||||
)
|
||||
|
||||
def test_executemany(self):
|
||||
con = self._connect()
|
||||
try:
|
||||
cur = con.cursor()
|
||||
self.executeDDL1(cur)
|
||||
largs = [("Cooper's",), ("Boag's",)]
|
||||
margs = [{"beer": "Cooper's"}, {"beer": "Boag's"}]
|
||||
if self.driver.paramstyle == "qmark":
|
||||
cur.executemany(
|
||||
"insert into %sbooze values (?)" % self.table_prefix, largs
|
||||
)
|
||||
elif self.driver.paramstyle == "numeric":
|
||||
cur.executemany(
|
||||
"insert into %sbooze values (:1)" % self.table_prefix, largs
|
||||
)
|
||||
elif self.driver.paramstyle == "named":
|
||||
cur.executemany(
|
||||
"insert into %sbooze values (:beer)" % self.table_prefix, margs
|
||||
)
|
||||
elif self.driver.paramstyle == "format":
|
||||
cur.executemany(
|
||||
"insert into %sbooze values (%%s)" % self.table_prefix, largs
|
||||
)
|
||||
elif self.driver.paramstyle == "pyformat":
|
||||
cur.executemany(
|
||||
"insert into %sbooze values (%%(beer)s)" % (self.table_prefix),
|
||||
margs,
|
||||
)
|
||||
else:
|
||||
self.fail("Unknown paramstyle")
|
||||
self.assertTrue(
|
||||
cur.rowcount in (-1, 2),
|
||||
"insert using cursor.executemany set cursor.rowcount to "
|
||||
"incorrect value %r" % cur.rowcount,
|
||||
)
|
||||
cur.execute("select name from %sbooze" % self.table_prefix)
|
||||
res = cur.fetchall()
|
||||
self.assertEqual(
|
||||
len(res), 2, "cursor.fetchall retrieved incorrect number of rows"
|
||||
)
|
||||
beers = [res[0][0], res[1][0]]
|
||||
beers.sort()
|
||||
self.assertEqual(
|
||||
beers[0], "Boag's", 'incorrect data "%s" retrieved' % beers[0]
|
||||
)
|
||||
self.assertEqual(beers[1], "Cooper's", "incorrect data retrieved")
|
||||
finally:
|
||||
con.close()
|
||||
|
||||
def test_fetchone(self):
|
||||
con = self._connect()
|
||||
try:
|
||||
cur = con.cursor()
|
||||
|
||||
# cursor.fetchone should raise an Error if called before
|
||||
# executing a select-type query
|
||||
self.assertRaises(self.driver.Error, cur.fetchone)
|
||||
|
||||
# cursor.fetchone should raise an Error if called after
|
||||
# executing a query that cannnot return rows
|
||||
self.executeDDL1(cur)
|
||||
self.assertRaises(self.driver.Error, cur.fetchone)
|
||||
|
||||
cur.execute("select name from %sbooze" % self.table_prefix)
|
||||
self.assertEqual(
|
||||
cur.fetchone(),
|
||||
None,
|
||||
"cursor.fetchone should return None if a query retrieves no rows",
|
||||
)
|
||||
self.assertTrue(cur.rowcount in (-1, 0))
|
||||
|
||||
# cursor.fetchone should raise an Error if called after
|
||||
# executing a query that cannnot return rows
|
||||
cur.execute(
|
||||
"insert into %sbooze values ('Victoria Bitter')" % (self.table_prefix)
|
||||
)
|
||||
self.assertRaises(self.driver.Error, cur.fetchone)
|
||||
|
||||
cur.execute("select name from %sbooze" % self.table_prefix)
|
||||
r = cur.fetchone()
|
||||
self.assertEqual(
|
||||
len(r), 1, "cursor.fetchone should have retrieved a single row"
|
||||
)
|
||||
self.assertEqual(
|
||||
r[0], "Victoria Bitter", "cursor.fetchone retrieved incorrect data"
|
||||
)
|
||||
self.assertEqual(
|
||||
cur.fetchone(),
|
||||
None,
|
||||
"cursor.fetchone should return None if no more rows available",
|
||||
)
|
||||
self.assertTrue(cur.rowcount in (-1, 1))
|
||||
finally:
|
||||
con.close()
|
||||
|
||||
samples = [
|
||||
"Carlton Cold",
|
||||
"Carlton Draft",
|
||||
"Mountain Goat",
|
||||
"Redback",
|
||||
"Victoria Bitter",
|
||||
"XXXX",
|
||||
]
|
||||
|
||||
def _populate(self):
|
||||
"""Return a list of sql commands to setup the DB for the fetch
|
||||
tests.
|
||||
"""
|
||||
populate = [
|
||||
"insert into %sbooze values ('%s')" % (self.table_prefix, s)
|
||||
for s in self.samples
|
||||
]
|
||||
return populate
|
||||
|
||||
def test_fetchmany(self):
|
||||
con = self._connect()
|
||||
try:
|
||||
cur = con.cursor()
|
||||
|
||||
# cursor.fetchmany should raise an Error if called without
|
||||
# issuing a query
|
||||
self.assertRaises(self.driver.Error, cur.fetchmany, 4)
|
||||
|
||||
self.executeDDL1(cur)
|
||||
for sql in self._populate():
|
||||
cur.execute(sql)
|
||||
|
||||
cur.execute("select name from %sbooze" % self.table_prefix)
|
||||
r = cur.fetchmany()
|
||||
self.assertEqual(
|
||||
len(r),
|
||||
1,
|
||||
"cursor.fetchmany retrieved incorrect number of rows, "
|
||||
"default of arraysize is one.",
|
||||
)
|
||||
cur.arraysize = 10
|
||||
r = cur.fetchmany(3) # Should get 3 rows
|
||||
self.assertEqual(
|
||||
len(r), 3, "cursor.fetchmany retrieved incorrect number of rows"
|
||||
)
|
||||
r = cur.fetchmany(4) # Should get 2 more
|
||||
self.assertEqual(
|
||||
len(r), 2, "cursor.fetchmany retrieved incorrect number of rows"
|
||||
)
|
||||
r = cur.fetchmany(4) # Should be an empty sequence
|
||||
self.assertEqual(
|
||||
len(r),
|
||||
0,
|
||||
"cursor.fetchmany should return an empty sequence after "
|
||||
"results are exhausted",
|
||||
)
|
||||
self.assertTrue(cur.rowcount in (-1, 6))
|
||||
|
||||
# Same as above, using cursor.arraysize
|
||||
cur.arraysize = 4
|
||||
cur.execute("select name from %sbooze" % self.table_prefix)
|
||||
r = cur.fetchmany() # Should get 4 rows
|
||||
self.assertEqual(
|
||||
len(r), 4, "cursor.arraysize not being honoured by fetchmany"
|
||||
)
|
||||
r = cur.fetchmany() # Should get 2 more
|
||||
self.assertEqual(len(r), 2)
|
||||
r = cur.fetchmany() # Should be an empty sequence
|
||||
self.assertEqual(len(r), 0)
|
||||
self.assertTrue(cur.rowcount in (-1, 6))
|
||||
|
||||
cur.arraysize = 6
|
||||
cur.execute("select name from %sbooze" % self.table_prefix)
|
||||
rows = cur.fetchmany() # Should get all rows
|
||||
self.assertTrue(cur.rowcount in (-1, 6))
|
||||
self.assertEqual(len(rows), 6)
|
||||
self.assertEqual(len(rows), 6)
|
||||
rows = [r[0] for r in rows]
|
||||
rows.sort()
|
||||
|
||||
# Make sure we get the right data back out
|
||||
for i in range(0, 6):
|
||||
self.assertEqual(
|
||||
rows[i],
|
||||
self.samples[i],
|
||||
"incorrect data retrieved by cursor.fetchmany",
|
||||
)
|
||||
|
||||
rows = cur.fetchmany() # Should return an empty list
|
||||
self.assertEqual(
|
||||
len(rows),
|
||||
0,
|
||||
"cursor.fetchmany should return an empty sequence if "
|
||||
"called after the whole result set has been fetched",
|
||||
)
|
||||
self.assertTrue(cur.rowcount in (-1, 6))
|
||||
|
||||
self.executeDDL2(cur)
|
||||
cur.execute("select name from %sbarflys" % self.table_prefix)
|
||||
r = cur.fetchmany() # Should get empty sequence
|
||||
self.assertEqual(
|
||||
len(r),
|
||||
0,
|
||||
"cursor.fetchmany should return an empty sequence if "
|
||||
"query retrieved no rows",
|
||||
)
|
||||
self.assertTrue(cur.rowcount in (-1, 0))
|
||||
|
||||
finally:
|
||||
con.close()
|
||||
|
||||
def test_fetchall(self):
|
||||
con = self._connect()
|
||||
try:
|
||||
cur = con.cursor()
|
||||
# cursor.fetchall should raise an Error if called
|
||||
# without executing a query that may return rows (such
|
||||
# as a select)
|
||||
self.assertRaises(self.driver.Error, cur.fetchall)
|
||||
|
||||
self.executeDDL1(cur)
|
||||
for sql in self._populate():
|
||||
cur.execute(sql)
|
||||
|
||||
# cursor.fetchall should raise an Error if called
|
||||
# after executing a a statement that cannot return rows
|
||||
self.assertRaises(self.driver.Error, cur.fetchall)
|
||||
|
||||
cur.execute("select name from %sbooze" % self.table_prefix)
|
||||
rows = cur.fetchall()
|
||||
self.assertTrue(cur.rowcount in (-1, len(self.samples)))
|
||||
self.assertEqual(
|
||||
len(rows),
|
||||
len(self.samples),
|
||||
"cursor.fetchall did not retrieve all rows",
|
||||
)
|
||||
rows = [r[0] for r in rows]
|
||||
rows.sort()
|
||||
for i in range(0, len(self.samples)):
|
||||
self.assertEqual(
|
||||
rows[i], self.samples[i], "cursor.fetchall retrieved incorrect rows"
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
self.assertEqual(
|
||||
len(rows),
|
||||
0,
|
||||
"cursor.fetchall should return an empty list if called "
|
||||
"after the whole result set has been fetched",
|
||||
)
|
||||
self.assertTrue(cur.rowcount in (-1, len(self.samples)))
|
||||
|
||||
self.executeDDL2(cur)
|
||||
cur.execute("select name from %sbarflys" % self.table_prefix)
|
||||
rows = cur.fetchall()
|
||||
self.assertTrue(cur.rowcount in (-1, 0))
|
||||
self.assertEqual(
|
||||
len(rows),
|
||||
0,
|
||||
"cursor.fetchall should return an empty list if "
|
||||
"a select query returns no rows",
|
||||
)
|
||||
|
||||
finally:
|
||||
con.close()
|
||||
|
||||
def test_mixedfetch(self):
|
||||
con = self._connect()
|
||||
try:
|
||||
cur = con.cursor()
|
||||
self.executeDDL1(cur)
|
||||
for sql in self._populate():
|
||||
cur.execute(sql)
|
||||
|
||||
cur.execute("select name from %sbooze" % self.table_prefix)
|
||||
rows1 = cur.fetchone()
|
||||
rows23 = cur.fetchmany(2)
|
||||
rows4 = cur.fetchone()
|
||||
rows56 = cur.fetchall()
|
||||
self.assertTrue(cur.rowcount in (-1, 6))
|
||||
self.assertEqual(
|
||||
len(rows23), 2, "fetchmany returned incorrect number of rows"
|
||||
)
|
||||
self.assertEqual(
|
||||
len(rows56), 2, "fetchall returned incorrect number of rows"
|
||||
)
|
||||
|
||||
rows = [rows1[0]]
|
||||
rows.extend([rows23[0][0], rows23[1][0]])
|
||||
rows.append(rows4[0])
|
||||
rows.extend([rows56[0][0], rows56[1][0]])
|
||||
rows.sort()
|
||||
for i in range(0, len(self.samples)):
|
||||
self.assertEqual(
|
||||
rows[i], self.samples[i], "incorrect data retrieved or inserted"
|
||||
)
|
||||
finally:
|
||||
con.close()
|
||||
|
||||
def help_nextset_setUp(self, cur):
|
||||
"""Should create a procedure called deleteme
|
||||
that returns two result sets, first the
|
||||
number of rows in booze then "name from booze"
|
||||
"""
|
||||
raise NotImplementedError("Helper not implemented")
|
||||
# sql="""
|
||||
# create procedure deleteme as
|
||||
# begin
|
||||
# select count(*) from booze
|
||||
# select name from booze
|
||||
# end
|
||||
# """
|
||||
# cur.execute(sql)
|
||||
|
||||
def help_nextset_tearDown(self, cur):
|
||||
"If cleaning up is needed after nextSetTest"
|
||||
raise NotImplementedError("Helper not implemented")
|
||||
# cur.execute("drop procedure deleteme")
|
||||
|
||||
def test_nextset(self):
|
||||
raise NotImplementedError("Drivers need to override this test")
|
||||
|
||||
def test_arraysize(self):
|
||||
# Not much here - rest of the tests for this are in test_fetchmany
|
||||
con = self._connect()
|
||||
try:
|
||||
cur = con.cursor()
|
||||
self.assertTrue(
|
||||
hasattr(cur, "arraysize"), "cursor.arraysize must be defined"
|
||||
)
|
||||
finally:
|
||||
con.close()
|
||||
|
||||
def test_setinputsizes(self):
|
||||
con = self._connect()
|
||||
try:
|
||||
cur = con.cursor()
|
||||
cur.setinputsizes((25,))
|
||||
self._paraminsert(cur) # Make sure cursor still works
|
||||
finally:
|
||||
con.close()
|
||||
|
||||
def test_setoutputsize_basic(self):
|
||||
# Basic test is to make sure setoutputsize doesn't blow up
|
||||
con = self._connect()
|
||||
try:
|
||||
cur = con.cursor()
|
||||
cur.setoutputsize(1000)
|
||||
cur.setoutputsize(2000, 0)
|
||||
self._paraminsert(cur) # Make sure the cursor still works
|
||||
finally:
|
||||
con.close()
|
||||
|
||||
def test_setoutputsize(self):
|
||||
# Real test for setoutputsize is driver dependant
|
||||
raise NotImplementedError("Driver needed to override this test")
|
||||
|
||||
def test_None(self):
|
||||
con = self._connect()
|
||||
try:
|
||||
cur = con.cursor()
|
||||
self.executeDDL1(cur)
|
||||
cur.execute("insert into %sbooze values (NULL)" % self.table_prefix)
|
||||
cur.execute("select name from %sbooze" % self.table_prefix)
|
||||
r = cur.fetchall()
|
||||
self.assertEqual(len(r), 1)
|
||||
self.assertEqual(len(r[0]), 1)
|
||||
self.assertEqual(r[0][0], None, "NULL value not returned as None")
|
||||
finally:
|
||||
con.close()
|
||||
|
||||
def test_Date(self):
|
||||
d1 = self.driver.Date(2002, 12, 25)
|
||||
d2 = self.driver.DateFromTicks(time.mktime((2002, 12, 25, 0, 0, 0, 0, 0, 0)))
|
||||
# Can we assume this? API doesn't specify, but it seems implied
|
||||
# self.assertEqual(str(d1),str(d2))
|
||||
|
||||
def test_Time(self):
|
||||
t1 = self.driver.Time(13, 45, 30)
|
||||
t2 = self.driver.TimeFromTicks(time.mktime((2001, 1, 1, 13, 45, 30, 0, 0, 0)))
|
||||
# Can we assume this? API doesn't specify, but it seems implied
|
||||
# self.assertEqual(str(t1),str(t2))
|
||||
|
||||
def test_Timestamp(self):
|
||||
t1 = self.driver.Timestamp(2002, 12, 25, 13, 45, 30)
|
||||
t2 = self.driver.TimestampFromTicks(
|
||||
time.mktime((2002, 12, 25, 13, 45, 30, 0, 0, 0))
|
||||
)
|
||||
# Can we assume this? API doesn't specify, but it seems implied
|
||||
# self.assertEqual(str(t1),str(t2))
|
||||
|
||||
def test_Binary(self):
|
||||
b = self.driver.Binary(b"Something")
|
||||
b = self.driver.Binary(b"")
|
||||
|
||||
def test_STRING(self):
|
||||
self.assertTrue(hasattr(self.driver, "STRING"), "module.STRING must be defined")
|
||||
|
||||
def test_BINARY(self):
|
||||
self.assertTrue(
|
||||
hasattr(self.driver, "BINARY"), "module.BINARY must be defined."
|
||||
)
|
||||
|
||||
def test_NUMBER(self):
|
||||
self.assertTrue(
|
||||
hasattr(self.driver, "NUMBER"), "module.NUMBER must be defined."
|
||||
)
|
||||
|
||||
def test_DATETIME(self):
|
||||
self.assertTrue(
|
||||
hasattr(self.driver, "DATETIME"), "module.DATETIME must be defined."
|
||||
)
|
||||
|
||||
def test_ROWID(self):
|
||||
self.assertTrue(hasattr(self.driver, "ROWID"), "module.ROWID must be defined.")
|
||||
@@ -0,0 +1,34 @@
|
||||
"""is64bit.Python() --> boolean value of detected Python word size. is64bit.os() --> os build version"""
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
def Python():
|
||||
return sys.maxsize > 2147483647
|
||||
|
||||
|
||||
def os():
|
||||
import platform
|
||||
|
||||
pm = platform.machine()
|
||||
if pm != ".." and pm.endswith("64"): # recent 64 bit Python
|
||||
return True
|
||||
else:
|
||||
import os
|
||||
|
||||
if "PROCESSOR_ARCHITEW6432" in os.environ:
|
||||
return True # 32 bit program running on 64 bit Windows
|
||||
try:
|
||||
return os.environ["PROCESSOR_ARCHITECTURE"].endswith(
|
||||
"64"
|
||||
) # 64 bit Windows 64 bit program
|
||||
except IndexError:
|
||||
pass # not Windows
|
||||
try:
|
||||
return "64" in platform.architecture()[0] # this often works in Linux
|
||||
except:
|
||||
return False # is an older version of Python, assume also an older os (best we can guess)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("is64bit.Python() =", Python(), "is64bit.os() =", os())
|
||||
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/python2
|
||||
# Configure this in order to run the testcases.
|
||||
"setuptestframework.py v 2.6.0.8"
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
|
||||
def maketemp():
|
||||
temphome = tempfile.gettempdir()
|
||||
tempdir = os.path.join(temphome, "adodbapi_test")
|
||||
try:
|
||||
os.mkdir(tempdir)
|
||||
except:
|
||||
pass
|
||||
return tempdir
|
||||
|
||||
|
||||
def _cleanup_function(testfolder, mdb_name):
|
||||
try:
|
||||
os.unlink(os.path.join(testfolder, mdb_name))
|
||||
except:
|
||||
pass # mdb database not present
|
||||
try:
|
||||
shutil.rmtree(testfolder)
|
||||
print(" cleaned up folder", testfolder)
|
||||
except:
|
||||
pass # test package not present
|
||||
|
||||
|
||||
def getcleanupfunction():
|
||||
return _cleanup_function
|
||||
|
||||
|
||||
def find_ado_path():
|
||||
adoName = os.path.normpath(os.getcwd() + "/../../adodbapi.py")
|
||||
adoPackage = os.path.dirname(adoName)
|
||||
return adoPackage
|
||||
|
||||
|
||||
# make a new package directory for the test copy of ado
|
||||
def makeadopackage(testfolder):
|
||||
adoName = os.path.normpath(os.getcwd() + "/../adodbapi.py")
|
||||
adoPath = os.path.dirname(adoName)
|
||||
if os.path.exists(adoName):
|
||||
newpackage = os.path.join(testfolder, "adodbapi")
|
||||
try:
|
||||
os.makedirs(newpackage)
|
||||
except OSError:
|
||||
print(
|
||||
"*Note: temporary adodbapi package already exists: may be two versions running?"
|
||||
)
|
||||
for f in os.listdir(adoPath):
|
||||
if f.endswith(".py"):
|
||||
shutil.copy(os.path.join(adoPath, f), newpackage)
|
||||
return testfolder
|
||||
else:
|
||||
raise OSError("Cannot find source of adodbapi to test.")
|
||||
|
||||
|
||||
def makemdb(testfolder, mdb_name):
|
||||
# following setup code borrowed from pywin32 odbc test suite
|
||||
# kindly contributed by Frank Millman.
|
||||
import os
|
||||
|
||||
_accessdatasource = os.path.join(testfolder, mdb_name)
|
||||
if os.path.isfile(_accessdatasource):
|
||||
print("using JET database=", _accessdatasource)
|
||||
else:
|
||||
from win32com.client import constants
|
||||
from win32com.client.gencache import EnsureDispatch
|
||||
|
||||
# Create a brand-new database - what is the story with these?
|
||||
dbe = None
|
||||
for suffix in (".36", ".35", ".30"):
|
||||
try:
|
||||
dbe = EnsureDispatch("DAO.DBEngine" + suffix)
|
||||
break
|
||||
except:
|
||||
pass
|
||||
if dbe:
|
||||
print(" ...Creating ACCESS db at " + _accessdatasource)
|
||||
workspace = dbe.Workspaces(0)
|
||||
newdb = workspace.CreateDatabase(
|
||||
_accessdatasource, constants.dbLangGeneral, constants.dbVersion40
|
||||
)
|
||||
newdb.Close()
|
||||
else:
|
||||
print(" ...copying test ACCESS db to " + _accessdatasource)
|
||||
mdbName = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), "..", "examples", "test.mdb")
|
||||
)
|
||||
import shutil
|
||||
|
||||
shutil.copy(mdbName, _accessdatasource)
|
||||
|
||||
return _accessdatasource
|
||||
@@ -0,0 +1,195 @@
|
||||
print("This module depends on the dbapi20 compliance tests created by Stuart Bishop")
|
||||
print("(see db-sig mailing list history for info)")
|
||||
import platform
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import dbapi20
|
||||
import setuptestframework
|
||||
|
||||
testfolder = setuptestframework.maketemp()
|
||||
if "--package" in sys.argv:
|
||||
pth = setuptestframework.makeadopackage(testfolder)
|
||||
sys.argv.remove("--package")
|
||||
else:
|
||||
pth = setuptestframework.find_ado_path()
|
||||
if pth not in sys.path:
|
||||
sys.path.insert(1, pth)
|
||||
# function to clean up the temporary folder -- calling program must run this function before exit.
|
||||
cleanup = setuptestframework.getcleanupfunction()
|
||||
|
||||
import adodbapi
|
||||
import adodbapi.is64bit as is64bit
|
||||
|
||||
db = adodbapi
|
||||
|
||||
if "--verbose" in sys.argv:
|
||||
db.adodbapi.verbose = 3
|
||||
|
||||
print(adodbapi.version)
|
||||
print("Tested with dbapi20 %s" % dbapi20.__version__)
|
||||
|
||||
try:
|
||||
onWindows = bool(sys.getwindowsversion()) # seems to work on all versions of Python
|
||||
except:
|
||||
onWindows = False
|
||||
|
||||
node = platform.node()
|
||||
|
||||
conn_kws = {}
|
||||
host = "testsql.2txt.us,1430" # if None, will use macro to fill in node name
|
||||
instance = r"%s\SQLEXPRESS"
|
||||
conn_kws["name"] = "adotest"
|
||||
|
||||
conn_kws["user"] = "adotestuser" # None implies Windows security
|
||||
conn_kws["password"] = "Sq1234567"
|
||||
# macro definition for keyword "security" using macro "auto_security"
|
||||
conn_kws["macro_auto_security"] = "security"
|
||||
|
||||
if host is None:
|
||||
conn_kws["macro_getnode"] = ["host", instance]
|
||||
else:
|
||||
conn_kws["host"] = host
|
||||
|
||||
conn_kws["provider"] = (
|
||||
"Provider=MSOLEDBSQL;DataTypeCompatibility=80;MARS Connection=True;"
|
||||
)
|
||||
connStr = "%(provider)s; %(security)s; Initial Catalog=%(name)s;Data Source=%(host)s"
|
||||
|
||||
if onWindows and node != "z-PC":
|
||||
pass # default should make a local SQL Server connection
|
||||
elif node == "xxx": # try Postgres database
|
||||
_computername = "25.223.161.222"
|
||||
_databasename = "adotest"
|
||||
_username = "adotestuser"
|
||||
_password = "12345678"
|
||||
_driver = "PostgreSQL Unicode"
|
||||
_provider = ""
|
||||
connStr = "%sDriver={%s};Server=%s;Database=%s;uid=%s;pwd=%s;" % (
|
||||
_provider,
|
||||
_driver,
|
||||
_computername,
|
||||
_databasename,
|
||||
_username,
|
||||
_password,
|
||||
)
|
||||
elif node == "yyy": # ACCESS data base is known to fail some tests.
|
||||
if is64bit.Python():
|
||||
driver = "Microsoft.ACE.OLEDB.12.0"
|
||||
else:
|
||||
driver = "Microsoft.Jet.OLEDB.4.0"
|
||||
testmdb = setuptestframework.makemdb(testfolder)
|
||||
connStr = r"Provider=%s;Data Source=%s" % (driver, testmdb)
|
||||
|
||||
print(f"Using Connection String like={connStr}")
|
||||
print(f"Keywords={conn_kws!r}")
|
||||
|
||||
|
||||
class test_adodbapi(dbapi20.DatabaseAPI20Test):
|
||||
driver = db
|
||||
connect_args = (connStr,)
|
||||
connect_kw_args = conn_kws
|
||||
|
||||
def __init__(self, arg):
|
||||
dbapi20.DatabaseAPI20Test.__init__(self, arg)
|
||||
|
||||
def getTestMethodName(self):
|
||||
return self.id().split(".")[-1]
|
||||
|
||||
def setUp(self):
|
||||
# Call superclass setUp In case this does something in the
|
||||
# future
|
||||
dbapi20.DatabaseAPI20Test.setUp(self)
|
||||
if self.getTestMethodName() == "test_callproc":
|
||||
con = self._connect()
|
||||
engine = con.dbms_name
|
||||
# print(f"Using database Engine={engine}")
|
||||
if engine != "MS Jet":
|
||||
sql = """
|
||||
create procedure templower
|
||||
@theData varchar(50)
|
||||
as
|
||||
select lower(@theData)
|
||||
"""
|
||||
else: # Jet
|
||||
sql = """
|
||||
create procedure templower
|
||||
(theData varchar(50))
|
||||
as
|
||||
select lower(theData);
|
||||
"""
|
||||
cur = con.cursor()
|
||||
try:
|
||||
cur.execute(sql)
|
||||
con.commit()
|
||||
except:
|
||||
pass
|
||||
cur.close()
|
||||
con.close()
|
||||
self.lower_func = "templower"
|
||||
|
||||
def tearDown(self):
|
||||
if self.getTestMethodName() == "test_callproc":
|
||||
con = self._connect()
|
||||
cur = con.cursor()
|
||||
try:
|
||||
cur.execute("drop procedure templower")
|
||||
except:
|
||||
pass
|
||||
con.commit()
|
||||
dbapi20.DatabaseAPI20Test.tearDown(self)
|
||||
|
||||
def help_nextset_setUp(self, cur):
|
||||
"Should create a procedure called deleteme"
|
||||
'that returns two result sets, first the number of rows in booze then "name from booze"'
|
||||
sql = """
|
||||
create procedure deleteme as
|
||||
begin
|
||||
select count(*) from %sbooze
|
||||
select name from %sbooze
|
||||
end
|
||||
""" % (
|
||||
self.table_prefix,
|
||||
self.table_prefix,
|
||||
)
|
||||
cur.execute(sql)
|
||||
|
||||
def help_nextset_tearDown(self, cur):
|
||||
"If cleaning up is needed after nextSetTest"
|
||||
try:
|
||||
cur.execute("drop procedure deleteme")
|
||||
except:
|
||||
pass
|
||||
|
||||
def test_nextset(self):
|
||||
con = self._connect()
|
||||
try:
|
||||
cur = con.cursor()
|
||||
|
||||
stmts = [self.ddl1] + self._populate()
|
||||
for sql in stmts:
|
||||
cur.execute(sql)
|
||||
|
||||
self.help_nextset_setUp(cur)
|
||||
|
||||
cur.callproc("deleteme")
|
||||
numberofrows = cur.fetchone()
|
||||
assert numberofrows[0] == 6
|
||||
assert cur.nextset()
|
||||
names = cur.fetchall()
|
||||
assert len(names) == len(self.samples)
|
||||
s = cur.nextset()
|
||||
assert s is None, "No more return sets, should return None"
|
||||
finally:
|
||||
try:
|
||||
self.help_nextset_tearDown(cur)
|
||||
finally:
|
||||
con.close()
|
||||
|
||||
def test_setoutputsize(self):
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
cleanup(testfolder, None)
|
||||
@@ -0,0 +1,30 @@
|
||||
def try_connection(verbose, *args, **kwargs):
|
||||
import adodbapi
|
||||
|
||||
dbconnect = adodbapi.connect
|
||||
try:
|
||||
s = dbconnect(*args, **kwargs) # connect to server
|
||||
if verbose:
|
||||
print("Connected to:", s.connection_string)
|
||||
print("which has tables:", s.get_table_names())
|
||||
s.close() # thanks, it worked, goodbye
|
||||
except adodbapi.DatabaseError as inst:
|
||||
print(inst.args[0]) # should be the error message
|
||||
print(f"***Failed getting connection using= {args!r} {kwargs!r}")
|
||||
return False, (args, kwargs), None
|
||||
|
||||
print(" (successful)")
|
||||
|
||||
return True, (args, kwargs), dbconnect
|
||||
|
||||
|
||||
def try_operation_with_expected_exception(
|
||||
expected_exception_list, some_function, *args, **kwargs
|
||||
):
|
||||
try:
|
||||
some_function(*args, **kwargs)
|
||||
except expected_exception_list as e:
|
||||
return True, e
|
||||
except:
|
||||
raise # an exception other than the expected occurred
|
||||
return False, "The expected exception did not occur"
|
||||
@@ -0,0 +1 @@
|
||||
pip
|
||||
@@ -0,0 +1,145 @@
|
||||
Metadata-Version: 2.4
|
||||
Name: annotated-doc
|
||||
Version: 0.0.4
|
||||
Summary: Document parameters, class attributes, return types, and variables inline, with Annotated.
|
||||
Author-Email: =?utf-8?q?Sebasti=C3=A1n_Ram=C3=ADrez?= <tiangolo@gmail.com>
|
||||
License-Expression: MIT
|
||||
License-File: LICENSE
|
||||
Classifier: Intended Audience :: Information Technology
|
||||
Classifier: Intended Audience :: System Administrators
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Topic :: Internet
|
||||
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||
Classifier: Topic :: Software Development :: Libraries
|
||||
Classifier: Topic :: Software Development
|
||||
Classifier: Typing :: Typed
|
||||
Classifier: Development Status :: 4 - Beta
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Programming Language :: Python :: 3 :: Only
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Programming Language :: Python :: 3.10
|
||||
Classifier: Programming Language :: Python :: 3.11
|
||||
Classifier: Programming Language :: Python :: 3.12
|
||||
Classifier: Programming Language :: Python :: 3.13
|
||||
Classifier: Programming Language :: Python :: 3.14
|
||||
Project-URL: Homepage, https://github.com/fastapi/annotated-doc
|
||||
Project-URL: Documentation, https://github.com/fastapi/annotated-doc
|
||||
Project-URL: Repository, https://github.com/fastapi/annotated-doc
|
||||
Project-URL: Issues, https://github.com/fastapi/annotated-doc/issues
|
||||
Project-URL: Changelog, https://github.com/fastapi/annotated-doc/release-notes.md
|
||||
Requires-Python: >=3.8
|
||||
Description-Content-Type: text/markdown
|
||||
|
||||
# Annotated Doc
|
||||
|
||||
Document parameters, class attributes, return types, and variables inline, with `Annotated`.
|
||||
|
||||
<a href="https://github.com/fastapi/annotated-doc/actions?query=workflow%3ATest+event%3Apush+branch%3Amain" target="_blank">
|
||||
<img src="https://github.com/fastapi/annotated-doc/actions/workflows/test.yml/badge.svg?event=push&branch=main" alt="Test">
|
||||
</a>
|
||||
<a href="https://coverage-badge.samuelcolvin.workers.dev/redirect/fastapi/annotated-doc" target="_blank">
|
||||
<img src="https://coverage-badge.samuelcolvin.workers.dev/fastapi/annotated-doc.svg" alt="Coverage">
|
||||
</a>
|
||||
<a href="https://pypi.org/project/annotated-doc" target="_blank">
|
||||
<img src="https://img.shields.io/pypi/v/annotated-doc?color=%2334D058&label=pypi%20package" alt="Package version">
|
||||
</a>
|
||||
<a href="https://pypi.org/project/annotated-doc" target="_blank">
|
||||
<img src="https://img.shields.io/pypi/pyversions/annotated-doc.svg?color=%2334D058" alt="Supported Python versions">
|
||||
</a>
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install annotated-doc
|
||||
```
|
||||
|
||||
Or with `uv`:
|
||||
|
||||
```Python
|
||||
uv add annotated-doc
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Import `Doc` and pass a single literal string with the documentation for the specific parameter, class attribute, return type, or variable.
|
||||
|
||||
For example, to document a parameter `name` in a function `hi` you could do:
|
||||
|
||||
```Python
|
||||
from typing import Annotated
|
||||
|
||||
from annotated_doc import Doc
|
||||
|
||||
def hi(name: Annotated[str, Doc("Who to say hi to")]) -> None:
|
||||
print(f"Hi, {name}!")
|
||||
```
|
||||
|
||||
You can also use it to document class attributes:
|
||||
|
||||
```Python
|
||||
from typing import Annotated
|
||||
|
||||
from annotated_doc import Doc
|
||||
|
||||
class User:
|
||||
name: Annotated[str, Doc("The user's name")]
|
||||
age: Annotated[int, Doc("The user's age")]
|
||||
```
|
||||
|
||||
The same way, you could document return types and variables, or anything that could have a type annotation with `Annotated`.
|
||||
|
||||
## Who Uses This
|
||||
|
||||
`annotated-doc` was made for:
|
||||
|
||||
* [FastAPI](https://fastapi.tiangolo.com/)
|
||||
* [Typer](https://typer.tiangolo.com/)
|
||||
* [SQLModel](https://sqlmodel.tiangolo.com/)
|
||||
* [Asyncer](https://asyncer.tiangolo.com/)
|
||||
|
||||
`annotated-doc` is supported by [griffe-typingdoc](https://github.com/mkdocstrings/griffe-typingdoc), which powers reference documentation like the one in the [FastAPI Reference](https://fastapi.tiangolo.com/reference/).
|
||||
|
||||
## Reasons not to use `annotated-doc`
|
||||
|
||||
You are already comfortable with one of the existing docstring formats, like:
|
||||
|
||||
* Sphinx
|
||||
* numpydoc
|
||||
* Google
|
||||
* Keras
|
||||
|
||||
Your team is already comfortable using them.
|
||||
|
||||
You prefer having the documentation about parameters all together in a docstring, separated from the code defining them.
|
||||
|
||||
You care about a specific set of users, using one specific editor, and that editor already has support for the specific docstring format you use.
|
||||
|
||||
## Reasons to use `annotated-doc`
|
||||
|
||||
* No micro-syntax to learn for newcomers, it’s **just Python** syntax.
|
||||
* **Editing** would be already fully supported by default by any editor (current or future) supporting Python syntax, including syntax errors, syntax highlighting, etc.
|
||||
* **Rendering** would be relatively straightforward to implement by static tools (tools that don't need runtime execution), as the information can be extracted from the AST they normally already create.
|
||||
* **Deduplication of information**: the name of a parameter would be defined in a single place, not duplicated inside of a docstring.
|
||||
* **Elimination** of the possibility of having **inconsistencies** when removing a parameter or class variable and **forgetting to remove** its documentation.
|
||||
* **Minimization** of the probability of adding a new parameter or class variable and **forgetting to add its documentation**.
|
||||
* **Elimination** of the possibility of having **inconsistencies** between the **name** of a parameter in the **signature** and the name in the docstring when it is renamed.
|
||||
* **Access** to the documentation string for each symbol at **runtime**, including existing (older) Python versions.
|
||||
* A more formalized way to document other symbols, like type aliases, that could use Annotated.
|
||||
* **Support** for apps using FastAPI, Typer and others.
|
||||
* **AI Accessibility**: AI tools will have an easier way understanding each parameter as the distance from documentation to parameter is much closer.
|
||||
|
||||
## History
|
||||
|
||||
I ([@tiangolo](https://github.com/tiangolo)) originally wanted for this to be part of the Python standard library (in [PEP 727](https://peps.python.org/pep-0727/)), but the proposal was withdrawn as there was a fair amount of negative feedback and opposition.
|
||||
|
||||
The conclusion was that this was better done as an external effort, in a third-party library.
|
||||
|
||||
So, here it is, with a simpler approach, as a third-party library, in a way that can be used by others, starting with FastAPI and friends.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the terms of the MIT license.
|
||||
@@ -0,0 +1,11 @@
|
||||
annotated_doc-0.0.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
annotated_doc-0.0.4.dist-info/METADATA,sha256=Irm5KJua33dY2qKKAjJ-OhKaVBVIfwFGej_dSe3Z1TU,6566
|
||||
annotated_doc-0.0.4.dist-info/RECORD,,
|
||||
annotated_doc-0.0.4.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
|
||||
annotated_doc-0.0.4.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
|
||||
annotated_doc-0.0.4.dist-info/licenses/LICENSE,sha256=__Fwd5pqy_ZavbQFwIfxzuF4ZpHkqWpANFF-SlBKDN8,1086
|
||||
annotated_doc/__init__.py,sha256=VuyxxUe80kfEyWnOrCx_Bk8hybo3aKo6RYBlkBBYW8k,52
|
||||
annotated_doc/__pycache__/__init__.cpython-312.pyc,,
|
||||
annotated_doc/__pycache__/main.cpython-312.pyc,,
|
||||
annotated_doc/main.py,sha256=5Zfvxv80SwwLqpRW73AZyZyiM4bWma9QWRbp_cgD20s,1075
|
||||
annotated_doc/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
@@ -0,0 +1,4 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: pdm-backend (2.4.5)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
@@ -0,0 +1,4 @@
|
||||
[console_scripts]
|
||||
|
||||
[gui_scripts]
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2025 Sebastián Ramírez
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -0,0 +1,3 @@
|
||||
from .main import Doc as Doc
|
||||
|
||||
__version__ = "0.0.4"
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,36 @@
|
||||
class Doc:
|
||||
"""Define the documentation of a type annotation using `Annotated`, to be
|
||||
used in class attributes, function and method parameters, return values,
|
||||
and variables.
|
||||
|
||||
The value should be a positional-only string literal to allow static tools
|
||||
like editors and documentation generators to use it.
|
||||
|
||||
This complements docstrings.
|
||||
|
||||
The string value passed is available in the attribute `documentation`.
|
||||
|
||||
Example:
|
||||
|
||||
```Python
|
||||
from typing import Annotated
|
||||
from annotated_doc import Doc
|
||||
|
||||
def hi(name: Annotated[str, Doc("Who to say hi to")]) -> None:
|
||||
print(f"Hi, {name}!")
|
||||
```
|
||||
"""
|
||||
|
||||
def __init__(self, documentation: str, /) -> None:
|
||||
self.documentation = documentation
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Doc({self.documentation!r})"
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.documentation)
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, Doc):
|
||||
return NotImplemented
|
||||
return self.documentation == other.documentation
|
||||
@@ -0,0 +1 @@
|
||||
pip
|
||||
@@ -0,0 +1,295 @@
|
||||
Metadata-Version: 2.3
|
||||
Name: annotated-types
|
||||
Version: 0.7.0
|
||||
Summary: Reusable constraint types to use with typing.Annotated
|
||||
Project-URL: Homepage, https://github.com/annotated-types/annotated-types
|
||||
Project-URL: Source, https://github.com/annotated-types/annotated-types
|
||||
Project-URL: Changelog, https://github.com/annotated-types/annotated-types/releases
|
||||
Author-email: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com>, Samuel Colvin <s@muelcolvin.com>, Zac Hatfield-Dodds <zac@zhd.dev>
|
||||
License-File: LICENSE
|
||||
Classifier: Development Status :: 4 - Beta
|
||||
Classifier: Environment :: Console
|
||||
Classifier: Environment :: MacOS X
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Intended Audience :: Information Technology
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Operating System :: POSIX :: Linux
|
||||
Classifier: Operating System :: Unix
|
||||
Classifier: Programming Language :: Python :: 3 :: Only
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Programming Language :: Python :: 3.10
|
||||
Classifier: Programming Language :: Python :: 3.11
|
||||
Classifier: Programming Language :: Python :: 3.12
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||
Classifier: Typing :: Typed
|
||||
Requires-Python: >=3.8
|
||||
Requires-Dist: typing-extensions>=4.0.0; python_version < '3.9'
|
||||
Description-Content-Type: text/markdown
|
||||
|
||||
# annotated-types
|
||||
|
||||
[](https://github.com/annotated-types/annotated-types/actions?query=event%3Apush+branch%3Amain+workflow%3ACI)
|
||||
[](https://pypi.python.org/pypi/annotated-types)
|
||||
[](https://github.com/annotated-types/annotated-types)
|
||||
[](https://github.com/annotated-types/annotated-types/blob/main/LICENSE)
|
||||
|
||||
[PEP-593](https://peps.python.org/pep-0593/) added `typing.Annotated` as a way of
|
||||
adding context-specific metadata to existing types, and specifies that
|
||||
`Annotated[T, x]` _should_ be treated as `T` by any tool or library without special
|
||||
logic for `x`.
|
||||
|
||||
This package provides metadata objects which can be used to represent common
|
||||
constraints such as upper and lower bounds on scalar values and collection sizes,
|
||||
a `Predicate` marker for runtime checks, and
|
||||
descriptions of how we intend these metadata to be interpreted. In some cases,
|
||||
we also note alternative representations which do not require this package.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
pip install annotated-types
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
from annotated_types import Gt, Len, Predicate
|
||||
|
||||
class MyClass:
|
||||
age: Annotated[int, Gt(18)] # Valid: 19, 20, ...
|
||||
# Invalid: 17, 18, "19", 19.0, ...
|
||||
factors: list[Annotated[int, Predicate(is_prime)]] # Valid: 2, 3, 5, 7, 11, ...
|
||||
# Invalid: 4, 8, -2, 5.0, "prime", ...
|
||||
|
||||
my_list: Annotated[list[int], Len(0, 10)] # Valid: [], [10, 20, 30, 40, 50]
|
||||
# Invalid: (1, 2), ["abc"], [0] * 20
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
_While `annotated-types` avoids runtime checks for performance, users should not
|
||||
construct invalid combinations such as `MultipleOf("non-numeric")` or `Annotated[int, Len(3)]`.
|
||||
Downstream implementors may choose to raise an error, emit a warning, silently ignore
|
||||
a metadata item, etc., if the metadata objects described below are used with an
|
||||
incompatible type - or for any other reason!_
|
||||
|
||||
### Gt, Ge, Lt, Le
|
||||
|
||||
Express inclusive and/or exclusive bounds on orderable values - which may be numbers,
|
||||
dates, times, strings, sets, etc. Note that the boundary value need not be of the
|
||||
same type that was annotated, so long as they can be compared: `Annotated[int, Gt(1.5)]`
|
||||
is fine, for example, and implies that the value is an integer x such that `x > 1.5`.
|
||||
|
||||
We suggest that implementors may also interpret `functools.partial(operator.le, 1.5)`
|
||||
as being equivalent to `Gt(1.5)`, for users who wish to avoid a runtime dependency on
|
||||
the `annotated-types` package.
|
||||
|
||||
To be explicit, these types have the following meanings:
|
||||
|
||||
* `Gt(x)` - value must be "Greater Than" `x` - equivalent to exclusive minimum
|
||||
* `Ge(x)` - value must be "Greater than or Equal" to `x` - equivalent to inclusive minimum
|
||||
* `Lt(x)` - value must be "Less Than" `x` - equivalent to exclusive maximum
|
||||
* `Le(x)` - value must be "Less than or Equal" to `x` - equivalent to inclusive maximum
|
||||
|
||||
### Interval
|
||||
|
||||
`Interval(gt, ge, lt, le)` allows you to specify an upper and lower bound with a single
|
||||
metadata object. `None` attributes should be ignored, and non-`None` attributes
|
||||
treated as per the single bounds above.
|
||||
|
||||
### MultipleOf
|
||||
|
||||
`MultipleOf(multiple_of=x)` might be interpreted in two ways:
|
||||
|
||||
1. Python semantics, implying `value % multiple_of == 0`, or
|
||||
2. [JSONschema semantics](https://json-schema.org/draft/2020-12/json-schema-validation.html#rfc.section.6.2.1),
|
||||
where `int(value / multiple_of) == value / multiple_of`.
|
||||
|
||||
We encourage users to be aware of these two common interpretations and their
|
||||
distinct behaviours, especially since very large or non-integer numbers make
|
||||
it easy to cause silent data corruption due to floating-point imprecision.
|
||||
|
||||
We encourage libraries to carefully document which interpretation they implement.
|
||||
|
||||
### MinLen, MaxLen, Len
|
||||
|
||||
`Len()` implies that `min_length <= len(value) <= max_length` - lower and upper bounds are inclusive.
|
||||
|
||||
As well as `Len()` which can optionally include upper and lower bounds, we also
|
||||
provide `MinLen(x)` and `MaxLen(y)` which are equivalent to `Len(min_length=x)`
|
||||
and `Len(max_length=y)` respectively.
|
||||
|
||||
`Len`, `MinLen`, and `MaxLen` may be used with any type which supports `len(value)`.
|
||||
|
||||
Examples of usage:
|
||||
|
||||
* `Annotated[list, MaxLen(10)]` (or `Annotated[list, Len(max_length=10))`) - list must have a length of 10 or less
|
||||
* `Annotated[str, MaxLen(10)]` - string must have a length of 10 or less
|
||||
* `Annotated[list, MinLen(3))` (or `Annotated[list, Len(min_length=3))`) - list must have a length of 3 or more
|
||||
* `Annotated[list, Len(4, 6)]` - list must have a length of 4, 5, or 6
|
||||
* `Annotated[list, Len(8, 8)]` - list must have a length of exactly 8
|
||||
|
||||
#### Changed in v0.4.0
|
||||
|
||||
* `min_inclusive` has been renamed to `min_length`, no change in meaning
|
||||
* `max_exclusive` has been renamed to `max_length`, upper bound is now **inclusive** instead of **exclusive**
|
||||
* The recommendation that slices are interpreted as `Len` has been removed due to ambiguity and different semantic
|
||||
meaning of the upper bound in slices vs. `Len`
|
||||
|
||||
See [issue #23](https://github.com/annotated-types/annotated-types/issues/23) for discussion.
|
||||
|
||||
### Timezone
|
||||
|
||||
`Timezone` can be used with a `datetime` or a `time` to express which timezones
|
||||
are allowed. `Annotated[datetime, Timezone(None)]` must be a naive datetime.
|
||||
`Timezone[...]` ([literal ellipsis](https://docs.python.org/3/library/constants.html#Ellipsis))
|
||||
expresses that any timezone-aware datetime is allowed. You may also pass a specific
|
||||
timezone string or [`tzinfo`](https://docs.python.org/3/library/datetime.html#tzinfo-objects)
|
||||
object such as `Timezone(timezone.utc)` or `Timezone("Africa/Abidjan")` to express that you only
|
||||
allow a specific timezone, though we note that this is often a symptom of fragile design.
|
||||
|
||||
#### Changed in v0.x.x
|
||||
|
||||
* `Timezone` accepts [`tzinfo`](https://docs.python.org/3/library/datetime.html#tzinfo-objects) objects instead of
|
||||
`timezone`, extending compatibility to [`zoneinfo`](https://docs.python.org/3/library/zoneinfo.html) and third party libraries.
|
||||
|
||||
### Unit
|
||||
|
||||
`Unit(unit: str)` expresses that the annotated numeric value is the magnitude of
|
||||
a quantity with the specified unit. For example, `Annotated[float, Unit("m/s")]`
|
||||
would be a float representing a velocity in meters per second.
|
||||
|
||||
Please note that `annotated_types` itself makes no attempt to parse or validate
|
||||
the unit string in any way. That is left entirely to downstream libraries,
|
||||
such as [`pint`](https://pint.readthedocs.io) or
|
||||
[`astropy.units`](https://docs.astropy.org/en/stable/units/).
|
||||
|
||||
An example of how a library might use this metadata:
|
||||
|
||||
```python
|
||||
from annotated_types import Unit
|
||||
from typing import Annotated, TypeVar, Callable, Any, get_origin, get_args
|
||||
|
||||
# given a type annotated with a unit:
|
||||
Meters = Annotated[float, Unit("m")]
|
||||
|
||||
|
||||
# you can cast the annotation to a specific unit type with any
|
||||
# callable that accepts a string and returns the desired type
|
||||
T = TypeVar("T")
|
||||
def cast_unit(tp: Any, unit_cls: Callable[[str], T]) -> T | None:
|
||||
if get_origin(tp) is Annotated:
|
||||
for arg in get_args(tp):
|
||||
if isinstance(arg, Unit):
|
||||
return unit_cls(arg.unit)
|
||||
return None
|
||||
|
||||
|
||||
# using `pint`
|
||||
import pint
|
||||
pint_unit = cast_unit(Meters, pint.Unit)
|
||||
|
||||
|
||||
# using `astropy.units`
|
||||
import astropy.units as u
|
||||
astropy_unit = cast_unit(Meters, u.Unit)
|
||||
```
|
||||
|
||||
### Predicate
|
||||
|
||||
`Predicate(func: Callable)` expresses that `func(value)` is truthy for valid values.
|
||||
Users should prefer the statically inspectable metadata above, but if you need
|
||||
the full power and flexibility of arbitrary runtime predicates... here it is.
|
||||
|
||||
For some common constraints, we provide generic types:
|
||||
|
||||
* `IsLower = Annotated[T, Predicate(str.islower)]`
|
||||
* `IsUpper = Annotated[T, Predicate(str.isupper)]`
|
||||
* `IsDigit = Annotated[T, Predicate(str.isdigit)]`
|
||||
* `IsFinite = Annotated[T, Predicate(math.isfinite)]`
|
||||
* `IsNotFinite = Annotated[T, Predicate(Not(math.isfinite))]`
|
||||
* `IsNan = Annotated[T, Predicate(math.isnan)]`
|
||||
* `IsNotNan = Annotated[T, Predicate(Not(math.isnan))]`
|
||||
* `IsInfinite = Annotated[T, Predicate(math.isinf)]`
|
||||
* `IsNotInfinite = Annotated[T, Predicate(Not(math.isinf))]`
|
||||
|
||||
so that you can write e.g. `x: IsFinite[float] = 2.0` instead of the longer
|
||||
(but exactly equivalent) `x: Annotated[float, Predicate(math.isfinite)] = 2.0`.
|
||||
|
||||
Some libraries might have special logic to handle known or understandable predicates,
|
||||
for example by checking for `str.isdigit` and using its presence to both call custom
|
||||
logic to enforce digit-only strings, and customise some generated external schema.
|
||||
Users are therefore encouraged to avoid indirection like `lambda s: s.lower()`, in
|
||||
favor of introspectable methods such as `str.lower` or `re.compile("pattern").search`.
|
||||
|
||||
To enable basic negation of commonly used predicates like `math.isnan` without introducing introspection that makes it impossible for implementers to introspect the predicate we provide a `Not` wrapper that simply negates the predicate in an introspectable manner. Several of the predicates listed above are created in this manner.
|
||||
|
||||
We do not specify what behaviour should be expected for predicates that raise
|
||||
an exception. For example `Annotated[int, Predicate(str.isdigit)]` might silently
|
||||
skip invalid constraints, or statically raise an error; or it might try calling it
|
||||
and then propagate or discard the resulting
|
||||
`TypeError: descriptor 'isdigit' for 'str' objects doesn't apply to a 'int' object`
|
||||
exception. We encourage libraries to document the behaviour they choose.
|
||||
|
||||
### Doc
|
||||
|
||||
`doc()` can be used to add documentation information in `Annotated`, for function and method parameters, variables, class attributes, return types, and any place where `Annotated` can be used.
|
||||
|
||||
It expects a value that can be statically analyzed, as the main use case is for static analysis, editors, documentation generators, and similar tools.
|
||||
|
||||
It returns a `DocInfo` class with a single attribute `documentation` containing the value passed to `doc()`.
|
||||
|
||||
This is the early adopter's alternative form of the [`typing-doc` proposal](https://github.com/tiangolo/fastapi/blob/typing-doc/typing_doc.md).
|
||||
|
||||
### Integrating downstream types with `GroupedMetadata`
|
||||
|
||||
Implementers may choose to provide a convenience wrapper that groups multiple pieces of metadata.
|
||||
This can help reduce verbosity and cognitive overhead for users.
|
||||
For example, an implementer like Pydantic might provide a `Field` or `Meta` type that accepts keyword arguments and transforms these into low-level metadata:
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass
|
||||
from typing import Iterator
|
||||
from annotated_types import GroupedMetadata, Ge
|
||||
|
||||
@dataclass
|
||||
class Field(GroupedMetadata):
|
||||
ge: int | None = None
|
||||
description: str | None = None
|
||||
|
||||
def __iter__(self) -> Iterator[object]:
|
||||
# Iterating over a GroupedMetadata object should yield annotated-types
|
||||
# constraint metadata objects which describe it as fully as possible,
|
||||
# and may include other unknown objects too.
|
||||
if self.ge is not None:
|
||||
yield Ge(self.ge)
|
||||
if self.description is not None:
|
||||
yield Description(self.description)
|
||||
```
|
||||
|
||||
Libraries consuming annotated-types constraints should check for `GroupedMetadata` and unpack it by iterating over the object and treating the results as if they had been "unpacked" in the `Annotated` type. The same logic should be applied to the [PEP 646 `Unpack` type](https://peps.python.org/pep-0646/), so that `Annotated[T, Field(...)]`, `Annotated[T, Unpack[Field(...)]]` and `Annotated[T, *Field(...)]` are all treated consistently.
|
||||
|
||||
Libraries consuming annotated-types should also ignore any metadata they do not recongize that came from unpacking a `GroupedMetadata`, just like they ignore unrecognized metadata in `Annotated` itself.
|
||||
|
||||
Our own `annotated_types.Interval` class is a `GroupedMetadata` which unpacks itself into `Gt`, `Lt`, etc., so this is not an abstract concern. Similarly, `annotated_types.Len` is a `GroupedMetadata` which unpacks itself into `MinLen` (optionally) and `MaxLen`.
|
||||
|
||||
### Consuming metadata
|
||||
|
||||
We intend to not be prescriptive as to _how_ the metadata and constraints are used, but as an example of how one might parse constraints from types annotations see our [implementation in `test_main.py`](https://github.com/annotated-types/annotated-types/blob/f59cf6d1b5255a0fe359b93896759a180bec30ae/tests/test_main.py#L94-L103).
|
||||
|
||||
It is up to the implementer to determine how this metadata is used.
|
||||
You could use the metadata for runtime type checking, for generating schemas or to generate example data, amongst other use cases.
|
||||
|
||||
## Design & History
|
||||
|
||||
This package was designed at the PyCon 2022 sprints by the maintainers of Pydantic
|
||||
and Hypothesis, with the goal of making it as easy as possible for end-users to
|
||||
provide more informative annotations for use by runtime libraries.
|
||||
|
||||
It is deliberately minimal, and following PEP-593 allows considerable downstream
|
||||
discretion in what (if anything!) they choose to support. Nonetheless, we expect
|
||||
that staying simple and covering _only_ the most common use-cases will give users
|
||||
and maintainers the best experience we can. If you'd like more constraints for your
|
||||
types - follow our lead, by defining them and documenting them downstream!
|
||||
@@ -0,0 +1,10 @@
|
||||
annotated_types-0.7.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
annotated_types-0.7.0.dist-info/METADATA,sha256=7ltqxksJJ0wCYFGBNIQCWTlWQGeAH0hRFdnK3CB895E,15046
|
||||
annotated_types-0.7.0.dist-info/RECORD,,
|
||||
annotated_types-0.7.0.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
|
||||
annotated_types-0.7.0.dist-info/licenses/LICENSE,sha256=_hBJiEsaDZNCkB6I4H8ykl0ksxIdmXK2poBfuYJLCV0,1083
|
||||
annotated_types/__init__.py,sha256=RynLsRKUEGI0KimXydlD1fZEfEzWwDo0Uon3zOKhG1Q,13819
|
||||
annotated_types/__pycache__/__init__.cpython-312.pyc,,
|
||||
annotated_types/__pycache__/test_cases.cpython-312.pyc,,
|
||||
annotated_types/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
annotated_types/test_cases.py,sha256=zHFX6EpcMbGJ8FzBYDbO56bPwx_DYIVSKbZM-4B3_lg,6421
|
||||
@@ -0,0 +1,4 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: hatchling 1.24.2
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2022 the contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,432 @@
|
||||
import math
|
||||
import sys
|
||||
import types
|
||||
from dataclasses import dataclass
|
||||
from datetime import tzinfo
|
||||
from typing import TYPE_CHECKING, Any, Callable, Iterator, Optional, SupportsFloat, SupportsIndex, TypeVar, Union
|
||||
|
||||
if sys.version_info < (3, 8):
|
||||
from typing_extensions import Protocol, runtime_checkable
|
||||
else:
|
||||
from typing import Protocol, runtime_checkable
|
||||
|
||||
if sys.version_info < (3, 9):
|
||||
from typing_extensions import Annotated, Literal
|
||||
else:
|
||||
from typing import Annotated, Literal
|
||||
|
||||
if sys.version_info < (3, 10):
|
||||
EllipsisType = type(Ellipsis)
|
||||
KW_ONLY = {}
|
||||
SLOTS = {}
|
||||
else:
|
||||
from types import EllipsisType
|
||||
|
||||
KW_ONLY = {"kw_only": True}
|
||||
SLOTS = {"slots": True}
|
||||
|
||||
|
||||
__all__ = (
|
||||
'BaseMetadata',
|
||||
'GroupedMetadata',
|
||||
'Gt',
|
||||
'Ge',
|
||||
'Lt',
|
||||
'Le',
|
||||
'Interval',
|
||||
'MultipleOf',
|
||||
'MinLen',
|
||||
'MaxLen',
|
||||
'Len',
|
||||
'Timezone',
|
||||
'Predicate',
|
||||
'LowerCase',
|
||||
'UpperCase',
|
||||
'IsDigits',
|
||||
'IsFinite',
|
||||
'IsNotFinite',
|
||||
'IsNan',
|
||||
'IsNotNan',
|
||||
'IsInfinite',
|
||||
'IsNotInfinite',
|
||||
'doc',
|
||||
'DocInfo',
|
||||
'__version__',
|
||||
)
|
||||
|
||||
__version__ = '0.7.0'
|
||||
|
||||
|
||||
T = TypeVar('T')
|
||||
|
||||
|
||||
# arguments that start with __ are considered
|
||||
# positional only
|
||||
# see https://peps.python.org/pep-0484/#positional-only-arguments
|
||||
|
||||
|
||||
class SupportsGt(Protocol):
|
||||
def __gt__(self: T, __other: T) -> bool:
|
||||
...
|
||||
|
||||
|
||||
class SupportsGe(Protocol):
|
||||
def __ge__(self: T, __other: T) -> bool:
|
||||
...
|
||||
|
||||
|
||||
class SupportsLt(Protocol):
|
||||
def __lt__(self: T, __other: T) -> bool:
|
||||
...
|
||||
|
||||
|
||||
class SupportsLe(Protocol):
|
||||
def __le__(self: T, __other: T) -> bool:
|
||||
...
|
||||
|
||||
|
||||
class SupportsMod(Protocol):
|
||||
def __mod__(self: T, __other: T) -> T:
|
||||
...
|
||||
|
||||
|
||||
class SupportsDiv(Protocol):
|
||||
def __div__(self: T, __other: T) -> T:
|
||||
...
|
||||
|
||||
|
||||
class BaseMetadata:
|
||||
"""Base class for all metadata.
|
||||
|
||||
This exists mainly so that implementers
|
||||
can do `isinstance(..., BaseMetadata)` while traversing field annotations.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
@dataclass(frozen=True, **SLOTS)
|
||||
class Gt(BaseMetadata):
|
||||
"""Gt(gt=x) implies that the value must be greater than x.
|
||||
|
||||
It can be used with any type that supports the ``>`` operator,
|
||||
including numbers, dates and times, strings, sets, and so on.
|
||||
"""
|
||||
|
||||
gt: SupportsGt
|
||||
|
||||
|
||||
@dataclass(frozen=True, **SLOTS)
|
||||
class Ge(BaseMetadata):
|
||||
"""Ge(ge=x) implies that the value must be greater than or equal to x.
|
||||
|
||||
It can be used with any type that supports the ``>=`` operator,
|
||||
including numbers, dates and times, strings, sets, and so on.
|
||||
"""
|
||||
|
||||
ge: SupportsGe
|
||||
|
||||
|
||||
@dataclass(frozen=True, **SLOTS)
|
||||
class Lt(BaseMetadata):
|
||||
"""Lt(lt=x) implies that the value must be less than x.
|
||||
|
||||
It can be used with any type that supports the ``<`` operator,
|
||||
including numbers, dates and times, strings, sets, and so on.
|
||||
"""
|
||||
|
||||
lt: SupportsLt
|
||||
|
||||
|
||||
@dataclass(frozen=True, **SLOTS)
|
||||
class Le(BaseMetadata):
|
||||
"""Le(le=x) implies that the value must be less than or equal to x.
|
||||
|
||||
It can be used with any type that supports the ``<=`` operator,
|
||||
including numbers, dates and times, strings, sets, and so on.
|
||||
"""
|
||||
|
||||
le: SupportsLe
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class GroupedMetadata(Protocol):
|
||||
"""A grouping of multiple objects, like typing.Unpack.
|
||||
|
||||
`GroupedMetadata` on its own is not metadata and has no meaning.
|
||||
All of the constraints and metadata should be fully expressable
|
||||
in terms of the `BaseMetadata`'s returned by `GroupedMetadata.__iter__()`.
|
||||
|
||||
Concrete implementations should override `GroupedMetadata.__iter__()`
|
||||
to add their own metadata.
|
||||
For example:
|
||||
|
||||
>>> @dataclass
|
||||
>>> class Field(GroupedMetadata):
|
||||
>>> gt: float | None = None
|
||||
>>> description: str | None = None
|
||||
...
|
||||
>>> def __iter__(self) -> Iterable[object]:
|
||||
>>> if self.gt is not None:
|
||||
>>> yield Gt(self.gt)
|
||||
>>> if self.description is not None:
|
||||
>>> yield Description(self.gt)
|
||||
|
||||
Also see the implementation of `Interval` below for an example.
|
||||
|
||||
Parsers should recognize this and unpack it so that it can be used
|
||||
both with and without unpacking:
|
||||
|
||||
- `Annotated[int, Field(...)]` (parser must unpack Field)
|
||||
- `Annotated[int, *Field(...)]` (PEP-646)
|
||||
""" # noqa: trailing-whitespace
|
||||
|
||||
@property
|
||||
def __is_annotated_types_grouped_metadata__(self) -> Literal[True]:
|
||||
return True
|
||||
|
||||
def __iter__(self) -> Iterator[object]:
|
||||
...
|
||||
|
||||
if not TYPE_CHECKING:
|
||||
__slots__ = () # allow subclasses to use slots
|
||||
|
||||
def __init_subclass__(cls, *args: Any, **kwargs: Any) -> None:
|
||||
# Basic ABC like functionality without the complexity of an ABC
|
||||
super().__init_subclass__(*args, **kwargs)
|
||||
if cls.__iter__ is GroupedMetadata.__iter__:
|
||||
raise TypeError("Can't subclass GroupedMetadata without implementing __iter__")
|
||||
|
||||
def __iter__(self) -> Iterator[object]: # noqa: F811
|
||||
raise NotImplementedError # more helpful than "None has no attribute..." type errors
|
||||
|
||||
|
||||
@dataclass(frozen=True, **KW_ONLY, **SLOTS)
|
||||
class Interval(GroupedMetadata):
|
||||
"""Interval can express inclusive or exclusive bounds with a single object.
|
||||
|
||||
It accepts keyword arguments ``gt``, ``ge``, ``lt``, and/or ``le``, which
|
||||
are interpreted the same way as the single-bound constraints.
|
||||
"""
|
||||
|
||||
gt: Union[SupportsGt, None] = None
|
||||
ge: Union[SupportsGe, None] = None
|
||||
lt: Union[SupportsLt, None] = None
|
||||
le: Union[SupportsLe, None] = None
|
||||
|
||||
def __iter__(self) -> Iterator[BaseMetadata]:
|
||||
"""Unpack an Interval into zero or more single-bounds."""
|
||||
if self.gt is not None:
|
||||
yield Gt(self.gt)
|
||||
if self.ge is not None:
|
||||
yield Ge(self.ge)
|
||||
if self.lt is not None:
|
||||
yield Lt(self.lt)
|
||||
if self.le is not None:
|
||||
yield Le(self.le)
|
||||
|
||||
|
||||
@dataclass(frozen=True, **SLOTS)
|
||||
class MultipleOf(BaseMetadata):
|
||||
"""MultipleOf(multiple_of=x) might be interpreted in two ways:
|
||||
|
||||
1. Python semantics, implying ``value % multiple_of == 0``, or
|
||||
2. JSONschema semantics, where ``int(value / multiple_of) == value / multiple_of``
|
||||
|
||||
We encourage users to be aware of these two common interpretations,
|
||||
and libraries to carefully document which they implement.
|
||||
"""
|
||||
|
||||
multiple_of: Union[SupportsDiv, SupportsMod]
|
||||
|
||||
|
||||
@dataclass(frozen=True, **SLOTS)
|
||||
class MinLen(BaseMetadata):
|
||||
"""
|
||||
MinLen() implies minimum inclusive length,
|
||||
e.g. ``len(value) >= min_length``.
|
||||
"""
|
||||
|
||||
min_length: Annotated[int, Ge(0)]
|
||||
|
||||
|
||||
@dataclass(frozen=True, **SLOTS)
|
||||
class MaxLen(BaseMetadata):
|
||||
"""
|
||||
MaxLen() implies maximum inclusive length,
|
||||
e.g. ``len(value) <= max_length``.
|
||||
"""
|
||||
|
||||
max_length: Annotated[int, Ge(0)]
|
||||
|
||||
|
||||
@dataclass(frozen=True, **SLOTS)
|
||||
class Len(GroupedMetadata):
|
||||
"""
|
||||
Len() implies that ``min_length <= len(value) <= max_length``.
|
||||
|
||||
Upper bound may be omitted or ``None`` to indicate no upper length bound.
|
||||
"""
|
||||
|
||||
min_length: Annotated[int, Ge(0)] = 0
|
||||
max_length: Optional[Annotated[int, Ge(0)]] = None
|
||||
|
||||
def __iter__(self) -> Iterator[BaseMetadata]:
|
||||
"""Unpack a Len into zone or more single-bounds."""
|
||||
if self.min_length > 0:
|
||||
yield MinLen(self.min_length)
|
||||
if self.max_length is not None:
|
||||
yield MaxLen(self.max_length)
|
||||
|
||||
|
||||
@dataclass(frozen=True, **SLOTS)
|
||||
class Timezone(BaseMetadata):
|
||||
"""Timezone(tz=...) requires a datetime to be aware (or ``tz=None``, naive).
|
||||
|
||||
``Annotated[datetime, Timezone(None)]`` must be a naive datetime.
|
||||
``Timezone[...]`` (the ellipsis literal) expresses that the datetime must be
|
||||
tz-aware but any timezone is allowed.
|
||||
|
||||
You may also pass a specific timezone string or tzinfo object such as
|
||||
``Timezone(timezone.utc)`` or ``Timezone("Africa/Abidjan")`` to express that
|
||||
you only allow a specific timezone, though we note that this is often
|
||||
a symptom of poor design.
|
||||
"""
|
||||
|
||||
tz: Union[str, tzinfo, EllipsisType, None]
|
||||
|
||||
|
||||
@dataclass(frozen=True, **SLOTS)
|
||||
class Unit(BaseMetadata):
|
||||
"""Indicates that the value is a physical quantity with the specified unit.
|
||||
|
||||
It is intended for usage with numeric types, where the value represents the
|
||||
magnitude of the quantity. For example, ``distance: Annotated[float, Unit('m')]``
|
||||
or ``speed: Annotated[float, Unit('m/s')]``.
|
||||
|
||||
Interpretation of the unit string is left to the discretion of the consumer.
|
||||
It is suggested to follow conventions established by python libraries that work
|
||||
with physical quantities, such as
|
||||
|
||||
- ``pint`` : <https://pint.readthedocs.io/en/stable/>
|
||||
- ``astropy.units``: <https://docs.astropy.org/en/stable/units/>
|
||||
|
||||
For indicating a quantity with a certain dimensionality but without a specific unit
|
||||
it is recommended to use square brackets, e.g. `Annotated[float, Unit('[time]')]`.
|
||||
Note, however, ``annotated_types`` itself makes no use of the unit string.
|
||||
"""
|
||||
|
||||
unit: str
|
||||
|
||||
|
||||
@dataclass(frozen=True, **SLOTS)
|
||||
class Predicate(BaseMetadata):
|
||||
"""``Predicate(func: Callable)`` implies `func(value)` is truthy for valid values.
|
||||
|
||||
Users should prefer statically inspectable metadata, but if you need the full
|
||||
power and flexibility of arbitrary runtime predicates... here it is.
|
||||
|
||||
We provide a few predefined predicates for common string constraints:
|
||||
``IsLower = Predicate(str.islower)``, ``IsUpper = Predicate(str.isupper)``, and
|
||||
``IsDigits = Predicate(str.isdigit)``. Users are encouraged to use methods which
|
||||
can be given special handling, and avoid indirection like ``lambda s: s.lower()``.
|
||||
|
||||
Some libraries might have special logic to handle certain predicates, e.g. by
|
||||
checking for `str.isdigit` and using its presence to both call custom logic to
|
||||
enforce digit-only strings, and customise some generated external schema.
|
||||
|
||||
We do not specify what behaviour should be expected for predicates that raise
|
||||
an exception. For example `Annotated[int, Predicate(str.isdigit)]` might silently
|
||||
skip invalid constraints, or statically raise an error; or it might try calling it
|
||||
and then propagate or discard the resulting exception.
|
||||
"""
|
||||
|
||||
func: Callable[[Any], bool]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
if getattr(self.func, "__name__", "<lambda>") == "<lambda>":
|
||||
return f"{self.__class__.__name__}({self.func!r})"
|
||||
if isinstance(self.func, (types.MethodType, types.BuiltinMethodType)) and (
|
||||
namespace := getattr(self.func.__self__, "__name__", None)
|
||||
):
|
||||
return f"{self.__class__.__name__}({namespace}.{self.func.__name__})"
|
||||
if isinstance(self.func, type(str.isascii)): # method descriptor
|
||||
return f"{self.__class__.__name__}({self.func.__qualname__})"
|
||||
return f"{self.__class__.__name__}({self.func.__name__})"
|
||||
|
||||
|
||||
@dataclass
|
||||
class Not:
|
||||
func: Callable[[Any], bool]
|
||||
|
||||
def __call__(self, __v: Any) -> bool:
|
||||
return not self.func(__v)
|
||||
|
||||
|
||||
_StrType = TypeVar("_StrType", bound=str)
|
||||
|
||||
LowerCase = Annotated[_StrType, Predicate(str.islower)]
|
||||
"""
|
||||
Return True if the string is a lowercase string, False otherwise.
|
||||
|
||||
A string is lowercase if all cased characters in the string are lowercase and there is at least one cased character in the string.
|
||||
""" # noqa: E501
|
||||
UpperCase = Annotated[_StrType, Predicate(str.isupper)]
|
||||
"""
|
||||
Return True if the string is an uppercase string, False otherwise.
|
||||
|
||||
A string is uppercase if all cased characters in the string are uppercase and there is at least one cased character in the string.
|
||||
""" # noqa: E501
|
||||
IsDigit = Annotated[_StrType, Predicate(str.isdigit)]
|
||||
IsDigits = IsDigit # type: ignore # plural for backwards compatibility, see #63
|
||||
"""
|
||||
Return True if the string is a digit string, False otherwise.
|
||||
|
||||
A string is a digit string if all characters in the string are digits and there is at least one character in the string.
|
||||
""" # noqa: E501
|
||||
IsAscii = Annotated[_StrType, Predicate(str.isascii)]
|
||||
"""
|
||||
Return True if all characters in the string are ASCII, False otherwise.
|
||||
|
||||
ASCII characters have code points in the range U+0000-U+007F. Empty string is ASCII too.
|
||||
"""
|
||||
|
||||
_NumericType = TypeVar('_NumericType', bound=Union[SupportsFloat, SupportsIndex])
|
||||
IsFinite = Annotated[_NumericType, Predicate(math.isfinite)]
|
||||
"""Return True if x is neither an infinity nor a NaN, and False otherwise."""
|
||||
IsNotFinite = Annotated[_NumericType, Predicate(Not(math.isfinite))]
|
||||
"""Return True if x is one of infinity or NaN, and False otherwise"""
|
||||
IsNan = Annotated[_NumericType, Predicate(math.isnan)]
|
||||
"""Return True if x is a NaN (not a number), and False otherwise."""
|
||||
IsNotNan = Annotated[_NumericType, Predicate(Not(math.isnan))]
|
||||
"""Return True if x is anything but NaN (not a number), and False otherwise."""
|
||||
IsInfinite = Annotated[_NumericType, Predicate(math.isinf)]
|
||||
"""Return True if x is a positive or negative infinity, and False otherwise."""
|
||||
IsNotInfinite = Annotated[_NumericType, Predicate(Not(math.isinf))]
|
||||
"""Return True if x is neither a positive or negative infinity, and False otherwise."""
|
||||
|
||||
try:
|
||||
from typing_extensions import DocInfo, doc # type: ignore [attr-defined]
|
||||
except ImportError:
|
||||
|
||||
@dataclass(frozen=True, **SLOTS)
|
||||
class DocInfo: # type: ignore [no-redef]
|
||||
""" "
|
||||
The return value of doc(), mainly to be used by tools that want to extract the
|
||||
Annotated documentation at runtime.
|
||||
"""
|
||||
|
||||
documentation: str
|
||||
"""The documentation string passed to doc()."""
|
||||
|
||||
def doc(
|
||||
documentation: str,
|
||||
) -> DocInfo:
|
||||
"""
|
||||
Add documentation to a type annotation inside of Annotated.
|
||||
|
||||
For example:
|
||||
|
||||
>>> def hi(name: Annotated[int, doc("The name of the user")]) -> None: ...
|
||||
"""
|
||||
return DocInfo(documentation)
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,151 @@
|
||||
import math
|
||||
import sys
|
||||
from datetime import date, datetime, timedelta, timezone
|
||||
from decimal import Decimal
|
||||
from typing import Any, Dict, Iterable, Iterator, List, NamedTuple, Set, Tuple
|
||||
|
||||
if sys.version_info < (3, 9):
|
||||
from typing_extensions import Annotated
|
||||
else:
|
||||
from typing import Annotated
|
||||
|
||||
import annotated_types as at
|
||||
|
||||
|
||||
class Case(NamedTuple):
|
||||
"""
|
||||
A test case for `annotated_types`.
|
||||
"""
|
||||
|
||||
annotation: Any
|
||||
valid_cases: Iterable[Any]
|
||||
invalid_cases: Iterable[Any]
|
||||
|
||||
|
||||
def cases() -> Iterable[Case]:
|
||||
# Gt, Ge, Lt, Le
|
||||
yield Case(Annotated[int, at.Gt(4)], (5, 6, 1000), (4, 0, -1))
|
||||
yield Case(Annotated[float, at.Gt(0.5)], (0.6, 0.7, 0.8, 0.9), (0.5, 0.0, -0.1))
|
||||
yield Case(
|
||||
Annotated[datetime, at.Gt(datetime(2000, 1, 1))],
|
||||
[datetime(2000, 1, 2), datetime(2000, 1, 3)],
|
||||
[datetime(2000, 1, 1), datetime(1999, 12, 31)],
|
||||
)
|
||||
yield Case(
|
||||
Annotated[datetime, at.Gt(date(2000, 1, 1))],
|
||||
[date(2000, 1, 2), date(2000, 1, 3)],
|
||||
[date(2000, 1, 1), date(1999, 12, 31)],
|
||||
)
|
||||
yield Case(
|
||||
Annotated[datetime, at.Gt(Decimal('1.123'))],
|
||||
[Decimal('1.1231'), Decimal('123')],
|
||||
[Decimal('1.123'), Decimal('0')],
|
||||
)
|
||||
|
||||
yield Case(Annotated[int, at.Ge(4)], (4, 5, 6, 1000, 4), (0, -1))
|
||||
yield Case(Annotated[float, at.Ge(0.5)], (0.5, 0.6, 0.7, 0.8, 0.9), (0.4, 0.0, -0.1))
|
||||
yield Case(
|
||||
Annotated[datetime, at.Ge(datetime(2000, 1, 1))],
|
||||
[datetime(2000, 1, 2), datetime(2000, 1, 3)],
|
||||
[datetime(1998, 1, 1), datetime(1999, 12, 31)],
|
||||
)
|
||||
|
||||
yield Case(Annotated[int, at.Lt(4)], (0, -1), (4, 5, 6, 1000, 4))
|
||||
yield Case(Annotated[float, at.Lt(0.5)], (0.4, 0.0, -0.1), (0.5, 0.6, 0.7, 0.8, 0.9))
|
||||
yield Case(
|
||||
Annotated[datetime, at.Lt(datetime(2000, 1, 1))],
|
||||
[datetime(1999, 12, 31), datetime(1999, 12, 31)],
|
||||
[datetime(2000, 1, 2), datetime(2000, 1, 3)],
|
||||
)
|
||||
|
||||
yield Case(Annotated[int, at.Le(4)], (4, 0, -1), (5, 6, 1000))
|
||||
yield Case(Annotated[float, at.Le(0.5)], (0.5, 0.0, -0.1), (0.6, 0.7, 0.8, 0.9))
|
||||
yield Case(
|
||||
Annotated[datetime, at.Le(datetime(2000, 1, 1))],
|
||||
[datetime(2000, 1, 1), datetime(1999, 12, 31)],
|
||||
[datetime(2000, 1, 2), datetime(2000, 1, 3)],
|
||||
)
|
||||
|
||||
# Interval
|
||||
yield Case(Annotated[int, at.Interval(gt=4)], (5, 6, 1000), (4, 0, -1))
|
||||
yield Case(Annotated[int, at.Interval(gt=4, lt=10)], (5, 6), (4, 10, 1000, 0, -1))
|
||||
yield Case(Annotated[float, at.Interval(ge=0.5, le=1)], (0.5, 0.9, 1), (0.49, 1.1))
|
||||
yield Case(
|
||||
Annotated[datetime, at.Interval(gt=datetime(2000, 1, 1), le=datetime(2000, 1, 3))],
|
||||
[datetime(2000, 1, 2), datetime(2000, 1, 3)],
|
||||
[datetime(2000, 1, 1), datetime(2000, 1, 4)],
|
||||
)
|
||||
|
||||
yield Case(Annotated[int, at.MultipleOf(multiple_of=3)], (0, 3, 9), (1, 2, 4))
|
||||
yield Case(Annotated[float, at.MultipleOf(multiple_of=0.5)], (0, 0.5, 1, 1.5), (0.4, 1.1))
|
||||
|
||||
# lengths
|
||||
|
||||
yield Case(Annotated[str, at.MinLen(3)], ('123', '1234', 'x' * 10), ('', '1', '12'))
|
||||
yield Case(Annotated[str, at.Len(3)], ('123', '1234', 'x' * 10), ('', '1', '12'))
|
||||
yield Case(Annotated[List[int], at.MinLen(3)], ([1, 2, 3], [1, 2, 3, 4], [1] * 10), ([], [1], [1, 2]))
|
||||
yield Case(Annotated[List[int], at.Len(3)], ([1, 2, 3], [1, 2, 3, 4], [1] * 10), ([], [1], [1, 2]))
|
||||
|
||||
yield Case(Annotated[str, at.MaxLen(4)], ('', '1234'), ('12345', 'x' * 10))
|
||||
yield Case(Annotated[str, at.Len(0, 4)], ('', '1234'), ('12345', 'x' * 10))
|
||||
yield Case(Annotated[List[str], at.MaxLen(4)], ([], ['a', 'bcdef'], ['a', 'b', 'c']), (['a'] * 5, ['b'] * 10))
|
||||
yield Case(Annotated[List[str], at.Len(0, 4)], ([], ['a', 'bcdef'], ['a', 'b', 'c']), (['a'] * 5, ['b'] * 10))
|
||||
|
||||
yield Case(Annotated[str, at.Len(3, 5)], ('123', '12345'), ('', '1', '12', '123456', 'x' * 10))
|
||||
yield Case(Annotated[str, at.Len(3, 3)], ('123',), ('12', '1234'))
|
||||
|
||||
yield Case(Annotated[Dict[int, int], at.Len(2, 3)], [{1: 1, 2: 2}], [{}, {1: 1}, {1: 1, 2: 2, 3: 3, 4: 4}])
|
||||
yield Case(Annotated[Set[int], at.Len(2, 3)], ({1, 2}, {1, 2, 3}), (set(), {1}, {1, 2, 3, 4}))
|
||||
yield Case(Annotated[Tuple[int, ...], at.Len(2, 3)], ((1, 2), (1, 2, 3)), ((), (1,), (1, 2, 3, 4)))
|
||||
|
||||
# Timezone
|
||||
|
||||
yield Case(
|
||||
Annotated[datetime, at.Timezone(None)], [datetime(2000, 1, 1)], [datetime(2000, 1, 1, tzinfo=timezone.utc)]
|
||||
)
|
||||
yield Case(
|
||||
Annotated[datetime, at.Timezone(...)], [datetime(2000, 1, 1, tzinfo=timezone.utc)], [datetime(2000, 1, 1)]
|
||||
)
|
||||
yield Case(
|
||||
Annotated[datetime, at.Timezone(timezone.utc)],
|
||||
[datetime(2000, 1, 1, tzinfo=timezone.utc)],
|
||||
[datetime(2000, 1, 1), datetime(2000, 1, 1, tzinfo=timezone(timedelta(hours=6)))],
|
||||
)
|
||||
yield Case(
|
||||
Annotated[datetime, at.Timezone('Europe/London')],
|
||||
[datetime(2000, 1, 1, tzinfo=timezone(timedelta(0), name='Europe/London'))],
|
||||
[datetime(2000, 1, 1), datetime(2000, 1, 1, tzinfo=timezone(timedelta(hours=6)))],
|
||||
)
|
||||
|
||||
# Quantity
|
||||
|
||||
yield Case(Annotated[float, at.Unit(unit='m')], (5, 4.2), ('5m', '4.2m'))
|
||||
|
||||
# predicate types
|
||||
|
||||
yield Case(at.LowerCase[str], ['abc', 'foobar'], ['', 'A', 'Boom'])
|
||||
yield Case(at.UpperCase[str], ['ABC', 'DEFO'], ['', 'a', 'abc', 'AbC'])
|
||||
yield Case(at.IsDigit[str], ['123'], ['', 'ab', 'a1b2'])
|
||||
yield Case(at.IsAscii[str], ['123', 'foo bar'], ['£100', '😊', 'whatever 👀'])
|
||||
|
||||
yield Case(Annotated[int, at.Predicate(lambda x: x % 2 == 0)], [0, 2, 4], [1, 3, 5])
|
||||
|
||||
yield Case(at.IsFinite[float], [1.23], [math.nan, math.inf, -math.inf])
|
||||
yield Case(at.IsNotFinite[float], [math.nan, math.inf], [1.23])
|
||||
yield Case(at.IsNan[float], [math.nan], [1.23, math.inf])
|
||||
yield Case(at.IsNotNan[float], [1.23, math.inf], [math.nan])
|
||||
yield Case(at.IsInfinite[float], [math.inf], [math.nan, 1.23])
|
||||
yield Case(at.IsNotInfinite[float], [math.nan, 1.23], [math.inf])
|
||||
|
||||
# check stacked predicates
|
||||
yield Case(at.IsInfinite[Annotated[float, at.Predicate(lambda x: x > 0)]], [math.inf], [-math.inf, 1.23, math.nan])
|
||||
|
||||
# doc
|
||||
yield Case(Annotated[int, at.doc("A number")], [1, 2], [])
|
||||
|
||||
# custom GroupedMetadata
|
||||
class MyCustomGroupedMetadata(at.GroupedMetadata):
|
||||
def __iter__(self) -> Iterator[at.Predicate]:
|
||||
yield at.Predicate(lambda x: float(x).is_integer())
|
||||
|
||||
yield Case(Annotated[float, MyCustomGroupedMetadata()], [0, 2.0], [0.01, 1.5])
|
||||
@@ -0,0 +1 @@
|
||||
pip
|
||||
@@ -0,0 +1,105 @@
|
||||
Metadata-Version: 2.4
|
||||
Name: anyio
|
||||
Version: 4.13.0
|
||||
Summary: High-level concurrency and networking framework on top of asyncio or Trio
|
||||
Author-email: Alex Grönholm <alex.gronholm@nextday.fi>
|
||||
License-Expression: MIT
|
||||
Project-URL: Documentation, https://anyio.readthedocs.io/en/latest/
|
||||
Project-URL: Changelog, https://anyio.readthedocs.io/en/stable/versionhistory.html
|
||||
Project-URL: Source code, https://github.com/agronholm/anyio
|
||||
Project-URL: Issue tracker, https://github.com/agronholm/anyio/issues
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Framework :: AnyIO
|
||||
Classifier: Typing :: Typed
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.10
|
||||
Classifier: Programming Language :: Python :: 3.11
|
||||
Classifier: Programming Language :: Python :: 3.12
|
||||
Classifier: Programming Language :: Python :: 3.13
|
||||
Classifier: Programming Language :: Python :: 3.14
|
||||
Requires-Python: >=3.10
|
||||
Description-Content-Type: text/x-rst
|
||||
License-File: LICENSE
|
||||
Requires-Dist: exceptiongroup>=1.0.2; python_version < "3.11"
|
||||
Requires-Dist: idna>=2.8
|
||||
Requires-Dist: typing_extensions>=4.5; python_version < "3.13"
|
||||
Provides-Extra: trio
|
||||
Requires-Dist: trio>=0.32.0; extra == "trio"
|
||||
Dynamic: license-file
|
||||
|
||||
.. image:: https://github.com/agronholm/anyio/actions/workflows/test.yml/badge.svg
|
||||
:target: https://github.com/agronholm/anyio/actions/workflows/test.yml
|
||||
:alt: Build Status
|
||||
.. image:: https://coveralls.io/repos/github/agronholm/anyio/badge.svg?branch=master
|
||||
:target: https://coveralls.io/github/agronholm/anyio?branch=master
|
||||
:alt: Code Coverage
|
||||
.. image:: https://readthedocs.org/projects/anyio/badge/?version=latest
|
||||
:target: https://anyio.readthedocs.io/en/latest/?badge=latest
|
||||
:alt: Documentation
|
||||
.. image:: https://badges.gitter.im/gitterHQ/gitter.svg
|
||||
:target: https://gitter.im/python-trio/AnyIO
|
||||
:alt: Gitter chat
|
||||
.. image:: https://tidelift.com/badges/package/pypi/anyio
|
||||
:target: https://tidelift.com/subscription/pkg/pypi-anyio
|
||||
:alt: Tidelift
|
||||
|
||||
AnyIO is an asynchronous networking and concurrency library that works on top of either asyncio_ or
|
||||
Trio_. It implements Trio-like `structured concurrency`_ (SC) on top of asyncio and works in harmony
|
||||
with the native SC of Trio itself.
|
||||
|
||||
Applications and libraries written against AnyIO's API will run unmodified on either asyncio_ or
|
||||
Trio_. AnyIO can also be adopted into a library or application incrementally – bit by bit, no full
|
||||
refactoring necessary. It will blend in with the native libraries of your chosen backend.
|
||||
|
||||
To find out why you might want to use AnyIO's APIs instead of asyncio's, you can read about it
|
||||
`here <https://anyio.readthedocs.io/en/stable/why.html>`_.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
View full documentation at: https://anyio.readthedocs.io/
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
AnyIO offers the following functionality:
|
||||
|
||||
* Task groups (nurseries_ in trio terminology)
|
||||
* High-level networking (TCP, UDP and UNIX sockets)
|
||||
|
||||
* `Happy eyeballs`_ algorithm for TCP connections (more robust than that of asyncio on Python
|
||||
3.8)
|
||||
* async/await style UDP sockets (unlike asyncio where you still have to use Transports and
|
||||
Protocols)
|
||||
|
||||
* A versatile API for byte streams and object streams
|
||||
* Inter-task synchronization and communication (locks, conditions, events, semaphores, object
|
||||
streams)
|
||||
* Worker threads
|
||||
* Subprocesses
|
||||
* Subinterpreter support for code parallelization (on Python 3.13 and later)
|
||||
* Asynchronous file I/O (using worker threads)
|
||||
* Signal handling
|
||||
* Asynchronous version of the functools_ module
|
||||
|
||||
AnyIO also comes with its own pytest_ plugin which also supports asynchronous fixtures.
|
||||
It even works with the popular Hypothesis_ library.
|
||||
|
||||
.. _asyncio: https://docs.python.org/3/library/asyncio.html
|
||||
.. _Trio: https://github.com/python-trio/trio
|
||||
.. _structured concurrency: https://en.wikipedia.org/wiki/Structured_concurrency
|
||||
.. _nurseries: https://trio.readthedocs.io/en/stable/reference-core.html#nurseries-and-spawning
|
||||
.. _Happy eyeballs: https://en.wikipedia.org/wiki/Happy_Eyeballs
|
||||
.. _pytest: https://docs.pytest.org/en/latest/
|
||||
.. _functools: https://docs.python.org/3/library/functools.html
|
||||
.. _Hypothesis: https://hypothesis.works/
|
||||
|
||||
Security contact information
|
||||
----------------------------
|
||||
|
||||
To report a security vulnerability, please use the `Tidelift security contact`_.
|
||||
Tidelift will coordinate the fix and disclosure.
|
||||
|
||||
.. _Tidelift security contact: https://tidelift.com/security
|
||||
@@ -0,0 +1,92 @@
|
||||
anyio-4.13.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
anyio-4.13.0.dist-info/METADATA,sha256=F0EYfiPlmTRwmJN2JktNxJg1GNnl0wHhzOWmz7pFvjM,4513
|
||||
anyio-4.13.0.dist-info/RECORD,,
|
||||
anyio-4.13.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
||||
anyio-4.13.0.dist-info/entry_points.txt,sha256=_d6Yu6uiaZmNe0CydowirE9Cmg7zUL2g08tQpoS3Qvc,39
|
||||
anyio-4.13.0.dist-info/licenses/LICENSE,sha256=U2GsncWPLvX9LpsJxoKXwX8ElQkJu8gCO9uC6s8iwrA,1081
|
||||
anyio-4.13.0.dist-info/top_level.txt,sha256=QglSMiWX8_5dpoVAEIHdEYzvqFMdSYWmCj6tYw2ITkQ,6
|
||||
anyio/__init__.py,sha256=7iDVqMUprUuKNY91FuoKqayAhR-OY136YDPI6P78HHk,6170
|
||||
anyio/__pycache__/__init__.cpython-312.pyc,,
|
||||
anyio/__pycache__/from_thread.cpython-312.pyc,,
|
||||
anyio/__pycache__/functools.cpython-312.pyc,,
|
||||
anyio/__pycache__/lowlevel.cpython-312.pyc,,
|
||||
anyio/__pycache__/pytest_plugin.cpython-312.pyc,,
|
||||
anyio/__pycache__/to_interpreter.cpython-312.pyc,,
|
||||
anyio/__pycache__/to_process.cpython-312.pyc,,
|
||||
anyio/__pycache__/to_thread.cpython-312.pyc,,
|
||||
anyio/_backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
anyio/_backends/__pycache__/__init__.cpython-312.pyc,,
|
||||
anyio/_backends/__pycache__/_asyncio.cpython-312.pyc,,
|
||||
anyio/_backends/__pycache__/_trio.cpython-312.pyc,,
|
||||
anyio/_backends/_asyncio.py,sha256=kuqlg2sBUsFdgY80xSDAw60Gx_4WNCl9iSL5XlY6lCU,99476
|
||||
anyio/_backends/_trio.py,sha256=l9U-TsKRxzmTQxSMvOhn0bNeFn_iRx3Ho30jvR5Bdu0,41366
|
||||
anyio/_core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
anyio/_core/__pycache__/__init__.cpython-312.pyc,,
|
||||
anyio/_core/__pycache__/_asyncio_selector_thread.cpython-312.pyc,,
|
||||
anyio/_core/__pycache__/_contextmanagers.cpython-312.pyc,,
|
||||
anyio/_core/__pycache__/_eventloop.cpython-312.pyc,,
|
||||
anyio/_core/__pycache__/_exceptions.cpython-312.pyc,,
|
||||
anyio/_core/__pycache__/_fileio.cpython-312.pyc,,
|
||||
anyio/_core/__pycache__/_resources.cpython-312.pyc,,
|
||||
anyio/_core/__pycache__/_signals.cpython-312.pyc,,
|
||||
anyio/_core/__pycache__/_sockets.cpython-312.pyc,,
|
||||
anyio/_core/__pycache__/_streams.cpython-312.pyc,,
|
||||
anyio/_core/__pycache__/_subprocesses.cpython-312.pyc,,
|
||||
anyio/_core/__pycache__/_synchronization.cpython-312.pyc,,
|
||||
anyio/_core/__pycache__/_tasks.cpython-312.pyc,,
|
||||
anyio/_core/__pycache__/_tempfile.cpython-312.pyc,,
|
||||
anyio/_core/__pycache__/_testing.cpython-312.pyc,,
|
||||
anyio/_core/__pycache__/_typedattr.cpython-312.pyc,,
|
||||
anyio/_core/_asyncio_selector_thread.py,sha256=2PdxFM3cs02Kp6BSppbvmRT7q7asreTW5FgBxEsflBo,5626
|
||||
anyio/_core/_contextmanagers.py,sha256=YInBCabiEeS-UaP_Jdxa1CaFC71ETPW8HZTHIM8Rsc8,7215
|
||||
anyio/_core/_eventloop.py,sha256=c2EdcBX-xnKwxPcC4Pjn3_qG9I-x4IWFO2R9RqCGjM4,6448
|
||||
anyio/_core/_exceptions.py,sha256=Y3aq-Wxd7Q2HqwSg7nZPvRsHEuGazv_qeet6gqEBdPk,4407
|
||||
anyio/_core/_fileio.py,sha256=CKi1gFNiW2G4knWeBE7He7-rptQwgYjDUWfG8DSlvLs,25665
|
||||
anyio/_core/_resources.py,sha256=NbmU5O5UX3xEyACnkmYX28Fmwdl-f-ny0tHym26e0w0,435
|
||||
anyio/_core/_signals.py,sha256=mjTBB2hTKNPRlU0IhnijeQedpWOGERDiMjSlJQsFrug,1016
|
||||
anyio/_core/_sockets.py,sha256=RBXHcUqZt5gg_-OOfgHVv8uq2FSKk1uVUzTdpjBoI1o,34977
|
||||
anyio/_core/_streams.py,sha256=FczFwIgDpnkK0bODWJXMpsUJYdvAD04kaUaGzJU8DK0,1806
|
||||
anyio/_core/_subprocesses.py,sha256=tkmkPKEkEaiMD8C9WRZBlmgjOYRDRbZdte6e-unay2E,7916
|
||||
anyio/_core/_synchronization.py,sha256=9G3fvRsPNrrWJ_Z6gD_80wXq8I8qgAyhwM8PvHQnT2c,21061
|
||||
anyio/_core/_tasks.py,sha256=pVB7K6AAulzUM8YgXAeqNZG44nSyZ1bYJjH8GznC00I,5435
|
||||
anyio/_core/_tempfile.py,sha256=jE2w59FRF3yRo4vjkjfZF2YcqsBZvc66VWRwrJGDYGk,19624
|
||||
anyio/_core/_testing.py,sha256=u7MPqGXwpTxqI7hclSdNA30z2GH1Nw258uwKvy_RfBg,2340
|
||||
anyio/_core/_typedattr.py,sha256=P4ozZikn3-DbpoYcvyghS_FOYAgbmUxeoU8-L_07pZM,2508
|
||||
anyio/abc/__init__.py,sha256=6mWhcl_pGXhrgZVHP_TCfMvIXIOp9mroEFM90fYCU_U,2869
|
||||
anyio/abc/__pycache__/__init__.cpython-312.pyc,,
|
||||
anyio/abc/__pycache__/_eventloop.cpython-312.pyc,,
|
||||
anyio/abc/__pycache__/_resources.cpython-312.pyc,,
|
||||
anyio/abc/__pycache__/_sockets.cpython-312.pyc,,
|
||||
anyio/abc/__pycache__/_streams.cpython-312.pyc,,
|
||||
anyio/abc/__pycache__/_subprocesses.cpython-312.pyc,,
|
||||
anyio/abc/__pycache__/_tasks.cpython-312.pyc,,
|
||||
anyio/abc/__pycache__/_testing.cpython-312.pyc,,
|
||||
anyio/abc/_eventloop.py,sha256=39lYnmtvoHaZw22sWBKOTA_zv7bamOnr8O49PqgDXdw,10629
|
||||
anyio/abc/_resources.py,sha256=DrYvkNN1hH6Uvv5_5uKySvDsnknGVDe8FCKfko0VtN8,783
|
||||
anyio/abc/_sockets.py,sha256=OmVDrfemVvF9c5K1tpBgQyV6fn5v0XyCExLAqBOGz9o,13124
|
||||
anyio/abc/_streams.py,sha256=HYvna1iZbWcwLROTO6IhLX79RTRLPShZMWe0sG1q54I,7481
|
||||
anyio/abc/_subprocesses.py,sha256=cumAPJTktOQtw63IqG0lDpyZqu_l1EElvQHMiwJgL08,2067
|
||||
anyio/abc/_tasks.py,sha256=KC7wrciE48AINOI-AhPutnFhe1ewfP7QnamFlDzqesQ,3721
|
||||
anyio/abc/_testing.py,sha256=tBJUzkSfOXJw23fe8qSJ03kJlShOYjjaEyFB6k6MYT8,1821
|
||||
anyio/from_thread.py,sha256=L-0w1HxJ6BSb-KuVi57k5Tkc3yzQrx3QK5tAxMPcY-0,19141
|
||||
anyio/functools.py,sha256=5AWM1iYTKkTzptvUhQDdLSh5GvbBW-vcs-SAUfIfA9A,12076
|
||||
anyio/lowlevel.py,sha256=AyKLVK3LaWSoK39LkCKxE4_GDMLKZBNqTrLUgk63y80,5158
|
||||
anyio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
anyio/pytest_plugin.py,sha256=t6h4KJstqIxfxwTZ1YO8vpUVuB99nfCLltn0NHfatHo,12775
|
||||
anyio/streams/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
anyio/streams/__pycache__/__init__.cpython-312.pyc,,
|
||||
anyio/streams/__pycache__/buffered.cpython-312.pyc,,
|
||||
anyio/streams/__pycache__/file.cpython-312.pyc,,
|
||||
anyio/streams/__pycache__/memory.cpython-312.pyc,,
|
||||
anyio/streams/__pycache__/stapled.cpython-312.pyc,,
|
||||
anyio/streams/__pycache__/text.cpython-312.pyc,,
|
||||
anyio/streams/__pycache__/tls.cpython-312.pyc,,
|
||||
anyio/streams/buffered.py,sha256=2R3PeJhe4EXrdYqz44Y6-Eg9R6DrmlsYrP36Ir43-po,6263
|
||||
anyio/streams/file.py,sha256=msnrotVKGMQomUu_Rj2qz9MvIdUp6d3JGr7MOEO8kV4,4428
|
||||
anyio/streams/memory.py,sha256=F0zwzvFJKAhX_LRZGoKzzqDC2oMM-f-yyTBrEYEGOaU,10740
|
||||
anyio/streams/stapled.py,sha256=T8Xqwf8K6EgURPxbt1N4i7A8BAk-gScv-GRhjLXIf_o,4390
|
||||
anyio/streams/text.py,sha256=BcVAGJw1VRvtIqnv-o0Rb0pwH7p8vwlvl21xHq522ag,5765
|
||||
anyio/streams/tls.py,sha256=DQVkXUvsTEYKkBO8dlVU7j_5H8QOtLy4sGi1Wrjqevo,15303
|
||||
anyio/to_interpreter.py,sha256=_mLngrMy97TMR6VbW4Y6YzDUk9ZuPcQMPlkuyRh3C9k,7100
|
||||
anyio/to_process.py,sha256=J7gAA_YOuoHqnpDAf5fm1Qu6kOmTzdFbiDNvnV755vk,9798
|
||||
anyio/to_thread.py,sha256=f6h_k2d743GBv9FhAnhM_YpTvWgIrzBy9cOE0eJ1UJw,2693
|
||||
@@ -0,0 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: setuptools (82.0.1)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
[pytest11]
|
||||
anyio = anyio.pytest_plugin
|
||||
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Alex Grönholm
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1 @@
|
||||
anyio
|
||||
@@ -0,0 +1,111 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from ._core._contextmanagers import AsyncContextManagerMixin as AsyncContextManagerMixin
|
||||
from ._core._contextmanagers import ContextManagerMixin as ContextManagerMixin
|
||||
from ._core._eventloop import current_time as current_time
|
||||
from ._core._eventloop import get_all_backends as get_all_backends
|
||||
from ._core._eventloop import get_available_backends as get_available_backends
|
||||
from ._core._eventloop import get_cancelled_exc_class as get_cancelled_exc_class
|
||||
from ._core._eventloop import run as run
|
||||
from ._core._eventloop import sleep as sleep
|
||||
from ._core._eventloop import sleep_forever as sleep_forever
|
||||
from ._core._eventloop import sleep_until as sleep_until
|
||||
from ._core._exceptions import BrokenResourceError as BrokenResourceError
|
||||
from ._core._exceptions import BrokenWorkerInterpreter as BrokenWorkerInterpreter
|
||||
from ._core._exceptions import BrokenWorkerProcess as BrokenWorkerProcess
|
||||
from ._core._exceptions import BusyResourceError as BusyResourceError
|
||||
from ._core._exceptions import ClosedResourceError as ClosedResourceError
|
||||
from ._core._exceptions import ConnectionFailed as ConnectionFailed
|
||||
from ._core._exceptions import DelimiterNotFound as DelimiterNotFound
|
||||
from ._core._exceptions import EndOfStream as EndOfStream
|
||||
from ._core._exceptions import IncompleteRead as IncompleteRead
|
||||
from ._core._exceptions import NoEventLoopError as NoEventLoopError
|
||||
from ._core._exceptions import RunFinishedError as RunFinishedError
|
||||
from ._core._exceptions import TypedAttributeLookupError as TypedAttributeLookupError
|
||||
from ._core._exceptions import WouldBlock as WouldBlock
|
||||
from ._core._fileio import AsyncFile as AsyncFile
|
||||
from ._core._fileio import Path as Path
|
||||
from ._core._fileio import open_file as open_file
|
||||
from ._core._fileio import wrap_file as wrap_file
|
||||
from ._core._resources import aclose_forcefully as aclose_forcefully
|
||||
from ._core._signals import open_signal_receiver as open_signal_receiver
|
||||
from ._core._sockets import TCPConnectable as TCPConnectable
|
||||
from ._core._sockets import UNIXConnectable as UNIXConnectable
|
||||
from ._core._sockets import as_connectable as as_connectable
|
||||
from ._core._sockets import connect_tcp as connect_tcp
|
||||
from ._core._sockets import connect_unix as connect_unix
|
||||
from ._core._sockets import create_connected_udp_socket as create_connected_udp_socket
|
||||
from ._core._sockets import (
|
||||
create_connected_unix_datagram_socket as create_connected_unix_datagram_socket,
|
||||
)
|
||||
from ._core._sockets import create_tcp_listener as create_tcp_listener
|
||||
from ._core._sockets import create_udp_socket as create_udp_socket
|
||||
from ._core._sockets import create_unix_datagram_socket as create_unix_datagram_socket
|
||||
from ._core._sockets import create_unix_listener as create_unix_listener
|
||||
from ._core._sockets import getaddrinfo as getaddrinfo
|
||||
from ._core._sockets import getnameinfo as getnameinfo
|
||||
from ._core._sockets import notify_closing as notify_closing
|
||||
from ._core._sockets import wait_readable as wait_readable
|
||||
from ._core._sockets import wait_socket_readable as wait_socket_readable
|
||||
from ._core._sockets import wait_socket_writable as wait_socket_writable
|
||||
from ._core._sockets import wait_writable as wait_writable
|
||||
from ._core._streams import create_memory_object_stream as create_memory_object_stream
|
||||
from ._core._subprocesses import open_process as open_process
|
||||
from ._core._subprocesses import run_process as run_process
|
||||
from ._core._synchronization import CapacityLimiter as CapacityLimiter
|
||||
from ._core._synchronization import (
|
||||
CapacityLimiterStatistics as CapacityLimiterStatistics,
|
||||
)
|
||||
from ._core._synchronization import Condition as Condition
|
||||
from ._core._synchronization import ConditionStatistics as ConditionStatistics
|
||||
from ._core._synchronization import Event as Event
|
||||
from ._core._synchronization import EventStatistics as EventStatistics
|
||||
from ._core._synchronization import Lock as Lock
|
||||
from ._core._synchronization import LockStatistics as LockStatistics
|
||||
from ._core._synchronization import ResourceGuard as ResourceGuard
|
||||
from ._core._synchronization import Semaphore as Semaphore
|
||||
from ._core._synchronization import SemaphoreStatistics as SemaphoreStatistics
|
||||
from ._core._tasks import TASK_STATUS_IGNORED as TASK_STATUS_IGNORED
|
||||
from ._core._tasks import CancelScope as CancelScope
|
||||
from ._core._tasks import create_task_group as create_task_group
|
||||
from ._core._tasks import current_effective_deadline as current_effective_deadline
|
||||
from ._core._tasks import fail_after as fail_after
|
||||
from ._core._tasks import move_on_after as move_on_after
|
||||
from ._core._tempfile import NamedTemporaryFile as NamedTemporaryFile
|
||||
from ._core._tempfile import SpooledTemporaryFile as SpooledTemporaryFile
|
||||
from ._core._tempfile import TemporaryDirectory as TemporaryDirectory
|
||||
from ._core._tempfile import TemporaryFile as TemporaryFile
|
||||
from ._core._tempfile import gettempdir as gettempdir
|
||||
from ._core._tempfile import gettempdirb as gettempdirb
|
||||
from ._core._tempfile import mkdtemp as mkdtemp
|
||||
from ._core._tempfile import mkstemp as mkstemp
|
||||
from ._core._testing import TaskInfo as TaskInfo
|
||||
from ._core._testing import get_current_task as get_current_task
|
||||
from ._core._testing import get_running_tasks as get_running_tasks
|
||||
from ._core._testing import wait_all_tasks_blocked as wait_all_tasks_blocked
|
||||
from ._core._typedattr import TypedAttributeProvider as TypedAttributeProvider
|
||||
from ._core._typedattr import TypedAttributeSet as TypedAttributeSet
|
||||
from ._core._typedattr import typed_attribute as typed_attribute
|
||||
|
||||
# Re-export imports so they look like they live directly in this package
|
||||
for __value in list(locals().values()):
|
||||
if getattr(__value, "__module__", "").startswith("anyio."):
|
||||
__value.__module__ = __name__
|
||||
|
||||
|
||||
del __value
|
||||
|
||||
|
||||
def __getattr__(attr: str) -> type[BrokenWorkerInterpreter]:
|
||||
"""Support deprecated aliases."""
|
||||
if attr == "BrokenWorkerIntepreter":
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"The 'BrokenWorkerIntepreter' alias is deprecated, use 'BrokenWorkerInterpreter' instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return BrokenWorkerInterpreter
|
||||
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {attr!r}")
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user