parent
2252be0709
commit
2f3e63ef02
Binary file not shown.
Binary file not shown.
@ -0,0 +1,56 @@
|
|||||||
|
"""adodbapi - A python DB API 2.0 (PEP 249) interface to Microsoft ADO
|
||||||
|
|
||||||
|
Copyright (C) 2002 Henrik Ekelund, version 2.1 by Vernon Cole
|
||||||
|
* http://sourceforge.net/projects/adodbapi
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
if sys.version_info < (3,0): # in Python 2, define all symbols, just like the bad old way
|
||||||
|
from apibase import *
|
||||||
|
VariantConversionMap = MultiMap # old name. Should use apibase.MultiMap
|
||||||
|
from .ado_consts import *
|
||||||
|
_makeByteBuffer = buffer
|
||||||
|
else:
|
||||||
|
# but if the user is running Python 3, then keep the dictionary clean
|
||||||
|
from .apibase import apilevel, threadsafety, paramstyle
|
||||||
|
from .apibase import Warning, Error, InterfaceError, DatabaseError, DataError, OperationalError, IntegrityError
|
||||||
|
from .apibase import InternalError, ProgrammingError, NotSupportedError, FetchFailedError
|
||||||
|
from .apibase import NUMBER, STRING, BINARY, DATETIME, ROWID
|
||||||
|
_makeByteBuffer = bytes
|
||||||
|
|
||||||
|
from .adodbapi import connect, Connection, __version__, dateconverter, Cursor
|
||||||
|
|
||||||
|
def Binary(aString):
|
||||||
|
"""This function constructs an object capable of holding a binary (long) string value. """
|
||||||
|
return _makeByteBuffer(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.
@ -0,0 +1,276 @@
|
|||||||
|
# ADO enumerated constants documented on MSDN:
|
||||||
|
# http://msdn.microsoft.com/en-us/library/ms678353(VS.85).aspx
|
||||||
|
|
||||||
|
# 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 'unknown direction ('+str(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
|
||||||
|
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, 'unknown type ('+str(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
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,70 @@
|
|||||||
|
""" db_print.py -- a simple demo for ADO database reads."""
|
||||||
|
from __future__ import with_statement #needed for Python 2.5
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import adodbapi.ado_consts as adc
|
||||||
|
|
||||||
|
cmd_args = ('proxy_host', 'proxy_port', '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"
|
||||||
|
if 'proxy_host' in kw_args:
|
||||||
|
import adodbapi.remote as db
|
||||||
|
else:
|
||||||
|
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,19 @@
|
|||||||
|
""" 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,38 @@
|
|||||||
|
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,32 @@
|
|||||||
|
from __future__ import with_statement # needed only if running Python 2.5
|
||||||
|
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 (Header1 text, Header2 text, Header3 text, Header4 text, Header5 text)')
|
||||||
|
|
||||||
|
sql = "INSERT INTO SheetOne (Header1, Header2 ,Header3, Header4, Header5) values (?,?,?,?,?)"
|
||||||
|
|
||||||
|
data = (1, 2, 3, 4, 5)
|
||||||
|
crsr.execute(sql, data) # write the first row of data
|
||||||
|
crsr.execute(sql, (6, 7, 8, 9, 10)) # another row of data
|
||||||
|
conn.close()
|
||||||
|
print(('Created spreadsheet=%s worksheet=%s' % (filename, 'SheetOne')))
|
@ -0,0 +1,33 @@
|
|||||||
|
"""is64bit.Python() --> boolean value of detected Python word size. is64bit.os() --> os build version"""
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def Python():
|
||||||
|
if sys.platform == 'cli': #IronPython
|
||||||
|
import System
|
||||||
|
return System.IntPtr.Size == 8
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
return sys.maxsize > 2147483647
|
||||||
|
except AttributeError:
|
||||||
|
return sys.maxint > 2147483647
|
||||||
|
|
||||||
|
def os():
|
||||||
|
import platform
|
||||||
|
pm = platform.machine()
|
||||||
|
if pm != '..' and pm.endswith('64'): # recent Python (not Iron)
|
||||||
|
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,506 @@
|
|||||||
|
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,92 @@
|
|||||||
|
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 either CPython or IronPython.
|
||||||
|
|
||||||
|
Home page: <http://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 Python 2.5+ Python 3.0+ and IronPython 2.6+
|
||||||
|
* Licensed under the LGPL license, which means that it can be used freely even in commercial programs subject to certain restrictions.
|
||||||
|
* Includes SERVER and REMOTE modules so that a Windows proxy can serve ADO databases to a Linux client using PyRO.
|
||||||
|
* 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 2.5 or higher
|
||||||
|
and pywin32 (Mark Hammond's python for windows extensions.)
|
||||||
|
or
|
||||||
|
Iron Python 2.6 or higher. (works in IPy2.0 for all data types except BUFFER)
|
||||||
|
|
||||||
|
Installation:
|
||||||
|
* (C-Python on Windows): Download pywin32 from http://sf.net/projects/pywin32 and install from .msi (adodbapi is included)
|
||||||
|
* ((to use Windows as a server, also download and install Pyro4 (requires Python 2.6 or later))) https://pypi.python.org/pypi/Pyro4
|
||||||
|
* (IronPython on Windows): Download adodbapi from http://sf.net/projects/adodbapi. Unpack the zip.
|
||||||
|
Open a command window as an administrator. CD to the folder containing the unzipped files.
|
||||||
|
Run "setup.py install" using the IronPython of your choice.
|
||||||
|
* (Linux, as a client): download and install from PyPi: "pip install adodbapi Pyro4"
|
||||||
|
|
||||||
|
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_convertion_function
|
||||||
|
............
|
||||||
|
whats 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.
|
||||||
|
|
||||||
|
whats 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]]
|
||||||
|
|
||||||
|
(( More information like this in older_whatsnew.txt ))
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
LGPL, see http://www.opensource.org/licenses/lgpl-license.php
|
||||||
|
|
||||||
|
Documentation
|
||||||
|
-------------
|
||||||
|
Start with:
|
||||||
|
|
||||||
|
http://www.python.org/topics/database/DatabaseAPI-2.0.html
|
||||||
|
read the examples in adodbapi/examples
|
||||||
|
and look at the test cases in adodbapi/test directory.
|
||||||
|
|
||||||
|
Mailing lists
|
||||||
|
-------------
|
||||||
|
The adodbapi mailing lists have been deactivated. Submit comments to the
|
||||||
|
pywin32 or IronPython mailing lists.
|
||||||
|
-- the bug tracker on sourceforge.net/projects/adodbapi will be checked, (infrequently).
|
@ -0,0 +1,14 @@
|
|||||||
|
"""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
|
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
@ -0,0 +1,161 @@
|
|||||||
|
# Configure this to _YOUR_ environment in order to run the testcases.
|
||||||
|
"testADOdbapiConfig.py v 2.6.0.A00"
|
||||||
|
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# #
|
||||||
|
# # 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 sys
|
||||||
|
import random
|
||||||
|
|
||||||
|
import is64bit
|
||||||
|
import setuptestframework
|
||||||
|
if sys.version_info >= (3,0):
|
||||||
|
import tryconnection3 as tryconnection
|
||||||
|
else:
|
||||||
|
import tryconnection2 as tryconnection
|
||||||
|
|
||||||
|
print((sys.version))
|
||||||
|
node = platform.node()
|
||||||
|
try: print(('node=%s: is64bit.os()= %s, is64bit.Python()= %s' % (node, is64bit.os(), is64bit.Python())))
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
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 = "PYFGCRLAOEUIDHTNTQJKXBMWVZ1234567890" # yes, I do use a dvorak keyboard!
|
||||||
|
tmp = ''.join([random.choice(_alphabet) for x in range(9)])
|
||||||
|
mdb_name = 'xx_' + tmp + '.mdb'
|
||||||
|
testfolder = setuptestframework.maketemp()
|
||||||
|
|
||||||
|
if '--package' in sys.argv:
|
||||||
|
pth = setuptestframework.makeadopackage(testfolder)
|
||||||
|
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 # will (hopefully) be imported using the "pth" discovered above
|
||||||
|
|
||||||
|
try:
|
||||||
|
print((adodbapi.version)) # show version
|
||||||
|
except:
|
||||||
|
print('"adodbapi.version" not present or not working.')
|
||||||
|
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
|
||||||
|
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # start your environment setup here v v v
|
||||||
|
SQL_HOST_NODE = 'Vpad'
|
||||||
|
doAllTests = '--all' in sys.argv
|
||||||
|
doAccessTest = not ('--nojet' in sys.argv)
|
||||||
|
doSqlServerTest = node == SQL_HOST_NODE or '--mssql' in sys.argv or doAllTests
|
||||||
|
doMySqlTest = '--mysql' in sys.argv or doAllTests
|
||||||
|
doPostgresTest = '--pg' in sys.argv or doAllTests
|
||||||
|
iterateOverTimeTests = ('--time' in sys.argv or doAllTests) and onWindows
|
||||||
|
|
||||||
|
THE_PROXY_HOST = '25.44.77.176' if node != SQL_HOST_NODE or not onWindows else '::1' # -- change this
|
||||||
|
|
||||||
|
try: #If mx extensions are installed, use mxDateTime
|
||||||
|
import mx.DateTime
|
||||||
|
doMxDateTimeTest=True
|
||||||
|
except:
|
||||||
|
doMxDateTimeTest=False #Requires eGenixMXExtensions
|
||||||
|
|
||||||
|
doTimeTest = True # obsolete python time format
|
||||||
|
|
||||||
|
if doAccessTest:
|
||||||
|
if onWindows and (node == SQL_HOST_NODE or not is64bit.Python()):
|
||||||
|
c = {'mdb': setuptestframework.makemdb(testfolder, mdb_name)}
|
||||||
|
else:
|
||||||
|
c = {'macro_find_temp_test_path' : ['mdb', 'server_test.mdb'],
|
||||||
|
'proxy_host' : THE_PROXY_HOST}
|
||||||
|
|
||||||
|
|
||||||
|
# macro definition for keyword "driver" using macro "is64bit" -- see documentation
|
||||||
|
c['macro_is64bit'] = ['driver', "Microsoft.ACE.OLEDB.12.0", "Microsoft.Jet.OLEDB.4.0"]
|
||||||
|
connStrAccess = "Provider=%(driver)s;Data Source=%(mdb)s"
|
||||||
|
print(' ...Testing ACCESS connection...')
|
||||||
|
doAccessTest, connStrAccess, dbAccessconnect = tryconnection.try_connection(verbose, connStrAccess, 10, **c)
|
||||||
|
|
||||||
|
if doSqlServerTest:
|
||||||
|
c = {'macro_getnode' : ['host', r"%s\SQLExpress"], # name of computer with SQL Server
|
||||||
|
#'host':'25.44.77.176;' # Network Library=dbmssocn',
|
||||||
|
'database': "adotest",
|
||||||
|
'user' : 'adotestuser', # None implies Windows security
|
||||||
|
'password' : "12345678",
|
||||||
|
# macro definition for keyword "security" using macro "auto_security"
|
||||||
|
'macro_auto_security' : 'security',
|
||||||
|
'provider' : 'SQLNCLI11; MARS Connection=True'
|
||||||
|
}
|
||||||
|
connStr = "Provider=%(provider)s; Initial Catalog=%(database)s; Data Source=%(host)s; %(security)s;"
|
||||||
|
|
||||||
|
if node != SQL_HOST_NODE:
|
||||||
|
if THE_PROXY_HOST:
|
||||||
|
c["proxy_host"] = THE_PROXY_HOST # the SQL server runs a proxy for this test
|
||||||
|
else:
|
||||||
|
c["pyro_connection"] = "PYRONAME:ado.connection"
|
||||||
|
print(' ...Testing MS-SQL login...')
|
||||||
|
doSqlServerTest, connStrSQLServer, dbSqlServerconnect = tryconnection.try_connection(verbose, connStr, 30, **c)
|
||||||
|
|
||||||
|
if doMySqlTest:
|
||||||
|
c = {'host' : "25.223.161.222",
|
||||||
|
'database' : 'test',
|
||||||
|
'user' : 'adotest',
|
||||||
|
'password' : '12345678',
|
||||||
|
'driver' : "MySQL ODBC 5.3 Unicode Driver"} # or _driver="MySQL ODBC 3.51 Driver
|
||||||
|
|
||||||
|
if not onWindows:
|
||||||
|
if THE_PROXY_HOST:
|
||||||
|
c["proxy_host"] = THE_PROXY_HOST
|
||||||
|
else:
|
||||||
|
c["pyro_connection"] = "PYRONAME:ado.connection"
|
||||||
|
|
||||||
|
c['macro_is64bit'] = ['provider', 'Provider=MSDASQL;']
|
||||||
|
cs = '%(provider)sDriver={%(driver)s};Server=%(host)s;Port=3306;' + \
|
||||||
|
'Database=%(database)s;user=%(user)s;password=%(password)s;Option=3;'
|
||||||
|
print(' ...Testing MySql login...')
|
||||||
|
doMySqlTest, connStrMySql, dbMySqlconnect = tryconnection.try_connection(verbose, cs, 5, **c)
|
||||||
|
|
||||||
|
if doPostgresTest:
|
||||||
|
_computername = "25.223.161.222"
|
||||||
|
_databasename='adotest'
|
||||||
|
_username = 'adotestuser'
|
||||||
|
_password = '12345678'
|
||||||
|
kws = {'timeout' : 4}
|
||||||
|
kws['macro_is64bit'] = ['prov_drv', 'Provider=MSDASQL;Driver={PostgreSQL Unicode(x64)}',
|
||||||
|
'Driver=PostgreSQL Unicode']
|
||||||
|
if not onWindows:
|
||||||
|
if THE_PROXY_HOST:
|
||||||
|
kws['proxy_host'] = THE_PROXY_HOST
|
||||||
|
else:
|
||||||
|
kws['pyro_connection'] = 'PYRONAME:ado.connection'
|
||||||
|
# get driver from http://www.postgresql.org/ftp/odbc/versions/
|
||||||
|
# test using positional and keyword arguments (bad example for real code)
|
||||||
|
print(' ...Testing PostgreSQL login...')
|
||||||
|
doPostgresTest, connStrPostgres, dbPostgresConnect = tryconnection.try_connection(verbose,
|
||||||
|
'%(prov_drv)s;Server=%(host)s;Database=%(database)s;uid=%(user)s;pwd=%(password)s;',
|
||||||
|
_username, _password, _computername, _databasename, **kws)
|
||||||
|
|
||||||
|
assert doAccessTest or doSqlServerTest or doMySqlTest or doPostgresTest, 'No database engine found for testing'
|
@ -0,0 +1,899 @@
|
|||||||
|
#!/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.14.3 $'[11:-2]
|
||||||
|
__author__ = 'Stuart Bishop <stuart@stuartbishop.net>'
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
import time
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if sys.version[0] >= '3': #python 3.x
|
||||||
|
_BaseException = Exception
|
||||||
|
def _failUnless(self, expr, msg=None):
|
||||||
|
self.assertTrue(expr, msg)
|
||||||
|
else: #python 2.x
|
||||||
|
from exceptions import Exception as _BaseException
|
||||||
|
def _failUnless(self, expr, msg=None):
|
||||||
|
self.failUnless(expr, msg) ## deprecated since Python 2.6
|
||||||
|
|
||||||
|
# set this to "True" to follow API 2.0 to the letter
|
||||||
|
TEST_FOR_NON_IDEMPOTENT_CLOSE = True
|
||||||
|
|
||||||
|
# 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 heirarchy 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
|
||||||
|
#
|
||||||
|
def str2bytes(sval):
|
||||||
|
if sys.version_info < (3,0) and isinstance(sval, str):
|
||||||
|
sval = sval.decode("latin1")
|
||||||
|
return sval.encode("latin1") #python 3 make unicode into bytes
|
||||||
|
|
||||||
|
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 _BaseException:
|
||||||
|
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
|
||||||
|
_failUnless(self, 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
|
||||||
|
_failUnless(self, 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 heirarchy.
|
||||||
|
if sys.version[0] == '3': #under Python 3 StardardError no longer exists
|
||||||
|
self.assertTrue(issubclass(self.driver.Warning,Exception))
|
||||||
|
self.assertTrue(issubclass(self.driver.Error,Exception))
|
||||||
|
else:
|
||||||
|
self.failUnless(issubclass(self.driver.Warning,Exception))
|
||||||
|
self.failUnless(issubclass(self.driver.Error,Exception))
|
||||||
|
|
||||||
|
_failUnless(self,
|
||||||
|
issubclass(self.driver.InterfaceError,self.driver.Error)
|
||||||
|
)
|
||||||
|
_failUnless(self,
|
||||||
|
issubclass(self.driver.DatabaseError,self.driver.Error)
|
||||||
|
)
|
||||||
|
_failUnless(self,
|
||||||
|
issubclass(self.driver.OperationalError,self.driver.Error)
|
||||||
|
)
|
||||||
|
_failUnless(self,
|
||||||
|
issubclass(self.driver.IntegrityError,self.driver.Error)
|
||||||
|
)
|
||||||
|
_failUnless(self,
|
||||||
|
issubclass(self.driver.InternalError,self.driver.Error)
|
||||||
|
)
|
||||||
|
_failUnless(self,
|
||||||
|
issubclass(self.driver.ProgrammingError,self.driver.Error)
|
||||||
|
)
|
||||||
|
_failUnless(self,
|
||||||
|
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
|
||||||
|
_failUnless(self,con.Warning is drv.Warning)
|
||||||
|
_failUnless(self,con.Error is drv.Error)
|
||||||
|
_failUnless(self,con.InterfaceError is drv.InterfaceError)
|
||||||
|
_failUnless(self,con.DatabaseError is drv.DatabaseError)
|
||||||
|
_failUnless(self,con.OperationalError is drv.OperationalError)
|
||||||
|
_failUnless(self,con.IntegrityError is drv.IntegrityError)
|
||||||
|
_failUnless(self,con.InternalError is drv.InternalError)
|
||||||
|
_failUnless(self,con.ProgrammingError is drv.ProgrammingError)
|
||||||
|
_failUnless(self,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)
|
||||||
|
_failUnless(self,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
|
||||||
|
))
|
||||||
|
_failUnless(self,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)
|
||||||
|
_failUnless(self,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)
|
||||||
|
|
||||||
|
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
|
||||||
|
))
|
||||||
|
_failUnless(self,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')
|
||||||
|
_failUnless(self,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 '
|
||||||
|
'incorrectly. Got=%s, Expected=%s' % (repr(res[0][1]), repr(trouble)))
|
||||||
|
self.assertEqual(res[1][1], trouble,
|
||||||
|
'cursor.fetchall retrieved incorrect data, or data inserted '
|
||||||
|
'incorrectly. Got=%s, Expected=%s' % (repr(res[1][1]), repr(trouble)
|
||||||
|
))
|
||||||
|
|
||||||
|
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')
|
||||||
|
_failUnless(self,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'
|
||||||
|
)
|
||||||
|
_failUnless(self,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'
|
||||||
|
)
|
||||||
|
_failUnless(self,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'
|
||||||
|
)
|
||||||
|
_failUnless(self,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)
|
||||||
|
_failUnless(self,cur.rowcount in (-1,6))
|
||||||
|
|
||||||
|
cur.arraysize=6
|
||||||
|
cur.execute('select name from %sbooze' % self.table_prefix)
|
||||||
|
rows = cur.fetchmany() # Should get all rows
|
||||||
|
_failUnless(self,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'
|
||||||
|
)
|
||||||
|
_failUnless(self,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'
|
||||||
|
)
|
||||||
|
_failUnless(self,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()
|
||||||
|
_failUnless(self,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'
|
||||||
|
)
|
||||||
|
_failUnless(self,cur.rowcount in (-1,len(self.samples)))
|
||||||
|
|
||||||
|
self.executeDDL2(cur)
|
||||||
|
cur.execute('select name from %sbarflys' % self.table_prefix)
|
||||||
|
rows = cur.fetchall()
|
||||||
|
_failUnless(self,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()
|
||||||
|
_failUnless(self,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):
|
||||||
|
con = self._connect()
|
||||||
|
try:
|
||||||
|
cur = con.cursor()
|
||||||
|
if not hasattr(cur,'nextset'):
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.executeDDL1(cur)
|
||||||
|
sql=self._populate()
|
||||||
|
for sql in self._populate():
|
||||||
|
cur.execute(sql)
|
||||||
|
|
||||||
|
self.help_nextset_setUp(cur)
|
||||||
|
|
||||||
|
cur.callproc('deleteme')
|
||||||
|
numberofrows=cur.fetchone()
|
||||||
|
assert numberofrows[0]== len(self.samples)
|
||||||
|
assert cur.nextset()
|
||||||
|
names=cur.fetchall()
|
||||||
|
assert len(names) == len(self.samples)
|
||||||
|
s=cur.nextset()
|
||||||
|
assert s == None,'No more return sets, should return None'
|
||||||
|
finally:
|
||||||
|
self.help_nextset_tearDown(cur)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
con.close()
|
||||||
|
|
||||||
|
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()
|
||||||
|
_failUnless(self,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(str2bytes('Something'))
|
||||||
|
b = self.driver.Binary(str2bytes(''))
|
||||||
|
|
||||||
|
def test_STRING(self):
|
||||||
|
_failUnless(self, hasattr(self.driver,'STRING'),
|
||||||
|
'module.STRING must be defined'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_BINARY(self):
|
||||||
|
_failUnless(self, hasattr(self.driver,'BINARY'),
|
||||||
|
'module.BINARY must be defined.'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_NUMBER(self):
|
||||||
|
_failUnless(self, hasattr(self.driver,'NUMBER'),
|
||||||
|
'module.NUMBER must be defined.'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_DATETIME(self):
|
||||||
|
_failUnless(self, hasattr(self.driver,'DATETIME'),
|
||||||
|
'module.DATETIME must be defined.'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_ROWID(self):
|
||||||
|
_failUnless(self, hasattr(self.driver,'ROWID'),
|
||||||
|
'module.ROWID must be defined.'
|
||||||
|
)
|
@ -0,0 +1,33 @@
|
|||||||
|
"""is64bit.Python() --> boolean value of detected Python word size. is64bit.os() --> os build version"""
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def Python():
|
||||||
|
if sys.platform == 'cli': #IronPython
|
||||||
|
import System
|
||||||
|
return System.IntPtr.Size == 8
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
return sys.maxsize > 2147483647
|
||||||
|
except AttributeError:
|
||||||
|
return sys.maxint > 2147483647
|
||||||
|
|
||||||
|
def os():
|
||||||
|
import platform
|
||||||
|
pm = platform.machine()
|
||||||
|
if pm != '..' and pm.endswith('64'): # recent Python (not Iron)
|
||||||
|
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,113 @@
|
|||||||
|
#!/usr/bin/python2
|
||||||
|
# Configure this in order to run the testcases.
|
||||||
|
"setuptestframework.py v 2.5.0.c9"
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
try:
|
||||||
|
OSErrors = (WindowsError, OSError)
|
||||||
|
except NameError: # not running on Windows
|
||||||
|
OSErrors = OSError
|
||||||
|
|
||||||
|
def maketemp():
|
||||||
|
temphome = tempfile.gettempdir()
|
||||||
|
tempdir = os.path.join(temphome, 'adodbapi_test')
|
||||||
|
## try: ## if running simultanous test, don't erase the other thread's work
|
||||||
|
## shutil.rmtree(tempdir) # kill off an old copy
|
||||||
|
## except: pass
|
||||||
|
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(os.path.join(testfolder, 'adodbapi'))
|
||||||
|
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.mkdir(newpackage)
|
||||||
|
except OSErrors:
|
||||||
|
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)
|
||||||
|
if sys.version_info >= (3,0): # only when running Py3.n
|
||||||
|
save = sys.stdout
|
||||||
|
sys.stdout = None
|
||||||
|
from lib2to3.main import main # use 2to3 to make test package
|
||||||
|
main("lib2to3.fixes",args=['-n','-w', newpackage])
|
||||||
|
sys.stdout = save
|
||||||
|
return testfolder
|
||||||
|
else:
|
||||||
|
raise EnvironmentError('Connot 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 not os.path.isfile(_accessdatasource):
|
||||||
|
try:
|
||||||
|
from win32com.client.gencache import EnsureDispatch
|
||||||
|
from win32com.client import constants
|
||||||
|
win32 = True
|
||||||
|
except ImportError: #perhaps we are running IronPython
|
||||||
|
win32 = False #iron Python
|
||||||
|
try:
|
||||||
|
from System import Activator, Type
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Create a brand-new database - what is the story with these?
|
||||||
|
dbe = None
|
||||||
|
for suffix in (".36", ".35", ".30"):
|
||||||
|
try:
|
||||||
|
if win32:
|
||||||
|
dbe = EnsureDispatch("DAO.DBEngine" + suffix)
|
||||||
|
else:
|
||||||
|
type= Type.GetTypeFromProgID("DAO.DBEngine" + suffix)
|
||||||
|
dbe = Activator.CreateInstance(type)
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if dbe:
|
||||||
|
print((' ...Creating ACCESS db at '+_accessdatasource))
|
||||||
|
if win32:
|
||||||
|
workspace = dbe.Workspaces(0)
|
||||||
|
newdb = workspace.CreateDatabase(_accessdatasource,
|
||||||
|
constants.dbLangGeneral,
|
||||||
|
constants.dbEncrypt)
|
||||||
|
else:
|
||||||
|
newdb = dbe.CreateDatabase(_accessdatasource,';LANGID=0x0409;CP=1252;COUNTRY=0')
|
||||||
|
newdb.Close()
|
||||||
|
else:
|
||||||
|
print((' ...copying test ACCESS db to '+_accessdatasource))
|
||||||
|
mdbName = os.path.normpath(os.getcwd() + '/../examples/test.mdb')
|
||||||
|
import shutil
|
||||||
|
shutil.copy(mdbName, _accessdatasource)
|
||||||
|
|
||||||
|
return _accessdatasource
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print('Setting up a Jet database for server to use for remote testing...')
|
||||||
|
temp = maketemp()
|
||||||
|
makemdb(temp, 'server_test.mdb')
|
@ -0,0 +1,181 @@
|
|||||||
|
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 unittest
|
||||||
|
import sys
|
||||||
|
|
||||||
|
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 = None # if None, will use macro to fill in node name
|
||||||
|
instance = r'%s\SQLExpress'
|
||||||
|
conn_kws['name'] = 'adotest'
|
||||||
|
|
||||||
|
if host is None:
|
||||||
|
conn_kws['macro_getnode'] = ['host', instance]
|
||||||
|
else:
|
||||||
|
conn_kws['host'] = host
|
||||||
|
|
||||||
|
conn_kws['provider'] = 'Provider=SQLNCLI11;DataTypeCompatibility=80;MARS Connection=True;'
|
||||||
|
connStr = "%(provider)s; Integrated Security=SSPI; 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)
|
||||||
|
else: # try a remote connection to an SQL server
|
||||||
|
conn_kws['proxy_host'] = '25.44.77.176'
|
||||||
|
import adodbapi.remote
|
||||||
|
db = adodbapi.remote
|
||||||
|
|
||||||
|
print(('Using Connection String like=%s' % connStr))
|
||||||
|
print(('Keywords=%s' % repr(conn_kws)))
|
||||||
|
|
||||||
|
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 testMethodName(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.testMethodName()=='test_callproc':
|
||||||
|
con = self._connect()
|
||||||
|
engine = con.dbms_name
|
||||||
|
## print('Using database Engine=%s' % 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.testMethodName()=='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 == 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,46 @@
|
|||||||
|
# This module may be retired as soon as Python 2.5 support is dropped.
|
||||||
|
#
|
||||||
|
# It exists only to allow trapping exceptions using the "except [exception list], e" format
|
||||||
|
# which is a syntax error in Python 3
|
||||||
|
|
||||||
|
def try_connection(verbose, *args, **kwargs):
|
||||||
|
import adodbapi
|
||||||
|
|
||||||
|
if "proxy_host" in kwargs or 'pyro_connection' in kwargs or 'proxy_host' in args:
|
||||||
|
import adodbapi.remote
|
||||||
|
import Pyro4
|
||||||
|
pyroError = Pyro4.errors.PyroError
|
||||||
|
dbconnect = adodbapi.remote.connect
|
||||||
|
remote = True
|
||||||
|
else:
|
||||||
|
dbconnect = adodbapi.connect
|
||||||
|
pyroError = NotImplementedError # (will not occur)
|
||||||
|
remote = False
|
||||||
|
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, pyroError) as inst:
|
||||||
|
print(inst.args[0]) # should be the error message
|
||||||
|
print('***Failed getting connection using=', repr(args), repr(kwargs))
|
||||||
|
if remote:
|
||||||
|
print('** Is your Python2 ado.connection server running?')
|
||||||
|
print('* Have you run "setuptestframework.py" to create server_test.mdb?')
|
||||||
|
return False, (args, kwargs), None
|
||||||
|
|
||||||
|
if remote:
|
||||||
|
print(" (remote)", end=' ')
|
||||||
|
print(" (successful)")
|
||||||
|
|
||||||
|
return True, (args, kwargs, remote), dbconnect
|
||||||
|
|
||||||
|
def try_operation_with_expected_exception(expected_exceptions, some_function, args, kwargs):
|
||||||
|
try:
|
||||||
|
some_function(*args, **kwargs)
|
||||||
|
except expected_exceptions 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,48 @@
|
|||||||
|
from __future__ import print_function
|
||||||
|
# This module may be retired as soon as Python 2.5 support is dropped.
|
||||||
|
#
|
||||||
|
# It exists only to allow trapping exceptions using the "except [exception list] as e" format
|
||||||
|
# which is a syntax error in Python 2.5
|
||||||
|
|
||||||
|
def try_connection(verbose, *args, **kwargs):
|
||||||
|
import adodbapi
|
||||||
|
|
||||||
|
if "proxy_host" in kwargs or "pyro_connection" in kwargs or "proxy_host" in args:
|
||||||
|
import adodbapi.remote
|
||||||
|
import Pyro4
|
||||||
|
pyroError = Pyro4.errors.PyroError
|
||||||
|
dbconnect = adodbapi.remote.connect
|
||||||
|
remote = True
|
||||||
|
else:
|
||||||
|
dbconnect = adodbapi.connect
|
||||||
|
pyroError = NotImplementedError # (will not occur)
|
||||||
|
remote = False
|
||||||
|
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('***Failed getting connection using=',repr(args),repr(kwargs))
|
||||||
|
if remote:
|
||||||
|
print('** Are you running a *Python3* ado.connection server? **')
|
||||||
|
print('* Have you run "setuptestframework.py" to create server_test.mdb?')
|
||||||
|
return False, (args, kwargs), None
|
||||||
|
|
||||||
|
if remote:
|
||||||
|
print(" (remote)",end="")
|
||||||
|
print(" (successful)")
|
||||||
|
|
||||||
|
return True, (args, kwargs, remote), 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'
|
Binary file not shown.
@ -0,0 +1,7 @@
|
|||||||
|
A Python ISAPI extension. Contributed by Phillip Frantz, and is
|
||||||
|
Copyright 2002-2003 by Blackdog Software Pty Ltd.
|
||||||
|
|
||||||
|
See the 'samples' directory, and particularly samples\README.txt
|
||||||
|
|
||||||
|
You can find documentation in the PyWin32.chm file that comes with pywin32 -
|
||||||
|
you can open this from Pythonwin->Help, or from the start menu.
|
@ -0,0 +1,33 @@
|
|||||||
|
# The Python ISAPI package.
|
||||||
|
|
||||||
|
# Exceptions thrown by the DLL framework.
|
||||||
|
class ISAPIError(Exception):
|
||||||
|
def __init__(self, errno, strerror = None, funcname = None):
|
||||||
|
# named attributes match IOError etc.
|
||||||
|
self.errno = errno
|
||||||
|
self.strerror = strerror
|
||||||
|
self.funcname = funcname
|
||||||
|
Exception.__init__(self, errno, strerror, funcname)
|
||||||
|
def __str__(self):
|
||||||
|
if self.strerror is None:
|
||||||
|
try:
|
||||||
|
import win32api
|
||||||
|
self.strerror = win32api.FormatMessage(self.errno).strip()
|
||||||
|
except:
|
||||||
|
self.strerror = "no error message is available"
|
||||||
|
# str() looks like a win32api error.
|
||||||
|
return str( (self.errno, self.strerror, self.funcname) )
|
||||||
|
|
||||||
|
class FilterError(ISAPIError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ExtensionError(ISAPIError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# A little development aid - a filter or extension callback function can
|
||||||
|
# raise one of these exceptions, and the handler module will be reloaded.
|
||||||
|
# This means you can change your code without restarting IIS.
|
||||||
|
# After a reload, your filter/extension will have the GetFilterVersion/
|
||||||
|
# GetExtensionVersion function called, but with None as the first arg.
|
||||||
|
class InternalReloadException(Exception):
|
||||||
|
pass
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,92 @@
|
|||||||
|
<!-- NOTE: This HTML is displayed inside the CHM file - hence some hrefs
|
||||||
|
will only work in that environment
|
||||||
|
-->
|
||||||
|
<HTML>
|
||||||
|
<BODY>
|
||||||
|
<TITLE>Introduction to Python ISAPI support</TITLE>
|
||||||
|
|
||||||
|
<h2>Introduction to Python ISAPI support</h2>
|
||||||
|
|
||||||
|
<h3>See also</h3>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/isapi_modules.html">The isapi related modules</a>
|
||||||
|
</li>
|
||||||
|
<li><a href="/isapi_objects.html">The isapi related objects</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p><i>Note: if you are viewing this documentation directly from disk,
|
||||||
|
most links in this document will fail - you can also find this document in the
|
||||||
|
CHM file that comes with pywin32, where the links will work</i>
|
||||||
|
|
||||||
|
<h3>Introduction</h3>
|
||||||
|
This documents Python support for hosting ISAPI exensions and filters inside
|
||||||
|
Microsoft Internet Information Server (IIS). It assumes a basic understanding
|
||||||
|
of the ISAPI filter and extension mechanism.
|
||||||
|
<p>
|
||||||
|
In summary, to implement a filter or extension, you provide a Python module
|
||||||
|
which defines a Filter and/or Extension class. Once your class has been
|
||||||
|
loaded, IIS/ISAPI will, via an extension DLL, call methods on your class.
|
||||||
|
<p>
|
||||||
|
A filter and a class instance need only provide 3 methods - for filters they
|
||||||
|
are called <code>GetFilterVersion</code>, <code>HttpFilterProc</code> and
|
||||||
|
<code>TerminateFilter</code>. For extensions they
|
||||||
|
are named <code>GetExtensionVersion</code>, <code>HttpExtensionProc</code> and
|
||||||
|
<code>TerminateExtension</code>. If you are familiar with writing ISAPI
|
||||||
|
extensions in C/C++, these names and their purpose will be familiar.
|
||||||
|
<p>
|
||||||
|
Most of the work is done in the <code>HttpFilterProc</code> and
|
||||||
|
<code>HttpExtensionProc</code> methods. These both take a single
|
||||||
|
parameter - an <a href="/HTTP_FILTER_CONTEXT.html">HTTP_FILTER_CONTEXT</a> and
|
||||||
|
<a href="/EXTENSION_CONTROL_BLOCK.html">EXTENSION_CONTROL_BLOCK</a>
|
||||||
|
object respectively.
|
||||||
|
<p>
|
||||||
|
In addition to these components, there is an 'isapi' package, containing
|
||||||
|
support facilities (base-classes, exceptions, etc) which can be leveraged
|
||||||
|
by the extension.
|
||||||
|
|
||||||
|
<h4>Base classes</h4>
|
||||||
|
There are a number of base classes provided to make writing extensions a little
|
||||||
|
simpler. Of particular note is <code>isapi.threaded_extension.ThreadPoolExtension</code>.
|
||||||
|
This implements a thread-pool and informs IIS that the request is progressing
|
||||||
|
in the background. Your sub-class need only provide a <code>Dispatch</code>
|
||||||
|
method, which is called on one of the worker threads rather than the thread
|
||||||
|
that the request came in on.
|
||||||
|
<p>
|
||||||
|
There is base-class for a filter in <code>isapi.simple</code>, but there is no
|
||||||
|
equivilent threaded filter - filters work under a different model, where
|
||||||
|
background processing is not possible.
|
||||||
|
<h4>Samples</h4>
|
||||||
|
Please see the <code>isapi/samples</code> directory for some sample filters
|
||||||
|
and extensions.
|
||||||
|
|
||||||
|
<H3>Implementation</H3>
|
||||||
|
A Python ISAPI filter extension consists of 2 main components:
|
||||||
|
<UL>
|
||||||
|
<LI>A DLL used by ISAPI to interface with Python.</LI>
|
||||||
|
<LI>A Python script used by that DLL to implement the filter or extension
|
||||||
|
functionality</LI>
|
||||||
|
</UL>
|
||||||
|
|
||||||
|
<h4>Extension DLL</h4>
|
||||||
|
The DLL is usually managed automatically by the isapi.install module. As the
|
||||||
|
Python script for the extension is installed, a generic DLL provided with
|
||||||
|
the isapi package is installed next to the script, and IIS configured to
|
||||||
|
use this DLL.
|
||||||
|
<p>
|
||||||
|
The name of the DLL always has the same base name as the Python script, but
|
||||||
|
with a leading underscore (_), and an extension of .dll. For example, the
|
||||||
|
sample "redirector.py" will, when installed, have "_redirector.dll" created
|
||||||
|
in the same directory.
|
||||||
|
<p/>
|
||||||
|
The Python script may provide 2 entry points - methods named __FilterFactory__
|
||||||
|
and __ExtensionFactory__, both taking no arguments and returning a filter or
|
||||||
|
extension object.
|
||||||
|
|
||||||
|
<h3>Using py2exe and the isapi package</h3>
|
||||||
|
You can instruct py2exe to create a 'frozen' Python ISAPI filter/extension.
|
||||||
|
In this case, py2exe will create a package with everything you need in one
|
||||||
|
directory, and the Python source file embedded in the .zip file.
|
||||||
|
<p>
|
||||||
|
In general, you will want to build a seperate installation executable along
|
||||||
|
with the ISAPI extension. This executable will be built from the same script.
|
||||||
|
See the ISAPI sample in the py2exe distribution.
|
@ -0,0 +1,730 @@
|
|||||||
|
"""Installation utilities for Python ISAPI filters and extensions."""
|
||||||
|
|
||||||
|
# this code adapted from "Tomcat JK2 ISAPI redirector", part of Apache
|
||||||
|
# Created July 2004, Mark Hammond.
|
||||||
|
import sys, os, imp, shutil, stat
|
||||||
|
import operator
|
||||||
|
from win32com.client import GetObject, Dispatch
|
||||||
|
from win32com.client.gencache import EnsureModule, EnsureDispatch
|
||||||
|
import win32api
|
||||||
|
import pythoncom
|
||||||
|
import winerror
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
_APP_INPROC = 0
|
||||||
|
_APP_OUTPROC = 1
|
||||||
|
_APP_POOLED = 2
|
||||||
|
_IIS_OBJECT = "IIS://LocalHost/W3SVC"
|
||||||
|
_IIS_SERVER = "IIsWebServer"
|
||||||
|
_IIS_WEBDIR = "IIsWebDirectory"
|
||||||
|
_IIS_WEBVIRTUALDIR = "IIsWebVirtualDir"
|
||||||
|
_IIS_FILTERS = "IIsFilters"
|
||||||
|
_IIS_FILTER = "IIsFilter"
|
||||||
|
|
||||||
|
_DEFAULT_SERVER_NAME = "Default Web Site"
|
||||||
|
_DEFAULT_HEADERS = "X-Powered-By: Python"
|
||||||
|
_DEFAULT_PROTECTION = _APP_POOLED
|
||||||
|
|
||||||
|
# Default is for 'execute' only access - ie, only the extension
|
||||||
|
# can be used. This can be overridden via your install script.
|
||||||
|
_DEFAULT_ACCESS_EXECUTE = True
|
||||||
|
_DEFAULT_ACCESS_READ = False
|
||||||
|
_DEFAULT_ACCESS_WRITE = False
|
||||||
|
_DEFAULT_ACCESS_SCRIPT = False
|
||||||
|
_DEFAULT_CONTENT_INDEXED = False
|
||||||
|
_DEFAULT_ENABLE_DIR_BROWSING = False
|
||||||
|
_DEFAULT_ENABLE_DEFAULT_DOC = False
|
||||||
|
|
||||||
|
_extensions = [ext for ext, _, _ in imp.get_suffixes()]
|
||||||
|
is_debug_build = '_d.pyd' in _extensions
|
||||||
|
|
||||||
|
this_dir = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
class FilterParameters:
|
||||||
|
Name = None
|
||||||
|
Description = None
|
||||||
|
Path = None
|
||||||
|
Server = None
|
||||||
|
# Params that control if/how AddExtensionFile is called.
|
||||||
|
AddExtensionFile = True
|
||||||
|
AddExtensionFile_Enabled = True
|
||||||
|
AddExtensionFile_GroupID = None # defaults to Name
|
||||||
|
AddExtensionFile_CanDelete = True
|
||||||
|
AddExtensionFile_Description = None # defaults to Description.
|
||||||
|
|
||||||
|
def __init__(self, **kw):
|
||||||
|
self.__dict__.update(kw)
|
||||||
|
|
||||||
|
class VirtualDirParameters:
|
||||||
|
Name = None # Must be provided.
|
||||||
|
Description = None # defaults to Name
|
||||||
|
AppProtection = _DEFAULT_PROTECTION
|
||||||
|
Headers = _DEFAULT_HEADERS
|
||||||
|
Path = None # defaults to WWW root.
|
||||||
|
Type = _IIS_WEBVIRTUALDIR
|
||||||
|
AccessExecute = _DEFAULT_ACCESS_EXECUTE
|
||||||
|
AccessRead = _DEFAULT_ACCESS_READ
|
||||||
|
AccessWrite = _DEFAULT_ACCESS_WRITE
|
||||||
|
AccessScript = _DEFAULT_ACCESS_SCRIPT
|
||||||
|
ContentIndexed = _DEFAULT_CONTENT_INDEXED
|
||||||
|
EnableDirBrowsing = _DEFAULT_ENABLE_DIR_BROWSING
|
||||||
|
EnableDefaultDoc = _DEFAULT_ENABLE_DEFAULT_DOC
|
||||||
|
DefaultDoc = None # Only set in IIS if not None
|
||||||
|
ScriptMaps = []
|
||||||
|
ScriptMapUpdate = "end" # can be 'start', 'end', 'replace'
|
||||||
|
Server = None
|
||||||
|
|
||||||
|
def __init__(self, **kw):
|
||||||
|
self.__dict__.update(kw)
|
||||||
|
|
||||||
|
def is_root(self):
|
||||||
|
"This virtual directory is a root directory if parent and name are blank"
|
||||||
|
parent, name = self.split_path()
|
||||||
|
return not parent and not name
|
||||||
|
|
||||||
|
def split_path(self):
|
||||||
|
return split_path(self.Name)
|
||||||
|
|
||||||
|
class ScriptMapParams:
|
||||||
|
Extension = None
|
||||||
|
Module = None
|
||||||
|
Flags = 5
|
||||||
|
Verbs = ""
|
||||||
|
# Params that control if/how AddExtensionFile is called.
|
||||||
|
AddExtensionFile = True
|
||||||
|
AddExtensionFile_Enabled = True
|
||||||
|
AddExtensionFile_GroupID = None # defaults to Name
|
||||||
|
AddExtensionFile_CanDelete = True
|
||||||
|
AddExtensionFile_Description = None # defaults to Description.
|
||||||
|
def __init__(self, **kw):
|
||||||
|
self.__dict__.update(kw)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"Format this parameter suitable for IIS"
|
||||||
|
items = [self.Extension, self.Module, self.Flags]
|
||||||
|
# IIS gets upset if there is a trailing verb comma, but no verbs
|
||||||
|
if self.Verbs:
|
||||||
|
items.append(self.Verbs)
|
||||||
|
items = [str(item) for item in items]
|
||||||
|
return ','.join(items)
|
||||||
|
|
||||||
|
class ISAPIParameters:
|
||||||
|
ServerName = _DEFAULT_SERVER_NAME
|
||||||
|
# Description = None
|
||||||
|
Filters = []
|
||||||
|
VirtualDirs = []
|
||||||
|
def __init__(self, **kw):
|
||||||
|
self.__dict__.update(kw)
|
||||||
|
|
||||||
|
verbose = 1 # The level - 0 is quiet.
|
||||||
|
def log(level, what):
|
||||||
|
if verbose >= level:
|
||||||
|
print(what)
|
||||||
|
|
||||||
|
# Convert an ADSI COM exception to the Win32 error code embedded in it.
|
||||||
|
def _GetWin32ErrorCode(com_exc):
|
||||||
|
hr = com_exc.hresult
|
||||||
|
# If we have more details in the 'excepinfo' struct, use it.
|
||||||
|
if com_exc.excepinfo:
|
||||||
|
hr = com_exc.excepinfo[-1]
|
||||||
|
if winerror.HRESULT_FACILITY(hr) != winerror.FACILITY_WIN32:
|
||||||
|
raise
|
||||||
|
return winerror.SCODE_CODE(hr)
|
||||||
|
|
||||||
|
class InstallationError(Exception): pass
|
||||||
|
class ItemNotFound(InstallationError): pass
|
||||||
|
class ConfigurationError(InstallationError): pass
|
||||||
|
|
||||||
|
def FindPath(options, server, name):
|
||||||
|
if name.lower().startswith("iis://"):
|
||||||
|
return name
|
||||||
|
else:
|
||||||
|
if name and name[0] != "/":
|
||||||
|
name = "/"+name
|
||||||
|
return FindWebServer(options, server)+"/ROOT"+name
|
||||||
|
|
||||||
|
def LocateWebServerPath(description):
|
||||||
|
"""
|
||||||
|
Find an IIS web server whose name or comment matches the provided
|
||||||
|
description (case-insensitive).
|
||||||
|
|
||||||
|
>>> LocateWebServerPath('Default Web Site') # doctest: +SKIP
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
>>> LocateWebServerPath('1') #doctest: +SKIP
|
||||||
|
"""
|
||||||
|
assert len(description) >= 1, "Server name or comment is required"
|
||||||
|
iis = GetObject(_IIS_OBJECT)
|
||||||
|
description = description.lower().strip()
|
||||||
|
for site in iis:
|
||||||
|
# Name is generally a number, but no need to assume that.
|
||||||
|
site_attributes = [getattr(site, attr, "").lower().strip()
|
||||||
|
for attr in ("Name", "ServerComment")]
|
||||||
|
if description in site_attributes:
|
||||||
|
return site.AdsPath
|
||||||
|
msg = "No web sites match the description '%s'" % description
|
||||||
|
raise ItemNotFound(msg)
|
||||||
|
|
||||||
|
def GetWebServer(description = None):
|
||||||
|
"""
|
||||||
|
Load the web server instance (COM object) for a given instance
|
||||||
|
or description.
|
||||||
|
If None is specified, the default website is retrieved (indicated
|
||||||
|
by the identifier 1.
|
||||||
|
"""
|
||||||
|
description = description or "1"
|
||||||
|
path = LocateWebServerPath(description)
|
||||||
|
server = LoadWebServer(path)
|
||||||
|
return server
|
||||||
|
|
||||||
|
def LoadWebServer(path):
|
||||||
|
try:
|
||||||
|
server = GetObject(path)
|
||||||
|
except pythoncom.com_error as details:
|
||||||
|
msg = details.strerror
|
||||||
|
if exc.excepinfo and exc.excepinfo[2]:
|
||||||
|
msg = exc.excepinfo[2]
|
||||||
|
msg = "WebServer %s: %s" % (path, msg)
|
||||||
|
raise ItemNotFound(msg)
|
||||||
|
return server
|
||||||
|
|
||||||
|
def FindWebServer(options, server_desc):
|
||||||
|
"""
|
||||||
|
Legacy function to allow options to define a .server property
|
||||||
|
to override the other parameter. Use GetWebServer instead.
|
||||||
|
"""
|
||||||
|
# options takes precedence
|
||||||
|
server_desc = options.server or server_desc
|
||||||
|
# make sure server_desc is unicode (could be mbcs if passed in
|
||||||
|
# sys.argv).
|
||||||
|
if server_desc and not isinstance(server_desc, str):
|
||||||
|
server_desc = server_desc.decode('mbcs')
|
||||||
|
|
||||||
|
# get the server (if server_desc is None, the default site is acquired)
|
||||||
|
server = GetWebServer(server_desc)
|
||||||
|
return server.adsPath
|
||||||
|
|
||||||
|
def split_path(path):
|
||||||
|
"""
|
||||||
|
Get the parent path and basename.
|
||||||
|
|
||||||
|
>>> split_path('/')
|
||||||
|
['', '']
|
||||||
|
|
||||||
|
>>> split_path('')
|
||||||
|
['', '']
|
||||||
|
|
||||||
|
>>> split_path('foo')
|
||||||
|
['', 'foo']
|
||||||
|
|
||||||
|
>>> split_path('/foo')
|
||||||
|
['', 'foo']
|
||||||
|
|
||||||
|
>>> split_path('/foo/bar')
|
||||||
|
['/foo', 'bar']
|
||||||
|
|
||||||
|
>>> split_path('foo/bar')
|
||||||
|
['/foo', 'bar']
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not path.startswith('/'): path = '/' + path
|
||||||
|
return path.rsplit('/', 1)
|
||||||
|
|
||||||
|
def _CreateDirectory(iis_dir, name, params):
|
||||||
|
# We used to go to lengths to keep an existing virtual directory
|
||||||
|
# in place. However, in some cases the existing directories got
|
||||||
|
# into a bad state, and an update failed to get them working.
|
||||||
|
# So we nuke it first. If this is a problem, we could consider adding
|
||||||
|
# a --keep-existing option.
|
||||||
|
try:
|
||||||
|
# Also seen the Class change to a generic IISObject - so nuke
|
||||||
|
# *any* existing object, regardless of Class
|
||||||
|
assert name.strip("/"), "mustn't delete the root!"
|
||||||
|
iis_dir.Delete('', name)
|
||||||
|
log(2, "Deleted old directory '%s'" % (name,))
|
||||||
|
except pythoncom.com_error:
|
||||||
|
pass
|
||||||
|
|
||||||
|
newDir = iis_dir.Create(params.Type, name)
|
||||||
|
log(2, "Creating new directory '%s' in %s..." % (name,iis_dir.Name))
|
||||||
|
|
||||||
|
friendly = params.Description or params.Name
|
||||||
|
newDir.AppFriendlyName = friendly
|
||||||
|
|
||||||
|
# Note that the new directory won't be visible in the IIS UI
|
||||||
|
# unless the directory exists on the filesystem.
|
||||||
|
try:
|
||||||
|
path = params.Path or iis_dir.Path
|
||||||
|
newDir.Path = path
|
||||||
|
except AttributeError:
|
||||||
|
# If params.Type is IIS_WEBDIRECTORY, an exception is thrown
|
||||||
|
pass
|
||||||
|
newDir.AppCreate2(params.AppProtection)
|
||||||
|
# XXX - note that these Headers only work in IIS6 and earlier. IIS7
|
||||||
|
# only supports them on the w3svc node - not even on individial sites,
|
||||||
|
# let alone individual extensions in the site!
|
||||||
|
if params.Headers:
|
||||||
|
newDir.HttpCustomHeaders = params.Headers
|
||||||
|
|
||||||
|
log(2, "Setting directory options...")
|
||||||
|
newDir.AccessExecute = params.AccessExecute
|
||||||
|
newDir.AccessRead = params.AccessRead
|
||||||
|
newDir.AccessWrite = params.AccessWrite
|
||||||
|
newDir.AccessScript = params.AccessScript
|
||||||
|
newDir.ContentIndexed = params.ContentIndexed
|
||||||
|
newDir.EnableDirBrowsing = params.EnableDirBrowsing
|
||||||
|
newDir.EnableDefaultDoc = params.EnableDefaultDoc
|
||||||
|
if params.DefaultDoc is not None:
|
||||||
|
newDir.DefaultDoc = params.DefaultDoc
|
||||||
|
newDir.SetInfo()
|
||||||
|
return newDir
|
||||||
|
|
||||||
|
|
||||||
|
def CreateDirectory(params, options):
|
||||||
|
_CallHook(params, "PreInstall", options)
|
||||||
|
if not params.Name:
|
||||||
|
raise ConfigurationError("No Name param")
|
||||||
|
parent, name = params.split_path()
|
||||||
|
target_dir = GetObject(FindPath(options, params.Server, parent))
|
||||||
|
|
||||||
|
if not params.is_root():
|
||||||
|
target_dir = _CreateDirectory(target_dir, name, params)
|
||||||
|
|
||||||
|
AssignScriptMaps(params.ScriptMaps, target_dir, params.ScriptMapUpdate)
|
||||||
|
|
||||||
|
_CallHook(params, "PostInstall", options, target_dir)
|
||||||
|
log(1, "Configured Virtual Directory: %s" % (params.Name,))
|
||||||
|
return target_dir
|
||||||
|
|
||||||
|
def AssignScriptMaps(script_maps, target, update='replace'):
|
||||||
|
"""Updates IIS with the supplied script map information.
|
||||||
|
|
||||||
|
script_maps is a list of ScriptMapParameter objects
|
||||||
|
|
||||||
|
target is an IIS Virtual Directory to assign the script maps to
|
||||||
|
|
||||||
|
update is a string indicating how to update the maps, one of ('start',
|
||||||
|
'end', or 'replace')
|
||||||
|
"""
|
||||||
|
# determine which function to use to assign script maps
|
||||||
|
script_map_func = '_AssignScriptMaps' + update.capitalize()
|
||||||
|
try:
|
||||||
|
script_map_func = eval(script_map_func)
|
||||||
|
except NameError:
|
||||||
|
msg = "Unknown ScriptMapUpdate option '%s'" % update
|
||||||
|
raise ConfigurationError(msg)
|
||||||
|
# use the str method to format the script maps for IIS
|
||||||
|
script_maps = [str(s) for s in script_maps]
|
||||||
|
# call the correct function
|
||||||
|
script_map_func(target, script_maps)
|
||||||
|
target.SetInfo()
|
||||||
|
|
||||||
|
def get_unique_items(sequence, reference):
|
||||||
|
"Return items in sequence that can't be found in reference."
|
||||||
|
return tuple([item for item in sequence if item not in reference])
|
||||||
|
|
||||||
|
def _AssignScriptMapsReplace(target, script_maps):
|
||||||
|
target.ScriptMaps = script_maps
|
||||||
|
|
||||||
|
def _AssignScriptMapsEnd(target, script_maps):
|
||||||
|
unique_new_maps = get_unique_items(script_maps, target.ScriptMaps)
|
||||||
|
target.ScriptMaps = target.ScriptMaps + unique_new_maps
|
||||||
|
|
||||||
|
def _AssignScriptMapsStart(target, script_maps):
|
||||||
|
unique_new_maps = get_unique_items(script_maps, target.ScriptMaps)
|
||||||
|
target.ScriptMaps = unique_new_maps + target.ScriptMaps
|
||||||
|
|
||||||
|
def CreateISAPIFilter(filterParams, options):
|
||||||
|
server = FindWebServer(options, filterParams.Server)
|
||||||
|
_CallHook(filterParams, "PreInstall", options)
|
||||||
|
try:
|
||||||
|
filters = GetObject(server+"/Filters")
|
||||||
|
except pythoncom.com_error as exc:
|
||||||
|
# Brand new sites don't have the '/Filters' collection - create it.
|
||||||
|
# Any errors other than 'not found' we shouldn't ignore.
|
||||||
|
if winerror.HRESULT_FACILITY(exc.hresult) != winerror.FACILITY_WIN32 or \
|
||||||
|
winerror.HRESULT_CODE(exc.hresult) != winerror.ERROR_PATH_NOT_FOUND:
|
||||||
|
raise
|
||||||
|
server_ob = GetObject(server)
|
||||||
|
filters = server_ob.Create(_IIS_FILTERS, "Filters")
|
||||||
|
filters.FilterLoadOrder = ""
|
||||||
|
filters.SetInfo()
|
||||||
|
|
||||||
|
# As for VirtualDir, delete an existing one.
|
||||||
|
assert filterParams.Name.strip("/"), "mustn't delete the root!"
|
||||||
|
try:
|
||||||
|
filters.Delete(_IIS_FILTER, filterParams.Name)
|
||||||
|
log(2, "Deleted old filter '%s'" % (filterParams.Name,))
|
||||||
|
except pythoncom.com_error:
|
||||||
|
pass
|
||||||
|
newFilter = filters.Create(_IIS_FILTER, filterParams.Name)
|
||||||
|
log(2, "Created new ISAPI filter...")
|
||||||
|
assert os.path.isfile(filterParams.Path)
|
||||||
|
newFilter.FilterPath = filterParams.Path
|
||||||
|
newFilter.FilterDescription = filterParams.Description
|
||||||
|
newFilter.SetInfo()
|
||||||
|
load_order = [b.strip() for b in filters.FilterLoadOrder.split(",") if b]
|
||||||
|
if filterParams.Name not in load_order:
|
||||||
|
load_order.append(filterParams.Name)
|
||||||
|
filters.FilterLoadOrder = ",".join(load_order)
|
||||||
|
filters.SetInfo()
|
||||||
|
_CallHook(filterParams, "PostInstall", options, newFilter)
|
||||||
|
log (1, "Configured Filter: %s" % (filterParams.Name,))
|
||||||
|
return newFilter
|
||||||
|
|
||||||
|
def DeleteISAPIFilter(filterParams, options):
|
||||||
|
_CallHook(filterParams, "PreRemove", options)
|
||||||
|
server = FindWebServer(options, filterParams.Server)
|
||||||
|
ob_path = server+"/Filters"
|
||||||
|
try:
|
||||||
|
filters = GetObject(ob_path)
|
||||||
|
except pythoncom.com_error as details:
|
||||||
|
# failure to open the filters just means a totally clean IIS install
|
||||||
|
# (IIS5 at least has no 'Filters' key when freshly installed).
|
||||||
|
log(2, "ISAPI filter path '%s' did not exist." % (ob_path,))
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
assert filterParams.Name.strip("/"), "mustn't delete the root!"
|
||||||
|
filters.Delete(_IIS_FILTER, filterParams.Name)
|
||||||
|
log(2, "Deleted ISAPI filter '%s'" % (filterParams.Name,))
|
||||||
|
except pythoncom.com_error as details:
|
||||||
|
rc = _GetWin32ErrorCode(details)
|
||||||
|
if rc != winerror.ERROR_PATH_NOT_FOUND:
|
||||||
|
raise
|
||||||
|
log(2, "ISAPI filter '%s' did not exist." % (filterParams.Name,))
|
||||||
|
# Remove from the load order
|
||||||
|
load_order = [b.strip() for b in filters.FilterLoadOrder.split(",") if b]
|
||||||
|
if filterParams.Name in load_order:
|
||||||
|
load_order.remove(filterParams.Name)
|
||||||
|
filters.FilterLoadOrder = ",".join(load_order)
|
||||||
|
filters.SetInfo()
|
||||||
|
_CallHook(filterParams, "PostRemove", options)
|
||||||
|
log (1, "Deleted Filter: %s" % (filterParams.Name,))
|
||||||
|
|
||||||
|
def _AddExtensionFile(module, def_groupid, def_desc, params, options):
|
||||||
|
group_id = params.AddExtensionFile_GroupID or def_groupid
|
||||||
|
desc = params.AddExtensionFile_Description or def_desc
|
||||||
|
try:
|
||||||
|
ob = GetObject(_IIS_OBJECT)
|
||||||
|
ob.AddExtensionFile(module,
|
||||||
|
params.AddExtensionFile_Enabled,
|
||||||
|
group_id,
|
||||||
|
params.AddExtensionFile_CanDelete,
|
||||||
|
desc)
|
||||||
|
log(2, "Added extension file '%s' (%s)" % (module, desc))
|
||||||
|
except (pythoncom.com_error, AttributeError) as details:
|
||||||
|
# IIS5 always fails. Probably should upgrade this to
|
||||||
|
# complain more loudly if IIS6 fails.
|
||||||
|
log(2, "Failed to add extension file '%s': %s" % (module, details))
|
||||||
|
|
||||||
|
def AddExtensionFiles(params, options):
|
||||||
|
"""Register the modules used by the filters/extensions as a trusted
|
||||||
|
'extension module' - required by the default IIS6 security settings."""
|
||||||
|
# Add each module only once.
|
||||||
|
added = {}
|
||||||
|
for vd in params.VirtualDirs:
|
||||||
|
for smp in vd.ScriptMaps:
|
||||||
|
if smp.Module not in added and smp.AddExtensionFile:
|
||||||
|
_AddExtensionFile(smp.Module, vd.Name, vd.Description, smp,
|
||||||
|
options)
|
||||||
|
added[smp.Module] = True
|
||||||
|
|
||||||
|
for fd in params.Filters:
|
||||||
|
if fd.Path not in added and fd.AddExtensionFile:
|
||||||
|
_AddExtensionFile(fd.Path, fd.Name, fd.Description, fd, options)
|
||||||
|
added[fd.Path] = True
|
||||||
|
|
||||||
|
def _DeleteExtensionFileRecord(module, options):
|
||||||
|
try:
|
||||||
|
ob = GetObject(_IIS_OBJECT)
|
||||||
|
ob.DeleteExtensionFileRecord(module)
|
||||||
|
log(2, "Deleted extension file record for '%s'" % module)
|
||||||
|
except (pythoncom.com_error, AttributeError) as details:
|
||||||
|
log(2, "Failed to remove extension file '%s': %s" % (module, details))
|
||||||
|
|
||||||
|
def DeleteExtensionFileRecords(params, options):
|
||||||
|
deleted = {} # only remove each .dll once.
|
||||||
|
for vd in params.VirtualDirs:
|
||||||
|
for smp in vd.ScriptMaps:
|
||||||
|
if smp.Module not in deleted and smp.AddExtensionFile:
|
||||||
|
_DeleteExtensionFileRecord(smp.Module, options)
|
||||||
|
deleted[smp.Module] = True
|
||||||
|
|
||||||
|
for filter_def in params.Filters:
|
||||||
|
if filter_def.Path not in deleted and filter_def.AddExtensionFile:
|
||||||
|
_DeleteExtensionFileRecord(filter_def.Path, options)
|
||||||
|
deleted[filter_def.Path] = True
|
||||||
|
|
||||||
|
def CheckLoaderModule(dll_name):
|
||||||
|
suffix = ""
|
||||||
|
if is_debug_build: suffix = "_d"
|
||||||
|
template = os.path.join(this_dir,
|
||||||
|
"PyISAPI_loader" + suffix + ".dll")
|
||||||
|
if not os.path.isfile(template):
|
||||||
|
raise ConfigurationError(
|
||||||
|
"Template loader '%s' does not exist" % (template,))
|
||||||
|
# We can't do a simple "is newer" check, as the DLL is specific to the
|
||||||
|
# Python version. So we check the date-time and size are identical,
|
||||||
|
# and skip the copy in that case.
|
||||||
|
src_stat = os.stat(template)
|
||||||
|
try:
|
||||||
|
dest_stat = os.stat(dll_name)
|
||||||
|
except os.error:
|
||||||
|
same = 0
|
||||||
|
else:
|
||||||
|
same = src_stat[stat.ST_SIZE]==dest_stat[stat.ST_SIZE] and \
|
||||||
|
src_stat[stat.ST_MTIME]==dest_stat[stat.ST_MTIME]
|
||||||
|
if not same:
|
||||||
|
log(2, "Updating %s->%s" % (template, dll_name))
|
||||||
|
shutil.copyfile(template, dll_name)
|
||||||
|
shutil.copystat(template, dll_name)
|
||||||
|
else:
|
||||||
|
log(2, "%s is up to date." % (dll_name,))
|
||||||
|
|
||||||
|
def _CallHook(ob, hook_name, options, *extra_args):
|
||||||
|
func = getattr(ob, hook_name, None)
|
||||||
|
if func is not None:
|
||||||
|
args = (ob,options) + extra_args
|
||||||
|
func(*args)
|
||||||
|
|
||||||
|
def Install(params, options):
|
||||||
|
_CallHook(params, "PreInstall", options)
|
||||||
|
for vd in params.VirtualDirs:
|
||||||
|
CreateDirectory(vd, options)
|
||||||
|
|
||||||
|
for filter_def in params.Filters:
|
||||||
|
CreateISAPIFilter(filter_def, options)
|
||||||
|
|
||||||
|
AddExtensionFiles(params, options)
|
||||||
|
|
||||||
|
_CallHook(params, "PostInstall", options)
|
||||||
|
|
||||||
|
def RemoveDirectory(params, options):
|
||||||
|
if params.is_root():
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
directory = GetObject(FindPath(options, params.Server, params.Name))
|
||||||
|
except pythoncom.com_error as details:
|
||||||
|
rc = _GetWin32ErrorCode(details)
|
||||||
|
if rc != winerror.ERROR_PATH_NOT_FOUND:
|
||||||
|
raise
|
||||||
|
log(2, "VirtualDirectory '%s' did not exist" % params.Name)
|
||||||
|
directory = None
|
||||||
|
if directory is not None:
|
||||||
|
# Be robust should IIS get upset about unloading.
|
||||||
|
try:
|
||||||
|
directory.AppUnLoad()
|
||||||
|
except:
|
||||||
|
exc_val = sys.exc_info()[1]
|
||||||
|
log(2, "AppUnLoad() for %s failed: %s" % (params.Name, exc_val))
|
||||||
|
# Continue trying to delete it.
|
||||||
|
try:
|
||||||
|
parent = GetObject(directory.Parent)
|
||||||
|
parent.Delete(directory.Class, directory.Name)
|
||||||
|
log (1, "Deleted Virtual Directory: %s" % (params.Name,))
|
||||||
|
except:
|
||||||
|
exc_val = sys.exc_info()[1]
|
||||||
|
log(1, "Failed to remove directory %s: %s" % (params.Name, exc_val))
|
||||||
|
|
||||||
|
def RemoveScriptMaps(vd_params, options):
|
||||||
|
"Remove script maps from the already installed virtual directory"
|
||||||
|
parent, name = vd_params.split_path()
|
||||||
|
target_dir = GetObject(FindPath(options, vd_params.Server, parent))
|
||||||
|
installed_maps = list(target_dir.ScriptMaps)
|
||||||
|
for _map in map(str, vd_params.ScriptMaps):
|
||||||
|
if _map in installed_maps:
|
||||||
|
installed_maps.remove(_map)
|
||||||
|
target_dir.ScriptMaps = installed_maps
|
||||||
|
target_dir.SetInfo()
|
||||||
|
|
||||||
|
def Uninstall(params, options):
|
||||||
|
_CallHook(params, "PreRemove", options)
|
||||||
|
|
||||||
|
DeleteExtensionFileRecords(params, options)
|
||||||
|
|
||||||
|
for vd in params.VirtualDirs:
|
||||||
|
_CallHook(vd, "PreRemove", options)
|
||||||
|
|
||||||
|
RemoveDirectory(vd, options)
|
||||||
|
if vd.is_root():
|
||||||
|
# if this is installed to the root virtual directory, we can't delete it
|
||||||
|
# so remove the script maps.
|
||||||
|
RemoveScriptMaps(vd, options)
|
||||||
|
|
||||||
|
_CallHook(vd, "PostRemove", options)
|
||||||
|
|
||||||
|
for filter_def in params.Filters:
|
||||||
|
DeleteISAPIFilter(filter_def, options)
|
||||||
|
_CallHook(params, "PostRemove", options)
|
||||||
|
|
||||||
|
# Patch up any missing module names in the params, replacing them with
|
||||||
|
# the DLL name that hosts this extension/filter.
|
||||||
|
def _PatchParamsModule(params, dll_name, file_must_exist = True):
|
||||||
|
if file_must_exist:
|
||||||
|
if not os.path.isfile(dll_name):
|
||||||
|
raise ConfigurationError("%s does not exist" % (dll_name,))
|
||||||
|
|
||||||
|
# Patch up all references to the DLL.
|
||||||
|
for f in params.Filters:
|
||||||
|
if f.Path is None: f.Path = dll_name
|
||||||
|
for d in params.VirtualDirs:
|
||||||
|
for sm in d.ScriptMaps:
|
||||||
|
if sm.Module is None: sm.Module = dll_name
|
||||||
|
|
||||||
|
def GetLoaderModuleName(mod_name, check_module = None):
|
||||||
|
# find the name of the DLL hosting us.
|
||||||
|
# By default, this is "_{module_base_name}.dll"
|
||||||
|
if hasattr(sys, "frozen"):
|
||||||
|
# What to do? The .dll knows its name, but this is likely to be
|
||||||
|
# executed via a .exe, which does not know.
|
||||||
|
base, ext = os.path.splitext(mod_name)
|
||||||
|
path, base = os.path.split(base)
|
||||||
|
# handle the common case of 'foo.exe'/'foow.exe'
|
||||||
|
if base.endswith('w'):
|
||||||
|
base = base[:-1]
|
||||||
|
# For py2exe, we have '_foo.dll' as the standard pyisapi loader - but
|
||||||
|
# 'foo.dll' is what we use (it just delegates).
|
||||||
|
# So no leading '_' on the installed name.
|
||||||
|
dll_name = os.path.abspath(os.path.join(path, base + ".dll"))
|
||||||
|
else:
|
||||||
|
base, ext = os.path.splitext(mod_name)
|
||||||
|
path, base = os.path.split(base)
|
||||||
|
dll_name = os.path.abspath(os.path.join(path, "_" + base + ".dll"))
|
||||||
|
# Check we actually have it.
|
||||||
|
if check_module is None: check_module = not hasattr(sys, "frozen")
|
||||||
|
if check_module:
|
||||||
|
CheckLoaderModule(dll_name)
|
||||||
|
return dll_name
|
||||||
|
|
||||||
|
# Note the 'log' params to these 'builtin' args - old versions of pywin32
|
||||||
|
# didn't log at all in this function (by intent; anyone calling this was
|
||||||
|
# responsible). So existing code that calls this function with the old
|
||||||
|
# signature (ie, without a 'log' param) still gets the same behaviour as
|
||||||
|
# before...
|
||||||
|
|
||||||
|
def InstallModule(conf_module_name, params, options, log=lambda *args:None):
|
||||||
|
"Install the extension"
|
||||||
|
if not hasattr(sys, "frozen"):
|
||||||
|
conf_module_name = os.path.abspath(conf_module_name)
|
||||||
|
if not os.path.isfile(conf_module_name):
|
||||||
|
raise ConfigurationError("%s does not exist" % (conf_module_name,))
|
||||||
|
|
||||||
|
loader_dll = GetLoaderModuleName(conf_module_name)
|
||||||
|
_PatchParamsModule(params, loader_dll)
|
||||||
|
Install(params, options)
|
||||||
|
log(1, "Installation complete.")
|
||||||
|
|
||||||
|
def UninstallModule(conf_module_name, params, options, log=lambda *args:None):
|
||||||
|
"Remove the extension"
|
||||||
|
loader_dll = GetLoaderModuleName(conf_module_name, False)
|
||||||
|
_PatchParamsModule(params, loader_dll, False)
|
||||||
|
Uninstall(params, options)
|
||||||
|
log(1, "Uninstallation complete.")
|
||||||
|
|
||||||
|
standard_arguments = {
|
||||||
|
"install" : InstallModule,
|
||||||
|
"remove" : UninstallModule,
|
||||||
|
}
|
||||||
|
|
||||||
|
def build_usage(handler_map):
|
||||||
|
docstrings = [handler.__doc__ for handler in handler_map.values()]
|
||||||
|
all_args = dict(zip(iter(handler_map.keys()), docstrings))
|
||||||
|
arg_names = "|".join(iter(all_args.keys()))
|
||||||
|
usage_string = "%prog [options] [" + arg_names + "]\n"
|
||||||
|
usage_string += "commands:\n"
|
||||||
|
for arg, desc in all_args.items():
|
||||||
|
usage_string += " %-10s: %s" % (arg, desc) + "\n"
|
||||||
|
return usage_string[:-1]
|
||||||
|
|
||||||
|
def MergeStandardOptions(options, params):
|
||||||
|
"""
|
||||||
|
Take an options object generated by the command line and merge
|
||||||
|
the values into the IISParameters object.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# We support 2 ways of extending our command-line/install support.
|
||||||
|
# * Many of the installation items allow you to specify "PreInstall",
|
||||||
|
# "PostInstall", "PreRemove" and "PostRemove" hooks
|
||||||
|
# All hooks are called with the 'params' object being operated on, and
|
||||||
|
# the 'optparser' options for this session (ie, the command-line options)
|
||||||
|
# PostInstall for VirtualDirectories and Filters both have an additional
|
||||||
|
# param - the ADSI object just created.
|
||||||
|
# * You can pass your own option parser for us to use, and/or define a map
|
||||||
|
# with your own custom arg handlers. It is a map of 'arg'->function.
|
||||||
|
# The function is called with (options, log_fn, arg). The function's
|
||||||
|
# docstring is used in the usage output.
|
||||||
|
def HandleCommandLine(params, argv=None, conf_module_name = None,
|
||||||
|
default_arg = "install",
|
||||||
|
opt_parser = None, custom_arg_handlers = {}):
|
||||||
|
"""Perform installation or removal of an ISAPI filter or extension.
|
||||||
|
|
||||||
|
This module handles standard command-line options and configuration
|
||||||
|
information, and installs, removes or updates the configuration of an
|
||||||
|
ISAPI filter or extension.
|
||||||
|
|
||||||
|
You must pass your configuration information in params - all other
|
||||||
|
arguments are optional, and allow you to configure the installation
|
||||||
|
process.
|
||||||
|
"""
|
||||||
|
global verbose
|
||||||
|
from optparse import OptionParser
|
||||||
|
|
||||||
|
argv = argv or sys.argv
|
||||||
|
if not conf_module_name:
|
||||||
|
conf_module_name = sys.argv[0]
|
||||||
|
# convert to a long name so that if we were somehow registered with
|
||||||
|
# the "short" version but unregistered with the "long" version we
|
||||||
|
# still work (that will depend on exactly how the installer was
|
||||||
|
# started)
|
||||||
|
try:
|
||||||
|
conf_module_name = win32api.GetLongPathName(conf_module_name)
|
||||||
|
except win32api.error as exc:
|
||||||
|
log(2, "Couldn't determine the long name for %r: %s" %
|
||||||
|
(conf_module_name, exc))
|
||||||
|
|
||||||
|
if opt_parser is None:
|
||||||
|
# Build our own parser.
|
||||||
|
parser = OptionParser(usage='')
|
||||||
|
else:
|
||||||
|
# The caller is providing their own filter, presumably with their
|
||||||
|
# own options all setup.
|
||||||
|
parser = opt_parser
|
||||||
|
|
||||||
|
# build a usage string if we don't have one.
|
||||||
|
if not parser.get_usage():
|
||||||
|
all_handlers = standard_arguments.copy()
|
||||||
|
all_handlers.update(custom_arg_handlers)
|
||||||
|
parser.set_usage(build_usage(all_handlers))
|
||||||
|
|
||||||
|
# allow the user to use uninstall as a synonym for remove if it wasn't
|
||||||
|
# defined by the custom arg handlers.
|
||||||
|
all_handlers.setdefault('uninstall', all_handlers['remove'])
|
||||||
|
|
||||||
|
parser.add_option("-q", "--quiet",
|
||||||
|
action="store_false", dest="verbose", default=True,
|
||||||
|
help="don't print status messages to stdout")
|
||||||
|
parser.add_option("-v", "--verbosity", action="count",
|
||||||
|
dest="verbose", default=1,
|
||||||
|
help="increase the verbosity of status messages")
|
||||||
|
parser.add_option("", "--server", action="store",
|
||||||
|
help="Specifies the IIS server to install/uninstall on." \
|
||||||
|
" Default is '%s/1'" % (_IIS_OBJECT,))
|
||||||
|
|
||||||
|
(options, args) = parser.parse_args(argv[1:])
|
||||||
|
MergeStandardOptions(options, params)
|
||||||
|
verbose = options.verbose
|
||||||
|
if not args:
|
||||||
|
args = [default_arg]
|
||||||
|
try:
|
||||||
|
for arg in args:
|
||||||
|
handler = all_handlers[arg]
|
||||||
|
handler(conf_module_name, params, options, log)
|
||||||
|
except (ItemNotFound, InstallationError) as details:
|
||||||
|
if options.verbose > 1:
|
||||||
|
traceback.print_exc()
|
||||||
|
print("%s: %s" % (details.__class__.__name__, details))
|
||||||
|
except KeyError:
|
||||||
|
parser.error("Invalid arg '%s'" % arg)
|
@ -0,0 +1,120 @@
|
|||||||
|
"""Constants needed by ISAPI filters and extensions."""
|
||||||
|
# ======================================================================
|
||||||
|
# Copyright 2002-2003 by Blackdog Software Pty Ltd.
|
||||||
|
#
|
||||||
|
# All Rights Reserved
|
||||||
|
#
|
||||||
|
# Permission to use, copy, modify, and distribute this software and
|
||||||
|
# its documentation for any purpose and without fee is hereby
|
||||||
|
# granted, provided that the above copyright notice appear in all
|
||||||
|
# copies and that both that copyright notice and this permission
|
||||||
|
# notice appear in supporting documentation, and that the name of
|
||||||
|
# Blackdog Software not be used in advertising or publicity pertaining to
|
||||||
|
# distribution of the software without specific, written prior
|
||||||
|
# permission.
|
||||||
|
#
|
||||||
|
# BLACKDOG SOFTWARE DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
|
||||||
|
# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
|
||||||
|
# NO EVENT SHALL BLACKDOG SOFTWARE BE LIABLE FOR ANY SPECIAL, INDIRECT OR
|
||||||
|
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||||
|
# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
# ======================================================================
|
||||||
|
|
||||||
|
# HTTP reply codes
|
||||||
|
|
||||||
|
HTTP_CONTINUE = 100
|
||||||
|
HTTP_SWITCHING_PROTOCOLS = 101
|
||||||
|
HTTP_PROCESSING = 102
|
||||||
|
HTTP_OK = 200
|
||||||
|
HTTP_CREATED = 201
|
||||||
|
HTTP_ACCEPTED = 202
|
||||||
|
HTTP_NON_AUTHORITATIVE = 203
|
||||||
|
HTTP_NO_CONTENT = 204
|
||||||
|
HTTP_RESET_CONTENT = 205
|
||||||
|
HTTP_PARTIAL_CONTENT = 206
|
||||||
|
HTTP_MULTI_STATUS = 207
|
||||||
|
HTTP_MULTIPLE_CHOICES = 300
|
||||||
|
HTTP_MOVED_PERMANENTLY = 301
|
||||||
|
HTTP_MOVED_TEMPORARILY = 302
|
||||||
|
HTTP_SEE_OTHER = 303
|
||||||
|
HTTP_NOT_MODIFIED = 304
|
||||||
|
HTTP_USE_PROXY = 305
|
||||||
|
HTTP_TEMPORARY_REDIRECT = 307
|
||||||
|
HTTP_BAD_REQUEST = 400
|
||||||
|
HTTP_UNAUTHORIZED = 401
|
||||||
|
HTTP_PAYMENT_REQUIRED = 402
|
||||||
|
HTTP_FORBIDDEN = 403
|
||||||
|
HTTP_NOT_FOUND = 404
|
||||||
|
HTTP_METHOD_NOT_ALLOWED = 405
|
||||||
|
HTTP_NOT_ACCEPTABLE = 406
|
||||||
|
HTTP_PROXY_AUTHENTICATION_REQUIRED= 407
|
||||||
|
HTTP_REQUEST_TIME_OUT = 408
|
||||||
|
HTTP_CONFLICT = 409
|
||||||
|
HTTP_GONE = 410
|
||||||
|
HTTP_LENGTH_REQUIRED = 411
|
||||||
|
HTTP_PRECONDITION_FAILED = 412
|
||||||
|
HTTP_REQUEST_ENTITY_TOO_LARGE = 413
|
||||||
|
HTTP_REQUEST_URI_TOO_LARGE = 414
|
||||||
|
HTTP_UNSUPPORTED_MEDIA_TYPE = 415
|
||||||
|
HTTP_RANGE_NOT_SATISFIABLE = 416
|
||||||
|
HTTP_EXPECTATION_FAILED = 417
|
||||||
|
HTTP_UNPROCESSABLE_ENTITY = 422
|
||||||
|
HTTP_INTERNAL_SERVER_ERROR = 500
|
||||||
|
HTTP_NOT_IMPLEMENTED = 501
|
||||||
|
HTTP_BAD_GATEWAY = 502
|
||||||
|
HTTP_SERVICE_UNAVAILABLE = 503
|
||||||
|
HTTP_GATEWAY_TIME_OUT = 504
|
||||||
|
HTTP_VERSION_NOT_SUPPORTED = 505
|
||||||
|
HTTP_VARIANT_ALSO_VARIES = 506
|
||||||
|
|
||||||
|
HSE_STATUS_SUCCESS = 1
|
||||||
|
HSE_STATUS_SUCCESS_AND_KEEP_CONN = 2
|
||||||
|
HSE_STATUS_PENDING = 3
|
||||||
|
HSE_STATUS_ERROR = 4
|
||||||
|
|
||||||
|
SF_NOTIFY_SECURE_PORT = 0x00000001
|
||||||
|
SF_NOTIFY_NONSECURE_PORT = 0x00000002
|
||||||
|
SF_NOTIFY_READ_RAW_DATA = 0x00008000
|
||||||
|
SF_NOTIFY_PREPROC_HEADERS = 0x00004000
|
||||||
|
SF_NOTIFY_AUTHENTICATION = 0x00002000
|
||||||
|
SF_NOTIFY_URL_MAP = 0x00001000
|
||||||
|
SF_NOTIFY_ACCESS_DENIED = 0x00000800
|
||||||
|
SF_NOTIFY_SEND_RESPONSE = 0x00000040
|
||||||
|
SF_NOTIFY_SEND_RAW_DATA = 0x00000400
|
||||||
|
SF_NOTIFY_LOG = 0x00000200
|
||||||
|
SF_NOTIFY_END_OF_REQUEST = 0x00000080
|
||||||
|
SF_NOTIFY_END_OF_NET_SESSION = 0x00000100
|
||||||
|
|
||||||
|
SF_NOTIFY_ORDER_HIGH = 0x00080000
|
||||||
|
SF_NOTIFY_ORDER_MEDIUM = 0x00040000
|
||||||
|
SF_NOTIFY_ORDER_LOW = 0x00020000
|
||||||
|
SF_NOTIFY_ORDER_DEFAULT = SF_NOTIFY_ORDER_LOW
|
||||||
|
|
||||||
|
SF_NOTIFY_ORDER_MASK = (SF_NOTIFY_ORDER_HIGH | \
|
||||||
|
SF_NOTIFY_ORDER_MEDIUM | \
|
||||||
|
SF_NOTIFY_ORDER_LOW)
|
||||||
|
|
||||||
|
SF_STATUS_REQ_FINISHED = 134217728 # 0x8000000
|
||||||
|
SF_STATUS_REQ_FINISHED_KEEP_CONN = 134217728 + 1
|
||||||
|
SF_STATUS_REQ_NEXT_NOTIFICATION = 134217728 + 2
|
||||||
|
SF_STATUS_REQ_HANDLED_NOTIFICATION = 134217728 + 3
|
||||||
|
SF_STATUS_REQ_ERROR = 134217728 + 4
|
||||||
|
SF_STATUS_REQ_READ_NEXT = 134217728 + 5
|
||||||
|
|
||||||
|
HSE_IO_SYNC = 0x00000001 # for WriteClient
|
||||||
|
HSE_IO_ASYNC = 0x00000002 # for WriteClient/TF/EU
|
||||||
|
HSE_IO_DISCONNECT_AFTER_SEND = 0x00000004 # for TF
|
||||||
|
HSE_IO_SEND_HEADERS = 0x00000008 # for TF
|
||||||
|
HSE_IO_NODELAY = 0x00001000 # turn off nagling
|
||||||
|
# These two are only used by VectorSend
|
||||||
|
HSE_IO_FINAL_SEND = 0x00000010
|
||||||
|
HSE_IO_CACHE_RESPONSE = 0x00000020
|
||||||
|
|
||||||
|
HSE_EXEC_URL_NO_HEADERS = 0x02
|
||||||
|
HSE_EXEC_URL_IGNORE_CURRENT_INTERCEPTOR = 0x04
|
||||||
|
HSE_EXEC_URL_IGNORE_VALIDATION_AND_RANGE = 0x10
|
||||||
|
HSE_EXEC_URL_DISABLE_CUSTOM_ERROR = 0x20
|
||||||
|
HSE_EXEC_URL_SSI_CMD = 0x40
|
||||||
|
HSE_EXEC_URL_HTTP_CACHE_ELIGIBLE = 0x80
|
@ -0,0 +1,20 @@
|
|||||||
|
In this directory you will find examples of ISAPI filters and extensions.
|
||||||
|
|
||||||
|
The filter loading mechanism works like this:
|
||||||
|
* IIS loads the special Python "loader" DLL. This DLL will generally have a
|
||||||
|
leading underscore as part of its name.
|
||||||
|
* This loader DLL looks for a Python module, by removing the first letter of
|
||||||
|
the DLL base name.
|
||||||
|
|
||||||
|
This means that an ISAPI extension module consists of 2 key files - the loader
|
||||||
|
DLL (eg, "_MyIISModule.dll", and a Python module (which for this example
|
||||||
|
would be "MyIISModule.py")
|
||||||
|
|
||||||
|
When you install an ISAPI extension, the installation code checks to see if
|
||||||
|
there is a loader DLL for your implementation file - if one does not exist,
|
||||||
|
or the standard loader is different, it is copied and renamed accordingly.
|
||||||
|
|
||||||
|
We use this mechanism to provide the maximum separation between different
|
||||||
|
Python extensions installed on the same server - otherwise filter order and
|
||||||
|
other tricky IIS semantics would need to be replicated. Also, each filter
|
||||||
|
gets its own thread-pool, etc.
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,196 @@
|
|||||||
|
# This extension demonstrates some advanced features of the Python ISAPI
|
||||||
|
# framework.
|
||||||
|
# We demonstrate:
|
||||||
|
# * Reloading your Python module without shutting down IIS (eg, when your
|
||||||
|
# .py implementation file changes.)
|
||||||
|
# * Custom command-line handling - both additional options and commands.
|
||||||
|
# * Using a query string - any part of the URL after a '?' is assumed to
|
||||||
|
# be "variable names" separated by '&' - we will print the values of
|
||||||
|
# these server variables.
|
||||||
|
# * If the tail portion of the URL is "ReportUnhealthy", IIS will be
|
||||||
|
# notified we are unhealthy via a HSE_REQ_REPORT_UNHEALTHY request.
|
||||||
|
# Whether this is acted upon depends on if the IIS health-checking
|
||||||
|
# tools are installed, but you should always see the reason written
|
||||||
|
# to the Windows event log - see the IIS documentation for more.
|
||||||
|
|
||||||
|
from isapi import isapicon
|
||||||
|
from isapi.simple import SimpleExtension
|
||||||
|
import sys, os, stat
|
||||||
|
|
||||||
|
if hasattr(sys, "isapidllhandle"):
|
||||||
|
import win32traceutil
|
||||||
|
|
||||||
|
# Notes on reloading
|
||||||
|
# If your HttpFilterProc or HttpExtensionProc functions raises
|
||||||
|
# 'isapi.InternalReloadException', the framework will not treat it
|
||||||
|
# as an error but instead will terminate your extension, reload your
|
||||||
|
# extension module, re-initialize the instance, and re-issue the request.
|
||||||
|
# The Initialize functions are called with None as their param. The
|
||||||
|
# return code from the terminate function is ignored.
|
||||||
|
#
|
||||||
|
# This is all the framework does to help you. It is up to your code
|
||||||
|
# when you raise this exception. This sample uses a Win32 "find
|
||||||
|
# notification". Whenever windows tells us one of the files in the
|
||||||
|
# directory has changed, we check if the time of our source-file has
|
||||||
|
# changed, and set a flag. Next imcoming request, we check the flag and
|
||||||
|
# raise the special exception if set.
|
||||||
|
#
|
||||||
|
# The end result is that the module is automatically reloaded whenever
|
||||||
|
# the source-file changes - you need take no further action to see your
|
||||||
|
# changes reflected in the running server.
|
||||||
|
|
||||||
|
# The framework only reloads your module - if you have libraries you
|
||||||
|
# depend on and also want reloaded, you must arrange for this yourself.
|
||||||
|
# One way of doing this would be to special case the import of these
|
||||||
|
# modules. Eg:
|
||||||
|
# --
|
||||||
|
# try:
|
||||||
|
# my_module = reload(my_module) # module already imported - reload it
|
||||||
|
# except NameError:
|
||||||
|
# import my_module # first time around - import it.
|
||||||
|
# --
|
||||||
|
# When your module is imported for the first time, the NameError will
|
||||||
|
# be raised, and the module imported. When the ISAPI framework reloads
|
||||||
|
# your module, the existing module will avoid the NameError, and allow
|
||||||
|
# you to reload that module.
|
||||||
|
|
||||||
|
from isapi import InternalReloadException
|
||||||
|
import win32event, win32file, winerror, win32con, threading
|
||||||
|
|
||||||
|
try:
|
||||||
|
reload_counter += 1
|
||||||
|
except NameError:
|
||||||
|
reload_counter = 0
|
||||||
|
|
||||||
|
# A watcher thread that checks for __file__ changing.
|
||||||
|
# When it detects it, it simply sets "change_detected" to true.
|
||||||
|
class ReloadWatcherThread(threading.Thread):
|
||||||
|
def __init__(self):
|
||||||
|
self.change_detected = False
|
||||||
|
self.filename = __file__
|
||||||
|
if self.filename.endswith("c") or self.filename.endswith("o"):
|
||||||
|
self.filename = self.filename[:-1]
|
||||||
|
self.handle = win32file.FindFirstChangeNotification(
|
||||||
|
os.path.dirname(self.filename),
|
||||||
|
False, # watch tree?
|
||||||
|
win32con.FILE_NOTIFY_CHANGE_LAST_WRITE)
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
last_time = os.stat(self.filename)[stat.ST_MTIME]
|
||||||
|
while 1:
|
||||||
|
try:
|
||||||
|
rc = win32event.WaitForSingleObject(self.handle,
|
||||||
|
win32event.INFINITE)
|
||||||
|
win32file.FindNextChangeNotification(self.handle)
|
||||||
|
except win32event.error as details:
|
||||||
|
# handle closed - thread should terminate.
|
||||||
|
if details.winerror != winerror.ERROR_INVALID_HANDLE:
|
||||||
|
raise
|
||||||
|
break
|
||||||
|
this_time = os.stat(self.filename)[stat.ST_MTIME]
|
||||||
|
if this_time != last_time:
|
||||||
|
print("Detected file change - flagging for reload.")
|
||||||
|
self.change_detected = True
|
||||||
|
last_time = this_time
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
win32file.FindCloseChangeNotification(self.handle)
|
||||||
|
|
||||||
|
# The ISAPI extension - handles requests in our virtual dir, and sends the
|
||||||
|
# response to the client.
|
||||||
|
class Extension(SimpleExtension):
|
||||||
|
"Python advanced sample Extension"
|
||||||
|
def __init__(self):
|
||||||
|
self.reload_watcher = ReloadWatcherThread()
|
||||||
|
self.reload_watcher.start()
|
||||||
|
|
||||||
|
def HttpExtensionProc(self, ecb):
|
||||||
|
# NOTE: If you use a ThreadPoolExtension, you must still perform
|
||||||
|
# this check in HttpExtensionProc - raising the exception from
|
||||||
|
# The "Dispatch" method will just cause the exception to be
|
||||||
|
# rendered to the browser.
|
||||||
|
if self.reload_watcher.change_detected:
|
||||||
|
print("Doing reload")
|
||||||
|
raise InternalReloadException
|
||||||
|
|
||||||
|
url = ecb.GetServerVariable("UNICODE_URL")
|
||||||
|
if url.endswith("ReportUnhealthy"):
|
||||||
|
ecb.ReportUnhealthy("I'm a little sick")
|
||||||
|
|
||||||
|
ecb.SendResponseHeaders("200 OK", "Content-Type: text/html\r\n\r\n", 0)
|
||||||
|
print("<HTML><BODY>", file=ecb)
|
||||||
|
|
||||||
|
qs = ecb.GetServerVariable("QUERY_STRING")
|
||||||
|
if qs:
|
||||||
|
queries = qs.split("&")
|
||||||
|
print("<PRE>", file=ecb)
|
||||||
|
for q in queries:
|
||||||
|
val = ecb.GetServerVariable(q, '<no such variable>')
|
||||||
|
print("%s=%r" % (q, val), file=ecb)
|
||||||
|
print("</PRE><P/>", file=ecb)
|
||||||
|
|
||||||
|
print("This module has been imported", file=ecb)
|
||||||
|
print("%d times" % (reload_counter,), file=ecb)
|
||||||
|
print("</BODY></HTML>", file=ecb)
|
||||||
|
ecb.close()
|
||||||
|
return isapicon.HSE_STATUS_SUCCESS
|
||||||
|
|
||||||
|
def TerminateExtension(self, status):
|
||||||
|
self.reload_watcher.stop()
|
||||||
|
|
||||||
|
# The entry points for the ISAPI extension.
|
||||||
|
def __ExtensionFactory__():
|
||||||
|
return Extension()
|
||||||
|
|
||||||
|
# Our special command line customization.
|
||||||
|
# Pre-install hook for our virtual directory.
|
||||||
|
def PreInstallDirectory(params, options):
|
||||||
|
# If the user used our special '--description' option,
|
||||||
|
# then we override our default.
|
||||||
|
if options.description:
|
||||||
|
params.Description = options.description
|
||||||
|
|
||||||
|
# Post install hook for our entire script
|
||||||
|
def PostInstall(params, options):
|
||||||
|
print()
|
||||||
|
print("The sample has been installed.")
|
||||||
|
print("Point your browser to /AdvancedPythonSample")
|
||||||
|
print("If you modify the source file and reload the page,")
|
||||||
|
print("you should see the reload counter increment")
|
||||||
|
|
||||||
|
# Handler for our custom 'status' argument.
|
||||||
|
def status_handler(options, log, arg):
|
||||||
|
"Query the status of something"
|
||||||
|
print("Everything seems to be fine!")
|
||||||
|
|
||||||
|
custom_arg_handlers = {"status": status_handler}
|
||||||
|
|
||||||
|
if __name__=='__main__':
|
||||||
|
# If run from the command-line, install ourselves.
|
||||||
|
from isapi.install import *
|
||||||
|
params = ISAPIParameters(PostInstall = PostInstall)
|
||||||
|
# Setup the virtual directories - this is a list of directories our
|
||||||
|
# extension uses - in this case only 1.
|
||||||
|
# Each extension has a "script map" - this is the mapping of ISAPI
|
||||||
|
# extensions.
|
||||||
|
sm = [
|
||||||
|
ScriptMapParams(Extension="*", Flags=0)
|
||||||
|
]
|
||||||
|
vd = VirtualDirParameters(Name="AdvancedPythonSample",
|
||||||
|
Description = Extension.__doc__,
|
||||||
|
ScriptMaps = sm,
|
||||||
|
ScriptMapUpdate = "replace",
|
||||||
|
# specify the pre-install hook.
|
||||||
|
PreInstall = PreInstallDirectory
|
||||||
|
)
|
||||||
|
params.VirtualDirs = [vd]
|
||||||
|
# Setup our custom option parser.
|
||||||
|
from optparse import OptionParser
|
||||||
|
parser = OptionParser('') # blank usage, so isapi sets it.
|
||||||
|
parser.add_option("", "--description",
|
||||||
|
action="store",
|
||||||
|
help="custom description to use for the virtual directory")
|
||||||
|
|
||||||
|
HandleCommandLine(params, opt_parser=parser,
|
||||||
|
custom_arg_handlers = custom_arg_handlers)
|
@ -0,0 +1,109 @@
|
|||||||
|
# This is a sample ISAPI extension written in Python.
|
||||||
|
#
|
||||||
|
# Please see README.txt in this directory, and specifically the
|
||||||
|
# information about the "loader" DLL - installing this sample will create
|
||||||
|
# "_redirector.dll" in the current directory. The readme explains this.
|
||||||
|
|
||||||
|
# Executing this script (or any server config script) will install the extension
|
||||||
|
# into your web server. As the server executes, the PyISAPI framework will load
|
||||||
|
# this module and create your Extension and Filter objects.
|
||||||
|
|
||||||
|
# This is the simplest possible redirector (or proxy) we can write. The
|
||||||
|
# extension installs with a mask of '*' in the root of the site.
|
||||||
|
# As an added bonus though, we optionally show how, on IIS6 and later, we
|
||||||
|
# can use HSE_ERQ_EXEC_URL to ignore certain requests - in IIS5 and earlier
|
||||||
|
# we can only do this with an ISAPI filter - see redirector_with_filter for
|
||||||
|
# an example. If this sample is run on IIS5 or earlier it simply ignores
|
||||||
|
# any excludes.
|
||||||
|
|
||||||
|
from isapi import isapicon, threaded_extension
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
try:
|
||||||
|
from urllib.request import urlopen
|
||||||
|
except ImportError:
|
||||||
|
# py3k spelling...
|
||||||
|
from urllib.request import urlopen
|
||||||
|
import win32api
|
||||||
|
|
||||||
|
# sys.isapidllhandle will exist when we are loaded by the IIS framework.
|
||||||
|
# In this case we redirect our output to the win32traceutil collector.
|
||||||
|
if hasattr(sys, "isapidllhandle"):
|
||||||
|
import win32traceutil
|
||||||
|
|
||||||
|
# The site we are proxying.
|
||||||
|
proxy = "http://www.python.org"
|
||||||
|
|
||||||
|
# Urls we exclude (ie, allow IIS to handle itself) - all are lowered,
|
||||||
|
# and these entries exist by default on Vista...
|
||||||
|
excludes = ["/iisstart.htm", "/welcome.png"]
|
||||||
|
|
||||||
|
# An "io completion" function, called when ecb.ExecURL completes...
|
||||||
|
def io_callback(ecb, url, cbIO, errcode):
|
||||||
|
# Get the status of our ExecURL
|
||||||
|
httpstatus, substatus, win32 = ecb.GetExecURLStatus()
|
||||||
|
print("ExecURL of %r finished with http status %d.%d, win32 status %d (%s)" % (
|
||||||
|
url, httpstatus, substatus, win32, win32api.FormatMessage(win32).strip()))
|
||||||
|
# nothing more to do!
|
||||||
|
ecb.DoneWithSession()
|
||||||
|
|
||||||
|
# The ISAPI extension - handles all requests in the site.
|
||||||
|
class Extension(threaded_extension.ThreadPoolExtension):
|
||||||
|
"Python sample Extension"
|
||||||
|
def Dispatch(self, ecb):
|
||||||
|
# Note that our ThreadPoolExtension base class will catch exceptions
|
||||||
|
# in our Dispatch method, and write the traceback to the client.
|
||||||
|
# That is perfect for this sample, so we don't catch our own.
|
||||||
|
#print 'IIS dispatching "%s"' % (ecb.GetServerVariable("URL"),)
|
||||||
|
url = ecb.GetServerVariable("URL").decode("ascii")
|
||||||
|
for exclude in excludes:
|
||||||
|
if url.lower().startswith(exclude):
|
||||||
|
print("excluding %s" % url)
|
||||||
|
if ecb.Version < 0x60000:
|
||||||
|
print("(but this is IIS5 or earlier - can't do 'excludes')")
|
||||||
|
else:
|
||||||
|
ecb.IOCompletion(io_callback, url)
|
||||||
|
ecb.ExecURL(None, None, None, None, None, isapicon.HSE_EXEC_URL_IGNORE_CURRENT_INTERCEPTOR)
|
||||||
|
return isapicon.HSE_STATUS_PENDING
|
||||||
|
|
||||||
|
new_url = proxy + url
|
||||||
|
print("Opening %s" % new_url)
|
||||||
|
fp = urlopen(new_url)
|
||||||
|
headers = fp.info()
|
||||||
|
# subtle py3k breakage: in py3k, str(headers) has normalized \r\n
|
||||||
|
# back to \n and also stuck an extra \n term. py2k leaves the
|
||||||
|
# \r\n from the server in tact and finishes with a single term.
|
||||||
|
if sys.version_info < (3,0):
|
||||||
|
header_text = str(headers) + "\r\n"
|
||||||
|
else:
|
||||||
|
# take *all* trailing \n off, replace remaining with
|
||||||
|
# \r\n, then add the 2 trailing \r\n.
|
||||||
|
header_text = str(headers).rstrip('\n').replace('\n', '\r\n') + '\r\n\r\n'
|
||||||
|
ecb.SendResponseHeaders("200 OK", header_text, False)
|
||||||
|
ecb.WriteClient(fp.read())
|
||||||
|
ecb.DoneWithSession()
|
||||||
|
print("Returned data from '%s'" % (new_url,))
|
||||||
|
return isapicon.HSE_STATUS_SUCCESS
|
||||||
|
|
||||||
|
# The entry points for the ISAPI extension.
|
||||||
|
def __ExtensionFactory__():
|
||||||
|
return Extension()
|
||||||
|
|
||||||
|
if __name__=='__main__':
|
||||||
|
# If run from the command-line, install ourselves.
|
||||||
|
from isapi.install import *
|
||||||
|
params = ISAPIParameters()
|
||||||
|
# Setup the virtual directories - this is a list of directories our
|
||||||
|
# extension uses - in this case only 1.
|
||||||
|
# Each extension has a "script map" - this is the mapping of ISAPI
|
||||||
|
# extensions.
|
||||||
|
sm = [
|
||||||
|
ScriptMapParams(Extension="*", Flags=0)
|
||||||
|
]
|
||||||
|
vd = VirtualDirParameters(Name="/",
|
||||||
|
Description = Extension.__doc__,
|
||||||
|
ScriptMaps = sm,
|
||||||
|
ScriptMapUpdate = "replace"
|
||||||
|
)
|
||||||
|
params.VirtualDirs = [vd]
|
||||||
|
HandleCommandLine(params)
|
@ -0,0 +1,78 @@
|
|||||||
|
# This is a sample ISAPI extension written in Python.
|
||||||
|
|
||||||
|
# This is like the other 'redirector' samples, but uses asnch IO when writing
|
||||||
|
# back to the client (it does *not* use asynch io talking to the remote
|
||||||
|
# server!)
|
||||||
|
|
||||||
|
from isapi import isapicon, threaded_extension
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
import urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
# sys.isapidllhandle will exist when we are loaded by the IIS framework.
|
||||||
|
# In this case we redirect our output to the win32traceutil collector.
|
||||||
|
if hasattr(sys, "isapidllhandle"):
|
||||||
|
import win32traceutil
|
||||||
|
|
||||||
|
# The site we are proxying.
|
||||||
|
proxy = "http://www.python.org"
|
||||||
|
|
||||||
|
# We synchronously read chunks of this size then asynchronously write them.
|
||||||
|
CHUNK_SIZE=8192
|
||||||
|
|
||||||
|
# The callback made when IIS completes the asynch write.
|
||||||
|
def io_callback(ecb, fp, cbIO, errcode):
|
||||||
|
print("IO callback", ecb, fp, cbIO, errcode)
|
||||||
|
chunk = fp.read(CHUNK_SIZE)
|
||||||
|
if chunk:
|
||||||
|
ecb.WriteClient(chunk, isapicon.HSE_IO_ASYNC)
|
||||||
|
# and wait for the next callback to say this chunk is done.
|
||||||
|
else:
|
||||||
|
# eof - say we are complete.
|
||||||
|
fp.close()
|
||||||
|
ecb.DoneWithSession()
|
||||||
|
|
||||||
|
# The ISAPI extension - handles all requests in the site.
|
||||||
|
class Extension(threaded_extension.ThreadPoolExtension):
|
||||||
|
"Python sample proxy server - asynch version."
|
||||||
|
def Dispatch(self, ecb):
|
||||||
|
print('IIS dispatching "%s"' % (ecb.GetServerVariable("URL"),))
|
||||||
|
url = ecb.GetServerVariable("URL")
|
||||||
|
|
||||||
|
new_url = proxy + url
|
||||||
|
print("Opening %s" % new_url)
|
||||||
|
fp = urllib.request.urlopen(new_url)
|
||||||
|
headers = fp.info()
|
||||||
|
ecb.SendResponseHeaders("200 OK", str(headers) + "\r\n", False)
|
||||||
|
# now send the first chunk asynchronously
|
||||||
|
ecb.ReqIOCompletion(io_callback, fp)
|
||||||
|
chunk = fp.read(CHUNK_SIZE)
|
||||||
|
if chunk:
|
||||||
|
ecb.WriteClient(chunk, isapicon.HSE_IO_ASYNC)
|
||||||
|
return isapicon.HSE_STATUS_PENDING
|
||||||
|
# no data - just close things now.
|
||||||
|
ecb.DoneWithSession()
|
||||||
|
return isapicon.HSE_STATUS_SUCCESS
|
||||||
|
|
||||||
|
# The entry points for the ISAPI extension.
|
||||||
|
def __ExtensionFactory__():
|
||||||
|
return Extension()
|
||||||
|
|
||||||
|
if __name__=='__main__':
|
||||||
|
# If run from the command-line, install ourselves.
|
||||||
|
from isapi.install import *
|
||||||
|
params = ISAPIParameters()
|
||||||
|
# Setup the virtual directories - this is a list of directories our
|
||||||
|
# extension uses - in this case only 1.
|
||||||
|
# Each extension has a "script map" - this is the mapping of ISAPI
|
||||||
|
# extensions.
|
||||||
|
sm = [
|
||||||
|
ScriptMapParams(Extension="*", Flags=0)
|
||||||
|
]
|
||||||
|
vd = VirtualDirParameters(Name="/",
|
||||||
|
Description = Extension.__doc__,
|
||||||
|
ScriptMaps = sm,
|
||||||
|
ScriptMapUpdate = "replace"
|
||||||
|
)
|
||||||
|
params.VirtualDirs = [vd]
|
||||||
|
HandleCommandLine(params)
|
@ -0,0 +1,155 @@
|
|||||||
|
# This is a sample configuration file for an ISAPI filter and extension
|
||||||
|
# written in Python.
|
||||||
|
#
|
||||||
|
# Please see README.txt in this directory, and specifically the
|
||||||
|
# information about the "loader" DLL - installing this sample will create
|
||||||
|
# "_redirector_with_filter.dll" in the current directory. The readme explains
|
||||||
|
# this.
|
||||||
|
|
||||||
|
# Executing this script (or any server config script) will install the extension
|
||||||
|
# into your web server. As the server executes, the PyISAPI framework will load
|
||||||
|
# this module and create your Extension and Filter objects.
|
||||||
|
|
||||||
|
# This sample provides sample redirector:
|
||||||
|
# It is implemented by a filter and an extension, so that some requests can
|
||||||
|
# be ignored. Compare with 'redirector_simple' which avoids the filter, but
|
||||||
|
# is unable to selectively ignore certain requests.
|
||||||
|
# The process is sample uses is:
|
||||||
|
# * The filter is installed globally, as all filters are.
|
||||||
|
# * A Virtual Directory named "python" is setup. This dir has our ISAPI
|
||||||
|
# extension as the only application, mapped to file-extension '*'. Thus, our
|
||||||
|
# extension handles *all* requests in this directory.
|
||||||
|
# The basic process is that the filter does URL rewriting, redirecting every
|
||||||
|
# URL to our Virtual Directory. Our extension then handles this request,
|
||||||
|
# forwarding the data from the proxied site.
|
||||||
|
# For example:
|
||||||
|
# * URL of "index.html" comes in.
|
||||||
|
# * Filter rewrites this to "/python/index.html"
|
||||||
|
# * Our extension sees the full "/python/index.html", removes the leading
|
||||||
|
# portion, and opens and forwards the remote URL.
|
||||||
|
|
||||||
|
|
||||||
|
# This sample is very small - it avoid most error handling, etc. It is for
|
||||||
|
# demonstration purposes only.
|
||||||
|
|
||||||
|
from isapi import isapicon, threaded_extension
|
||||||
|
from isapi.simple import SimpleFilter
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
import urllib.request, urllib.parse, urllib.error
|
||||||
|
|
||||||
|
# sys.isapidllhandle will exist when we are loaded by the IIS framework.
|
||||||
|
# In this case we redirect our output to the win32traceutil collector.
|
||||||
|
if hasattr(sys, "isapidllhandle"):
|
||||||
|
import win32traceutil
|
||||||
|
|
||||||
|
# The site we are proxying.
|
||||||
|
proxy = "http://www.python.org"
|
||||||
|
# The name of the virtual directory we install in, and redirect from.
|
||||||
|
virtualdir = "/python"
|
||||||
|
|
||||||
|
# The key feature of this redirector over the simple redirector is that it
|
||||||
|
# can choose to ignore certain responses by having the filter not rewrite them
|
||||||
|
# to our virtual dir. For this sample, we just exclude the IIS help directory.
|
||||||
|
|
||||||
|
# The ISAPI extension - handles requests in our virtual dir, and sends the
|
||||||
|
# response to the client.
|
||||||
|
class Extension(threaded_extension.ThreadPoolExtension):
|
||||||
|
"Python sample Extension"
|
||||||
|
def Dispatch(self, ecb):
|
||||||
|
# Note that our ThreadPoolExtension base class will catch exceptions
|
||||||
|
# in our Dispatch method, and write the traceback to the client.
|
||||||
|
# That is perfect for this sample, so we don't catch our own.
|
||||||
|
#print 'IIS dispatching "%s"' % (ecb.GetServerVariable("URL"),)
|
||||||
|
url = ecb.GetServerVariable("URL")
|
||||||
|
if url.startswith(virtualdir):
|
||||||
|
new_url = proxy + url[len(virtualdir):]
|
||||||
|
print("Opening", new_url)
|
||||||
|
fp = urllib.request.urlopen(new_url)
|
||||||
|
headers = fp.info()
|
||||||
|
ecb.SendResponseHeaders("200 OK", str(headers) + "\r\n", False)
|
||||||
|
ecb.WriteClient(fp.read())
|
||||||
|
ecb.DoneWithSession()
|
||||||
|
print("Returned data from '%s'!" % (new_url,))
|
||||||
|
else:
|
||||||
|
# this should never happen - we should only see requests that
|
||||||
|
# start with our virtual directory name.
|
||||||
|
print("Not proxying '%s'" % (url,))
|
||||||
|
|
||||||
|
|
||||||
|
# The ISAPI filter.
|
||||||
|
class Filter(SimpleFilter):
|
||||||
|
"Sample Python Redirector"
|
||||||
|
filter_flags = isapicon.SF_NOTIFY_PREPROC_HEADERS | \
|
||||||
|
isapicon.SF_NOTIFY_ORDER_DEFAULT
|
||||||
|
|
||||||
|
def HttpFilterProc(self, fc):
|
||||||
|
#print "Filter Dispatch"
|
||||||
|
nt = fc.NotificationType
|
||||||
|
if nt != isapicon.SF_NOTIFY_PREPROC_HEADERS:
|
||||||
|
return isapicon.SF_STATUS_REQ_NEXT_NOTIFICATION
|
||||||
|
|
||||||
|
pp = fc.GetData()
|
||||||
|
url = pp.GetHeader("url")
|
||||||
|
#print "URL is '%s'" % (url,)
|
||||||
|
prefix = virtualdir
|
||||||
|
if not url.startswith(prefix):
|
||||||
|
new_url = prefix + url
|
||||||
|
print("New proxied URL is '%s'" % (new_url,))
|
||||||
|
pp.SetHeader("url", new_url)
|
||||||
|
# For the sake of demonstration, show how the FilterContext
|
||||||
|
# attribute is used. It always starts out life as None, and
|
||||||
|
# any assignments made are automatically decref'd by the
|
||||||
|
# framework during a SF_NOTIFY_END_OF_NET_SESSION notification.
|
||||||
|
if fc.FilterContext is None:
|
||||||
|
fc.FilterContext = 0
|
||||||
|
fc.FilterContext += 1
|
||||||
|
print("This is request number %d on this connection" % fc.FilterContext)
|
||||||
|
return isapicon.SF_STATUS_REQ_HANDLED_NOTIFICATION
|
||||||
|
else:
|
||||||
|
print("Filter ignoring URL '%s'" % (url,))
|
||||||
|
|
||||||
|
# Some older code that handled SF_NOTIFY_URL_MAP.
|
||||||
|
#~ print "Have URL_MAP notify"
|
||||||
|
#~ urlmap = fc.GetData()
|
||||||
|
#~ print "URI is", urlmap.URL
|
||||||
|
#~ print "Path is", urlmap.PhysicalPath
|
||||||
|
#~ if urlmap.URL.startswith("/UC/"):
|
||||||
|
#~ # Find the /UC/ in the physical path, and nuke it (except
|
||||||
|
#~ # as the path is physical, it is \)
|
||||||
|
#~ p = urlmap.PhysicalPath
|
||||||
|
#~ pos = p.index("\\UC\\")
|
||||||
|
#~ p = p[:pos] + p[pos+3:]
|
||||||
|
#~ p = r"E:\src\pyisapi\webroot\PyTest\formTest.htm"
|
||||||
|
#~ print "New path is", p
|
||||||
|
#~ urlmap.PhysicalPath = p
|
||||||
|
|
||||||
|
# The entry points for the ISAPI extension.
|
||||||
|
def __FilterFactory__():
|
||||||
|
return Filter()
|
||||||
|
def __ExtensionFactory__():
|
||||||
|
return Extension()
|
||||||
|
|
||||||
|
if __name__=='__main__':
|
||||||
|
# If run from the command-line, install ourselves.
|
||||||
|
from isapi.install import *
|
||||||
|
params = ISAPIParameters()
|
||||||
|
# Setup all filters - these are global to the site.
|
||||||
|
params.Filters = [
|
||||||
|
FilterParameters(Name="PythonRedirector",
|
||||||
|
Description=Filter.__doc__),
|
||||||
|
]
|
||||||
|
# Setup the virtual directories - this is a list of directories our
|
||||||
|
# extension uses - in this case only 1.
|
||||||
|
# Each extension has a "script map" - this is the mapping of ISAPI
|
||||||
|
# extensions.
|
||||||
|
sm = [
|
||||||
|
ScriptMapParams(Extension="*", Flags=0)
|
||||||
|
]
|
||||||
|
vd = VirtualDirParameters(Name=virtualdir[1:],
|
||||||
|
Description = Extension.__doc__,
|
||||||
|
ScriptMaps = sm,
|
||||||
|
ScriptMapUpdate = "replace"
|
||||||
|
)
|
||||||
|
params.VirtualDirs = [vd]
|
||||||
|
HandleCommandLine(params)
|
@ -0,0 +1,154 @@
|
|||||||
|
# This extension is used mainly for testing purposes - it is not
|
||||||
|
# designed to be a simple sample, but instead is a hotch-potch of things
|
||||||
|
# that attempts to exercise the framework.
|
||||||
|
|
||||||
|
from isapi import isapicon
|
||||||
|
from isapi.simple import SimpleExtension
|
||||||
|
import sys, os, stat
|
||||||
|
|
||||||
|
if hasattr(sys, "isapidllhandle"):
|
||||||
|
import win32traceutil
|
||||||
|
|
||||||
|
# We use the same reload support as 'advanced.py' demonstrates.
|
||||||
|
from isapi import InternalReloadException
|
||||||
|
import win32event, win32file, winerror, win32con, threading
|
||||||
|
|
||||||
|
# A watcher thread that checks for __file__ changing.
|
||||||
|
# When it detects it, it simply sets "change_detected" to true.
|
||||||
|
class ReloadWatcherThread(threading.Thread):
|
||||||
|
def __init__(self):
|
||||||
|
self.change_detected = False
|
||||||
|
self.filename = __file__
|
||||||
|
if self.filename.endswith("c") or self.filename.endswith("o"):
|
||||||
|
self.filename = self.filename[:-1]
|
||||||
|
self.handle = win32file.FindFirstChangeNotification(
|
||||||
|
os.path.dirname(self.filename),
|
||||||
|
False, # watch tree?
|
||||||
|
win32con.FILE_NOTIFY_CHANGE_LAST_WRITE)
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
last_time = os.stat(self.filename)[stat.ST_MTIME]
|
||||||
|
while 1:
|
||||||
|
try:
|
||||||
|
rc = win32event.WaitForSingleObject(self.handle,
|
||||||
|
win32event.INFINITE)
|
||||||
|
win32file.FindNextChangeNotification(self.handle)
|
||||||
|
except win32event.error as details:
|
||||||
|
# handle closed - thread should terminate.
|
||||||
|
if details.winerror != winerror.ERROR_INVALID_HANDLE:
|
||||||
|
raise
|
||||||
|
break
|
||||||
|
this_time = os.stat(self.filename)[stat.ST_MTIME]
|
||||||
|
if this_time != last_time:
|
||||||
|
print("Detected file change - flagging for reload.")
|
||||||
|
self.change_detected = True
|
||||||
|
last_time = this_time
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
win32file.FindCloseChangeNotification(self.handle)
|
||||||
|
|
||||||
|
def TransmitFileCallback(ecb, hFile, cbIO, errCode):
|
||||||
|
print("Transmit complete!")
|
||||||
|
ecb.close()
|
||||||
|
|
||||||
|
# The ISAPI extension - handles requests in our virtual dir, and sends the
|
||||||
|
# response to the client.
|
||||||
|
class Extension(SimpleExtension):
|
||||||
|
"Python test Extension"
|
||||||
|
def __init__(self):
|
||||||
|
self.reload_watcher = ReloadWatcherThread()
|
||||||
|
self.reload_watcher.start()
|
||||||
|
|
||||||
|
def HttpExtensionProc(self, ecb):
|
||||||
|
# NOTE: If you use a ThreadPoolExtension, you must still perform
|
||||||
|
# this check in HttpExtensionProc - raising the exception from
|
||||||
|
# The "Dispatch" method will just cause the exception to be
|
||||||
|
# rendered to the browser.
|
||||||
|
if self.reload_watcher.change_detected:
|
||||||
|
print("Doing reload")
|
||||||
|
raise InternalReloadException
|
||||||
|
|
||||||
|
if ecb.GetServerVariable("UNICODE_URL").endswith("test.py"):
|
||||||
|
file_flags = win32con.FILE_FLAG_SEQUENTIAL_SCAN | win32con.FILE_FLAG_OVERLAPPED
|
||||||
|
hfile = win32file.CreateFile(__file__, win32con.GENERIC_READ,
|
||||||
|
0, None, win32con.OPEN_EXISTING,
|
||||||
|
file_flags, None)
|
||||||
|
flags = isapicon.HSE_IO_ASYNC | isapicon.HSE_IO_DISCONNECT_AFTER_SEND | \
|
||||||
|
isapicon.HSE_IO_SEND_HEADERS
|
||||||
|
# We pass hFile to the callback simply as a way of keeping it alive
|
||||||
|
# for the duration of the transmission
|
||||||
|
try:
|
||||||
|
ecb.TransmitFile(TransmitFileCallback, hfile,
|
||||||
|
int(hfile),
|
||||||
|
"200 OK",
|
||||||
|
0, 0, None, None, flags)
|
||||||
|
except:
|
||||||
|
# Errors keep this source file open!
|
||||||
|
hfile.Close()
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
# default response
|
||||||
|
ecb.SendResponseHeaders("200 OK", "Content-Type: text/html\r\n\r\n", 0)
|
||||||
|
print("<HTML><BODY>", file=ecb)
|
||||||
|
print("The root of this site is at", ecb.MapURLToPath("/"), file=ecb)
|
||||||
|
print("</BODY></HTML>", file=ecb)
|
||||||
|
ecb.close()
|
||||||
|
return isapicon.HSE_STATUS_SUCCESS
|
||||||
|
|
||||||
|
def TerminateExtension(self, status):
|
||||||
|
self.reload_watcher.stop()
|
||||||
|
|
||||||
|
# The entry points for the ISAPI extension.
|
||||||
|
def __ExtensionFactory__():
|
||||||
|
return Extension()
|
||||||
|
|
||||||
|
# Our special command line customization.
|
||||||
|
# Pre-install hook for our virtual directory.
|
||||||
|
def PreInstallDirectory(params, options):
|
||||||
|
# If the user used our special '--description' option,
|
||||||
|
# then we override our default.
|
||||||
|
if options.description:
|
||||||
|
params.Description = options.description
|
||||||
|
|
||||||
|
# Post install hook for our entire script
|
||||||
|
def PostInstall(params, options):
|
||||||
|
print()
|
||||||
|
print("The sample has been installed.")
|
||||||
|
print("Point your browser to /PyISAPITest")
|
||||||
|
|
||||||
|
# Handler for our custom 'status' argument.
|
||||||
|
def status_handler(options, log, arg):
|
||||||
|
"Query the status of something"
|
||||||
|
print("Everything seems to be fine!")
|
||||||
|
|
||||||
|
custom_arg_handlers = {"status": status_handler}
|
||||||
|
|
||||||
|
if __name__=='__main__':
|
||||||
|
# If run from the command-line, install ourselves.
|
||||||
|
from isapi.install import *
|
||||||
|
params = ISAPIParameters(PostInstall = PostInstall)
|
||||||
|
# Setup the virtual directories - this is a list of directories our
|
||||||
|
# extension uses - in this case only 1.
|
||||||
|
# Each extension has a "script map" - this is the mapping of ISAPI
|
||||||
|
# extensions.
|
||||||
|
sm = [
|
||||||
|
ScriptMapParams(Extension="*", Flags=0)
|
||||||
|
]
|
||||||
|
vd = VirtualDirParameters(Name="PyISAPITest",
|
||||||
|
Description = Extension.__doc__,
|
||||||
|
ScriptMaps = sm,
|
||||||
|
ScriptMapUpdate = "replace",
|
||||||
|
# specify the pre-install hook.
|
||||||
|
PreInstall = PreInstallDirectory
|
||||||
|
)
|
||||||
|
params.VirtualDirs = [vd]
|
||||||
|
# Setup our custom option parser.
|
||||||
|
from optparse import OptionParser
|
||||||
|
parser = OptionParser('') # blank usage, so isapi sets it.
|
||||||
|
parser.add_option("", "--description",
|
||||||
|
action="store",
|
||||||
|
help="custom description to use for the virtual directory")
|
||||||
|
|
||||||
|
HandleCommandLine(params, opt_parser=parser,
|
||||||
|
custom_arg_handlers = custom_arg_handlers)
|
@ -0,0 +1,68 @@
|
|||||||
|
"""Simple base-classes for extensions and filters.
|
||||||
|
|
||||||
|
None of the filter and extension functions are considered 'optional' by the
|
||||||
|
framework. These base-classes provide simple implementations for the
|
||||||
|
Initialize and Terminate functions, allowing you to omit them,
|
||||||
|
|
||||||
|
It is not necessary to use these base-classes - but if you don't, you
|
||||||
|
must ensure each of the required methods are implemented.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class SimpleExtension:
|
||||||
|
"Base class for a simple ISAPI extension"
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def GetExtensionVersion(self, vi):
|
||||||
|
"""Called by the ISAPI framework to get the extension version
|
||||||
|
|
||||||
|
The default implementation uses the classes docstring to
|
||||||
|
set the extension description."""
|
||||||
|
# nod to our reload capability - vi is None when we are reloaded.
|
||||||
|
if vi is not None:
|
||||||
|
vi.ExtensionDesc = self.__doc__
|
||||||
|
|
||||||
|
def HttpExtensionProc(self, control_block):
|
||||||
|
"""Called by the ISAPI framework for each extension request.
|
||||||
|
|
||||||
|
sub-classes must provide an implementation for this method.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError("sub-classes should override HttpExtensionProc")
|
||||||
|
|
||||||
|
def TerminateExtension(self, status):
|
||||||
|
"""Called by the ISAPI framework as the extension terminates.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class SimpleFilter:
|
||||||
|
"Base class for a a simple ISAPI filter"
|
||||||
|
filter_flags = None
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def GetFilterVersion(self, fv):
|
||||||
|
"""Called by the ISAPI framework to get the extension version
|
||||||
|
|
||||||
|
The default implementation uses the classes docstring to
|
||||||
|
set the extension description, and uses the classes
|
||||||
|
filter_flags attribute to set the ISAPI filter flags - you
|
||||||
|
must specify filter_flags in your class.
|
||||||
|
"""
|
||||||
|
if self.filter_flags is None:
|
||||||
|
raise RuntimeError("You must specify the filter flags")
|
||||||
|
# nod to our reload capability - fv is None when we are reloaded.
|
||||||
|
if fv is not None:
|
||||||
|
fv.Flags = self.filter_flags
|
||||||
|
fv.FilterDesc = self.__doc__
|
||||||
|
|
||||||
|
def HttpFilterProc(self, fc):
|
||||||
|
"""Called by the ISAPI framework for each filter request.
|
||||||
|
|
||||||
|
sub-classes must provide an implementation for this method.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError("sub-classes should override HttpExtensionProc")
|
||||||
|
|
||||||
|
def TerminateFilter(self, status):
|
||||||
|
"""Called by the ISAPI framework as the filter terminates.
|
||||||
|
"""
|
||||||
|
pass
|
@ -0,0 +1,3 @@
|
|||||||
|
This is a directory for tests of the PyISAPI framework.
|
||||||
|
|
||||||
|
For demos, please see the pyisapi 'samples' directory.
|
Binary file not shown.
@ -0,0 +1,111 @@
|
|||||||
|
# This is an ISAPI extension purely for testing purposes. It is NOT
|
||||||
|
# a 'demo' (even though it may be useful!)
|
||||||
|
#
|
||||||
|
# Install this extension, then point your browser to:
|
||||||
|
# "http://localhost/pyisapi_test/test1"
|
||||||
|
# This will execute the method 'test1' below. See below for the list of
|
||||||
|
# test methods that are acceptable.
|
||||||
|
|
||||||
|
from isapi import isapicon, threaded_extension, ExtensionError
|
||||||
|
from isapi.simple import SimpleFilter
|
||||||
|
import traceback
|
||||||
|
import urllib.request, urllib.parse, urllib.error
|
||||||
|
import winerror
|
||||||
|
|
||||||
|
# If we have no console (eg, am running from inside IIS), redirect output
|
||||||
|
# somewhere useful - in this case, the standard win32 trace collector.
|
||||||
|
import win32api
|
||||||
|
try:
|
||||||
|
win32api.GetConsoleTitle()
|
||||||
|
except win32api.error:
|
||||||
|
# No console - redirect
|
||||||
|
import win32traceutil
|
||||||
|
|
||||||
|
# The ISAPI extension - handles requests in our virtual dir, and sends the
|
||||||
|
# response to the client.
|
||||||
|
class Extension(threaded_extension.ThreadPoolExtension):
|
||||||
|
"Python ISAPI Tester"
|
||||||
|
def Dispatch(self, ecb):
|
||||||
|
print('Tester dispatching "%s"' % (ecb.GetServerVariable("URL"),))
|
||||||
|
url = ecb.GetServerVariable("URL")
|
||||||
|
test_name = url.split("/")[-1]
|
||||||
|
meth = getattr(self, test_name, None)
|
||||||
|
if meth is None:
|
||||||
|
raise AttributeError("No test named '%s'" % (test_name,))
|
||||||
|
result = meth(ecb)
|
||||||
|
if result is None:
|
||||||
|
# This means the test finalized everything
|
||||||
|
return
|
||||||
|
ecb.SendResponseHeaders("200 OK", "Content-type: text/html\r\n\r\n",
|
||||||
|
False)
|
||||||
|
print("<HTML><BODY>Finished running test <i>", test_name, "</i>", file=ecb)
|
||||||
|
print("<pre>", file=ecb)
|
||||||
|
print(result, file=ecb)
|
||||||
|
print("</pre>", file=ecb)
|
||||||
|
print("</BODY></HTML>", file=ecb)
|
||||||
|
ecb.DoneWithSession()
|
||||||
|
|
||||||
|
def test1(self, ecb):
|
||||||
|
try:
|
||||||
|
ecb.GetServerVariable("foo bar")
|
||||||
|
raise RuntimeError("should have failed!")
|
||||||
|
except ExtensionError as err:
|
||||||
|
assert err.errno == winerror.ERROR_INVALID_INDEX, err
|
||||||
|
return "worked!"
|
||||||
|
|
||||||
|
def test_long_vars(self, ecb):
|
||||||
|
qs = ecb.GetServerVariable("QUERY_STRING")
|
||||||
|
# Our implementation has a default buffer size of 8k - so we test
|
||||||
|
# the code that handles an overflow by ensuring there are more
|
||||||
|
# than 8k worth of chars in the URL.
|
||||||
|
expected_query = ('x' * 8500)
|
||||||
|
if len(qs)==0:
|
||||||
|
# Just the URL with no query part - redirect to myself, but with
|
||||||
|
# a huge query portion.
|
||||||
|
me = ecb.GetServerVariable("URL")
|
||||||
|
headers = "Location: " + me + "?" + expected_query + "\r\n\r\n"
|
||||||
|
ecb.SendResponseHeaders("301 Moved", headers)
|
||||||
|
ecb.DoneWithSession()
|
||||||
|
return None
|
||||||
|
if qs == expected_query:
|
||||||
|
return "Total length of variable is %d - test worked!" % (len(qs),)
|
||||||
|
else:
|
||||||
|
return "Unexpected query portion! Got %d chars, expected %d" % \
|
||||||
|
(len(qs), len(expected_query))
|
||||||
|
|
||||||
|
def test_unicode_vars(self, ecb):
|
||||||
|
# We need to check that we are running IIS6! This seems the only
|
||||||
|
# effective way from an extension.
|
||||||
|
ver = float(ecb.GetServerVariable("SERVER_SOFTWARE").split('/')[1])
|
||||||
|
if ver < 6.0:
|
||||||
|
return "This is IIS version %g - unicode only works in IIS6 and later" % ver
|
||||||
|
|
||||||
|
us = ecb.GetServerVariable("UNICODE_SERVER_NAME")
|
||||||
|
if not isinstance(us, str):
|
||||||
|
raise RuntimeError("unexpected type!")
|
||||||
|
if us != str(ecb.GetServerVariable("SERVER_NAME")):
|
||||||
|
raise RuntimeError("Unicode and non-unicode values were not the same")
|
||||||
|
return "worked!"
|
||||||
|
|
||||||
|
# The entry points for the ISAPI extension.
|
||||||
|
def __ExtensionFactory__():
|
||||||
|
return Extension()
|
||||||
|
|
||||||
|
if __name__=='__main__':
|
||||||
|
# If run from the command-line, install ourselves.
|
||||||
|
from isapi.install import *
|
||||||
|
params = ISAPIParameters()
|
||||||
|
# Setup the virtual directories - this is a list of directories our
|
||||||
|
# extension uses - in this case only 1.
|
||||||
|
# Each extension has a "script map" - this is the mapping of ISAPI
|
||||||
|
# extensions.
|
||||||
|
sm = [
|
||||||
|
ScriptMapParams(Extension="*", Flags=0)
|
||||||
|
]
|
||||||
|
vd = VirtualDirParameters(Name="pyisapi_test",
|
||||||
|
Description = Extension.__doc__,
|
||||||
|
ScriptMaps = sm,
|
||||||
|
ScriptMapUpdate = "replace"
|
||||||
|
)
|
||||||
|
params.VirtualDirs = [vd]
|
||||||
|
HandleCommandLine(params)
|
@ -0,0 +1,171 @@
|
|||||||
|
"""An ISAPI extension base class implemented using a thread-pool."""
|
||||||
|
# $Id$
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from isapi import isapicon, ExtensionError
|
||||||
|
import isapi.simple
|
||||||
|
from win32file import GetQueuedCompletionStatus, CreateIoCompletionPort, \
|
||||||
|
PostQueuedCompletionStatus, CloseHandle
|
||||||
|
from win32security import SetThreadToken
|
||||||
|
from win32event import INFINITE
|
||||||
|
from pywintypes import OVERLAPPED
|
||||||
|
|
||||||
|
import threading
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
ISAPI_REQUEST = 1
|
||||||
|
ISAPI_SHUTDOWN = 2
|
||||||
|
|
||||||
|
class WorkerThread(threading.Thread):
|
||||||
|
def __init__(self, extension, io_req_port):
|
||||||
|
self.running = False
|
||||||
|
self.io_req_port = io_req_port
|
||||||
|
self.extension = extension
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
# We wait 15 seconds for a thread to terminate, but if it fails to,
|
||||||
|
# we don't want the process to hang at exit waiting for it...
|
||||||
|
self.setDaemon(True)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.running = True
|
||||||
|
while self.running:
|
||||||
|
errCode, bytes, key, overlapped = \
|
||||||
|
GetQueuedCompletionStatus(self.io_req_port, INFINITE)
|
||||||
|
if key == ISAPI_SHUTDOWN and overlapped is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
# Let the parent extension handle the command.
|
||||||
|
dispatcher = self.extension.dispatch_map.get(key)
|
||||||
|
if dispatcher is None:
|
||||||
|
raise RuntimeError("Bad request '%s'" % (key,))
|
||||||
|
|
||||||
|
dispatcher(errCode, bytes, key, overlapped)
|
||||||
|
|
||||||
|
def call_handler(self, cblock):
|
||||||
|
self.extension.Dispatch(cblock)
|
||||||
|
|
||||||
|
# A generic thread-pool based extension, using IO Completion Ports.
|
||||||
|
# Sub-classes can override one method to implement a simple extension, or
|
||||||
|
# may leverage the CompletionPort to queue their own requests, and implement a
|
||||||
|
# fully asynch extension.
|
||||||
|
class ThreadPoolExtension(isapi.simple.SimpleExtension):
|
||||||
|
"Base class for an ISAPI extension based around a thread-pool"
|
||||||
|
max_workers = 20
|
||||||
|
worker_shutdown_wait = 15000 # 15 seconds for workers to quit...
|
||||||
|
def __init__(self):
|
||||||
|
self.workers = []
|
||||||
|
# extensible dispatch map, for sub-classes that need to post their
|
||||||
|
# own requests to the completion port.
|
||||||
|
# Each of these functions is called with the result of
|
||||||
|
# GetQueuedCompletionStatus for our port.
|
||||||
|
self.dispatch_map = {
|
||||||
|
ISAPI_REQUEST: self.DispatchConnection,
|
||||||
|
}
|
||||||
|
|
||||||
|
def GetExtensionVersion(self, vi):
|
||||||
|
isapi.simple.SimpleExtension.GetExtensionVersion(self, vi)
|
||||||
|
# As per Q192800, the CompletionPort should be created with the number
|
||||||
|
# of processors, even if the number of worker threads is much larger.
|
||||||
|
# Passing 0 means the system picks the number.
|
||||||
|
self.io_req_port = CreateIoCompletionPort(-1, None, 0, 0)
|
||||||
|
# start up the workers
|
||||||
|
self.workers = []
|
||||||
|
for i in range(self.max_workers):
|
||||||
|
worker = WorkerThread(self, self.io_req_port)
|
||||||
|
worker.start()
|
||||||
|
self.workers.append(worker)
|
||||||
|
|
||||||
|
def HttpExtensionProc(self, control_block):
|
||||||
|
overlapped = OVERLAPPED()
|
||||||
|
overlapped.object = control_block
|
||||||
|
PostQueuedCompletionStatus(self.io_req_port, 0, ISAPI_REQUEST, overlapped)
|
||||||
|
return isapicon.HSE_STATUS_PENDING
|
||||||
|
|
||||||
|
def TerminateExtension(self, status):
|
||||||
|
for worker in self.workers:
|
||||||
|
worker.running = False
|
||||||
|
for worker in self.workers:
|
||||||
|
PostQueuedCompletionStatus(self.io_req_port, 0, ISAPI_SHUTDOWN, None)
|
||||||
|
# wait for them to terminate - pity we aren't using 'native' threads
|
||||||
|
# as then we could do a smart wait - but now we need to poll....
|
||||||
|
end_time = time.time() + self.worker_shutdown_wait/1000
|
||||||
|
alive = self.workers
|
||||||
|
while alive:
|
||||||
|
if time.time() > end_time:
|
||||||
|
# xxx - might be nice to log something here.
|
||||||
|
break
|
||||||
|
time.sleep(0.2)
|
||||||
|
alive = [w for w in alive if w.isAlive()]
|
||||||
|
self.dispatch_map = {} # break circles
|
||||||
|
CloseHandle(self.io_req_port)
|
||||||
|
|
||||||
|
# This is the one operation the base class supports - a simple
|
||||||
|
# Connection request. We setup the thread-token, and dispatch to the
|
||||||
|
# sub-class's 'Dispatch' method.
|
||||||
|
def DispatchConnection(self, errCode, bytes, key, overlapped):
|
||||||
|
control_block = overlapped.object
|
||||||
|
# setup the correct user for this request
|
||||||
|
hRequestToken = control_block.GetImpersonationToken()
|
||||||
|
SetThreadToken(None, hRequestToken)
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
self.Dispatch(control_block)
|
||||||
|
except:
|
||||||
|
self.HandleDispatchError(control_block)
|
||||||
|
finally:
|
||||||
|
# reset the security context
|
||||||
|
SetThreadToken(None, None)
|
||||||
|
|
||||||
|
def Dispatch(self, ecb):
|
||||||
|
"""Overridden by the sub-class to handle connection requests.
|
||||||
|
|
||||||
|
This class creates a thread-pool using a Windows completion port,
|
||||||
|
and dispatches requests via this port. Sub-classes can generally
|
||||||
|
implement each connection request using blocking reads and writes, and
|
||||||
|
the thread-pool will still provide decent response to the end user.
|
||||||
|
|
||||||
|
The sub-class can set a max_workers attribute (default is 20). Note
|
||||||
|
that this generally does *not* mean 20 threads will all be concurrently
|
||||||
|
running, via the magic of Windows completion ports.
|
||||||
|
|
||||||
|
There is no default implementation - sub-classes must implement this.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError("sub-classes should override Dispatch")
|
||||||
|
|
||||||
|
def HandleDispatchError(self, ecb):
|
||||||
|
"""Handles errors in the Dispatch method.
|
||||||
|
|
||||||
|
When a Dispatch method call fails, this method is called to handle
|
||||||
|
the exception. The default implementation formats the traceback
|
||||||
|
in the browser.
|
||||||
|
"""
|
||||||
|
ecb.HttpStatusCode = isapicon.HSE_STATUS_ERROR
|
||||||
|
#control_block.LogData = "we failed!"
|
||||||
|
exc_typ, exc_val, exc_tb = sys.exc_info()
|
||||||
|
limit = None
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
import cgi
|
||||||
|
ecb.SendResponseHeaders("200 OK", "Content-type: text/html\r\n\r\n",
|
||||||
|
False)
|
||||||
|
print(file=ecb)
|
||||||
|
print("<H3>Traceback (most recent call last):</H3>", file=ecb)
|
||||||
|
list = traceback.format_tb(exc_tb, limit) + \
|
||||||
|
traceback.format_exception_only(exc_typ, exc_val)
|
||||||
|
print("<PRE>%s<B>%s</B></PRE>" % (
|
||||||
|
cgi.escape("".join(list[:-1])), cgi.escape(list[-1]),), file=ecb)
|
||||||
|
except ExtensionError:
|
||||||
|
# The client disconnected without reading the error body -
|
||||||
|
# its probably not a real browser at the other end, ignore it.
|
||||||
|
pass
|
||||||
|
except:
|
||||||
|
print("FAILED to render the error message!")
|
||||||
|
traceback.print_exc()
|
||||||
|
print("ORIGINAL extension error:")
|
||||||
|
traceback.print_exception(exc_typ, exc_val, exc_tb)
|
||||||
|
finally:
|
||||||
|
# holding tracebacks in a local of a frame that may itself be
|
||||||
|
# part of a traceback used to be evil and cause leaks!
|
||||||
|
exc_tb = None
|
||||||
|
ecb.DoneWithSession()
|
@ -0,0 +1,3 @@
|
|||||||
|
# Magic utility that "redirects" to pythoncomxx.dll
|
||||||
|
import pywintypes
|
||||||
|
pywintypes.__import_pywin32_system_module__("pythoncom", globals())
|
Binary file not shown.
Binary file not shown.
@ -0,0 +1,30 @@
|
|||||||
|
Unless stated in the specfic source file, this work is
|
||||||
|
Copyright (c) 1994-2008, Mark Hammond
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
|
||||||
|
Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in
|
||||||
|
the documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
Neither name of Mark Hammond nor the name of contributors may be used
|
||||||
|
to endorse or promote products derived from this software without
|
||||||
|
specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS
|
||||||
|
IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||||
|
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
|
||||||
|
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
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.
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,221 @@
|
|||||||
|
# basictimerapp - a really simple timer application.
|
||||||
|
# This should be run using the command line:
|
||||||
|
# pythonwin /app demos\basictimerapp.py
|
||||||
|
import win32ui
|
||||||
|
import win32api
|
||||||
|
import win32con
|
||||||
|
import sys
|
||||||
|
from pywin.framework import app, cmdline, dlgappcore, cmdline
|
||||||
|
import timer
|
||||||
|
import time
|
||||||
|
import string
|
||||||
|
|
||||||
|
class TimerAppDialog(dlgappcore.AppDialog):
|
||||||
|
softspace=1
|
||||||
|
def __init__(self, appName = ""):
|
||||||
|
dlgappcore.AppDialog.__init__(self, win32ui.IDD_GENERAL_STATUS)
|
||||||
|
self.timerAppName = appName
|
||||||
|
self.argOff = 0
|
||||||
|
if len(self.timerAppName)==0:
|
||||||
|
if len(sys.argv)>1 and sys.argv[1][0]!='/':
|
||||||
|
self.timerAppName = sys.argv[1]
|
||||||
|
self.argOff = 1
|
||||||
|
|
||||||
|
def PreDoModal(self):
|
||||||
|
# sys.stderr = sys.stdout
|
||||||
|
pass
|
||||||
|
|
||||||
|
def ProcessArgs(self, args):
|
||||||
|
for arg in args:
|
||||||
|
if arg=="/now":
|
||||||
|
self.OnOK()
|
||||||
|
|
||||||
|
def OnInitDialog(self):
|
||||||
|
win32ui.SetProfileFileName('pytimer.ini')
|
||||||
|
self.title = win32ui.GetProfileVal(self.timerAppName, "Title", "Remote System Timer")
|
||||||
|
self.buildTimer = win32ui.GetProfileVal(self.timerAppName, "Timer", "EachMinuteIntervaler()")
|
||||||
|
self.doWork = win32ui.GetProfileVal(self.timerAppName, "Work", "DoDemoWork()")
|
||||||
|
# replace "\n" with real \n.
|
||||||
|
self.doWork = self.doWork.replace('\\n','\n')
|
||||||
|
dlgappcore.AppDialog.OnInitDialog(self)
|
||||||
|
|
||||||
|
self.SetWindowText(self.title)
|
||||||
|
self.prompt1 = self.GetDlgItem(win32ui.IDC_PROMPT1)
|
||||||
|
self.prompt2 = self.GetDlgItem(win32ui.IDC_PROMPT2)
|
||||||
|
self.prompt3 = self.GetDlgItem(win32ui.IDC_PROMPT3)
|
||||||
|
self.butOK = self.GetDlgItem(win32con.IDOK)
|
||||||
|
self.butCancel = self.GetDlgItem(win32con.IDCANCEL)
|
||||||
|
self.prompt1.SetWindowText("Python Timer App")
|
||||||
|
self.prompt2.SetWindowText("")
|
||||||
|
self.prompt3.SetWindowText("")
|
||||||
|
self.butOK.SetWindowText("Do it now")
|
||||||
|
self.butCancel.SetWindowText("Close")
|
||||||
|
|
||||||
|
self.timerManager = TimerManager(self)
|
||||||
|
self.ProcessArgs(sys.argv[self.argOff:])
|
||||||
|
self.timerManager.go()
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def OnDestroy(self,msg):
|
||||||
|
dlgappcore.AppDialog.OnDestroy(self, msg)
|
||||||
|
self.timerManager.stop()
|
||||||
|
def OnOK(self):
|
||||||
|
# stop the timer, then restart after setting special boolean
|
||||||
|
self.timerManager.stop()
|
||||||
|
self.timerManager.bConnectNow = 1
|
||||||
|
self.timerManager.go()
|
||||||
|
return
|
||||||
|
# def OnCancel(self): default behaviour - cancel == close.
|
||||||
|
# return
|
||||||
|
|
||||||
|
class TimerManager:
|
||||||
|
def __init__(self, dlg):
|
||||||
|
self.dlg = dlg
|
||||||
|
self.timerId = None
|
||||||
|
self.intervaler = eval(self.dlg.buildTimer)
|
||||||
|
self.bConnectNow = 0
|
||||||
|
self.bHaveSetPrompt1 = 0
|
||||||
|
def CaptureOutput(self):
|
||||||
|
self.oldOut = sys.stdout
|
||||||
|
self.oldErr = sys.stderr
|
||||||
|
sys.stdout = sys.stderr = self
|
||||||
|
self.bHaveSetPrompt1 = 0
|
||||||
|
def ReleaseOutput(self):
|
||||||
|
sys.stdout = self.oldOut
|
||||||
|
sys.stderr = self.oldErr
|
||||||
|
def write(self, str):
|
||||||
|
s = str.strip()
|
||||||
|
if len(s):
|
||||||
|
if self.bHaveSetPrompt1:
|
||||||
|
dest = self.dlg.prompt3
|
||||||
|
else:
|
||||||
|
dest = self.dlg.prompt1
|
||||||
|
self.bHaveSetPrompt1 = 1
|
||||||
|
dest.SetWindowText(s)
|
||||||
|
def go(self):
|
||||||
|
self.OnTimer(None,None)
|
||||||
|
def stop(self):
|
||||||
|
if self.timerId: timer.kill_timer (self.timerId)
|
||||||
|
self.timerId = None
|
||||||
|
|
||||||
|
def OnTimer(self, id, timeVal):
|
||||||
|
if id: timer.kill_timer (id)
|
||||||
|
if self.intervaler.IsTime() or self.bConnectNow :
|
||||||
|
# do the work.
|
||||||
|
try:
|
||||||
|
self.dlg.SetWindowText(self.dlg.title + " - Working...")
|
||||||
|
self.dlg.butOK.EnableWindow(0)
|
||||||
|
self.dlg.butCancel.EnableWindow(0)
|
||||||
|
self.CaptureOutput()
|
||||||
|
try:
|
||||||
|
exec(self.dlg.doWork)
|
||||||
|
print("The last operation completed successfully.")
|
||||||
|
except:
|
||||||
|
t, v, tb = sys.exc_info()
|
||||||
|
str = "Failed: %s: %s" % (t, repr(v))
|
||||||
|
print(str)
|
||||||
|
self.oldErr.write(str)
|
||||||
|
tb = None # Prevent cycle
|
||||||
|
finally:
|
||||||
|
self.ReleaseOutput()
|
||||||
|
self.dlg.butOK.EnableWindow()
|
||||||
|
self.dlg.butCancel.EnableWindow()
|
||||||
|
self.dlg.SetWindowText(self.dlg.title)
|
||||||
|
else:
|
||||||
|
now = time.time()
|
||||||
|
nextTime = self.intervaler.GetNextTime()
|
||||||
|
if nextTime:
|
||||||
|
timeDiffSeconds = nextTime - now
|
||||||
|
timeDiffMinutes = int(timeDiffSeconds / 60)
|
||||||
|
timeDiffSeconds = timeDiffSeconds % 60
|
||||||
|
timeDiffHours = int(timeDiffMinutes / 60)
|
||||||
|
timeDiffMinutes = timeDiffMinutes % 60
|
||||||
|
self.dlg.prompt1.SetWindowText("Next connection due in %02d:%02d:%02d" % (timeDiffHours,timeDiffMinutes,timeDiffSeconds))
|
||||||
|
self.timerId = timer.set_timer (self.intervaler.GetWakeupInterval(), self.OnTimer)
|
||||||
|
self.bConnectNow = 0
|
||||||
|
|
||||||
|
class TimerIntervaler:
|
||||||
|
def __init__(self):
|
||||||
|
self.nextTime = None
|
||||||
|
self.wakeUpInterval = 2000
|
||||||
|
def GetWakeupInterval(self):
|
||||||
|
return self.wakeUpInterval
|
||||||
|
def GetNextTime(self):
|
||||||
|
return self.nextTime
|
||||||
|
def IsTime(self):
|
||||||
|
now = time.time()
|
||||||
|
if self.nextTime is None:
|
||||||
|
self.nextTime = self.SetFirstTime(now)
|
||||||
|
ret = 0
|
||||||
|
if now >= self.nextTime:
|
||||||
|
ret = 1
|
||||||
|
self.nextTime = self.SetNextTime(self.nextTime, now)
|
||||||
|
# do the work.
|
||||||
|
return ret
|
||||||
|
|
||||||
|
class EachAnyIntervaler(TimerIntervaler):
|
||||||
|
def __init__(self, timeAt, timePos, timeAdd, wakeUpInterval = None):
|
||||||
|
TimerIntervaler.__init__(self)
|
||||||
|
self.timeAt = timeAt
|
||||||
|
self.timePos = timePos
|
||||||
|
self.timeAdd = timeAdd
|
||||||
|
if wakeUpInterval:
|
||||||
|
self.wakeUpInterval = wakeUpInterval
|
||||||
|
def SetFirstTime(self, now):
|
||||||
|
timeTup = time.localtime(now)
|
||||||
|
lst = []
|
||||||
|
for item in timeTup:
|
||||||
|
lst.append(item)
|
||||||
|
bAdd = timeTup[self.timePos] > self.timeAt
|
||||||
|
lst[self.timePos] = self.timeAt
|
||||||
|
for pos in range(self.timePos+1, 6):
|
||||||
|
lst[pos]=0
|
||||||
|
ret = time.mktime(tuple(lst))
|
||||||
|
if (bAdd):
|
||||||
|
ret = ret + self.timeAdd
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
def SetNextTime(self, lastTime, now):
|
||||||
|
return lastTime + self.timeAdd
|
||||||
|
|
||||||
|
class EachMinuteIntervaler(EachAnyIntervaler):
|
||||||
|
def __init__(self, at=0):
|
||||||
|
EachAnyIntervaler.__init__(self, at, 5, 60, 2000)
|
||||||
|
|
||||||
|
class EachHourIntervaler(EachAnyIntervaler):
|
||||||
|
def __init__(self, at=0):
|
||||||
|
EachAnyIntervaler.__init__(self, at, 4, 3600, 10000)
|
||||||
|
|
||||||
|
class EachDayIntervaler(EachAnyIntervaler):
|
||||||
|
def __init__(self,at=0):
|
||||||
|
EachAnyIntervaler.__init__(self, at, 3, 86400, 10000)
|
||||||
|
|
||||||
|
class TimerDialogApp(dlgappcore.DialogApp):
|
||||||
|
def CreateDialog(self):
|
||||||
|
return TimerAppDialog()
|
||||||
|
|
||||||
|
def DoDemoWork():
|
||||||
|
print("Doing the work...")
|
||||||
|
print("About to connect")
|
||||||
|
win32api.MessageBeep(win32con.MB_ICONASTERISK)
|
||||||
|
win32api.Sleep(2000)
|
||||||
|
print("Doing something else...")
|
||||||
|
win32api.MessageBeep(win32con.MB_ICONEXCLAMATION)
|
||||||
|
win32api.Sleep(2000)
|
||||||
|
print("More work.")
|
||||||
|
win32api.MessageBeep(win32con.MB_ICONHAND)
|
||||||
|
win32api.Sleep(2000)
|
||||||
|
print("The last bit.")
|
||||||
|
win32api.MessageBeep(win32con.MB_OK)
|
||||||
|
win32api.Sleep(2000)
|
||||||
|
|
||||||
|
app = TimerDialogApp()
|
||||||
|
|
||||||
|
def t():
|
||||||
|
t = TimerAppDialog("Test Dialog")
|
||||||
|
t.DoModal()
|
||||||
|
return t
|
||||||
|
|
||||||
|
if __name__=='__main__':
|
||||||
|
import demoutils
|
||||||
|
demoutils.NeedApp()
|
@ -0,0 +1,194 @@
|
|||||||
|
# A demo of an Application object that has some custom print functionality.
|
||||||
|
|
||||||
|
# If you desire, you can also run this from inside Pythonwin, in which
|
||||||
|
# case it will do the demo inside the Pythonwin environment.
|
||||||
|
|
||||||
|
# This sample was contributed by Roger Burnham.
|
||||||
|
|
||||||
|
from pywin.mfc import docview, dialog, afxres
|
||||||
|
from pywin.framework import app
|
||||||
|
|
||||||
|
import win32con
|
||||||
|
import win32ui
|
||||||
|
import win32api
|
||||||
|
|
||||||
|
PRINTDLGORD = 1538
|
||||||
|
IDC_PRINT_MAG_EDIT = 1010
|
||||||
|
|
||||||
|
|
||||||
|
class PrintDemoTemplate(docview.DocTemplate):
|
||||||
|
def _SetupSharedMenu_(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class PrintDemoView(docview.ScrollView):
|
||||||
|
|
||||||
|
def OnInitialUpdate(self):
|
||||||
|
ret = self._obj_.OnInitialUpdate()
|
||||||
|
self.colors = {'Black' : (0x00<<0) + (0x00<<8) + (0x00<<16),
|
||||||
|
'Red' : (0xff<<0) + (0x00<<8) + (0x00<<16),
|
||||||
|
'Green' : (0x00<<0) + (0xff<<8) + (0x00<<16),
|
||||||
|
'Blue' : (0x00<<0) + (0x00<<8) + (0xff<<16),
|
||||||
|
'Cyan' : (0x00<<0) + (0xff<<8) + (0xff<<16),
|
||||||
|
'Magenta': (0xff<<0) + (0x00<<8) + (0xff<<16),
|
||||||
|
'Yellow' : (0xff<<0) + (0xff<<8) + (0x00<<16),
|
||||||
|
}
|
||||||
|
self.pens = {}
|
||||||
|
for name, color in self.colors.items():
|
||||||
|
self.pens[name] = win32ui.CreatePen(win32con.PS_SOLID,
|
||||||
|
5, color)
|
||||||
|
self.pen = None
|
||||||
|
self.size = (128,128)
|
||||||
|
self.SetScaleToFitSize(self.size)
|
||||||
|
self.HookCommand(self.OnFilePrint, afxres.ID_FILE_PRINT)
|
||||||
|
self.HookCommand(self.OnFilePrintPreview,
|
||||||
|
win32ui.ID_FILE_PRINT_PREVIEW)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def OnDraw(self, dc):
|
||||||
|
oldPen = None
|
||||||
|
x,y = self.size
|
||||||
|
delta = 2
|
||||||
|
colors = list(self.colors.keys())
|
||||||
|
colors.sort()
|
||||||
|
colors = colors*2
|
||||||
|
for color in colors:
|
||||||
|
if oldPen is None:
|
||||||
|
oldPen = dc.SelectObject(self.pens[color])
|
||||||
|
else:
|
||||||
|
dc.SelectObject(self.pens[color])
|
||||||
|
dc.MoveTo(( delta, delta))
|
||||||
|
dc.LineTo((x-delta, delta))
|
||||||
|
dc.LineTo((x-delta, y-delta))
|
||||||
|
dc.LineTo(( delta, y-delta))
|
||||||
|
dc.LineTo(( delta, delta))
|
||||||
|
delta = delta + 4
|
||||||
|
if x-delta <= 0 or y-delta <= 0:
|
||||||
|
break
|
||||||
|
dc.SelectObject(oldPen)
|
||||||
|
|
||||||
|
def OnPrepareDC (self, dc, pInfo):
|
||||||
|
if dc.IsPrinting():
|
||||||
|
mag = self.prtDlg['mag']
|
||||||
|
dc.SetMapMode(win32con.MM_ANISOTROPIC);
|
||||||
|
dc.SetWindowOrg((0, 0))
|
||||||
|
dc.SetWindowExt((1, 1))
|
||||||
|
dc.SetViewportOrg((0, 0))
|
||||||
|
dc.SetViewportExt((mag, mag))
|
||||||
|
|
||||||
|
def OnPreparePrinting(self, pInfo):
|
||||||
|
flags = (win32ui.PD_USEDEVMODECOPIES|
|
||||||
|
win32ui.PD_PAGENUMS|
|
||||||
|
win32ui.PD_NOPAGENUMS|
|
||||||
|
win32ui.PD_NOSELECTION)
|
||||||
|
self.prtDlg = ImagePrintDialog(pInfo, PRINTDLGORD, flags)
|
||||||
|
pInfo.SetPrintDialog(self.prtDlg)
|
||||||
|
pInfo.SetMinPage(1)
|
||||||
|
pInfo.SetMaxPage(1)
|
||||||
|
pInfo.SetFromPage(1)
|
||||||
|
pInfo.SetToPage(1)
|
||||||
|
ret = self.DoPreparePrinting(pInfo)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def OnBeginPrinting(self, dc, pInfo):
|
||||||
|
return self._obj_.OnBeginPrinting(dc, pInfo)
|
||||||
|
|
||||||
|
def OnEndPrinting(self, dc, pInfo):
|
||||||
|
del self.prtDlg
|
||||||
|
return self._obj_.OnEndPrinting(dc, pInfo)
|
||||||
|
|
||||||
|
def OnFilePrintPreview(self, *arg):
|
||||||
|
self._obj_.OnFilePrintPreview()
|
||||||
|
|
||||||
|
def OnFilePrint(self, *arg):
|
||||||
|
self._obj_.OnFilePrint()
|
||||||
|
|
||||||
|
def OnPrint(self, dc, pInfo):
|
||||||
|
doc = self.GetDocument()
|
||||||
|
metrics = dc.GetTextMetrics()
|
||||||
|
cxChar = metrics['tmAveCharWidth']
|
||||||
|
cyChar = metrics['tmHeight']
|
||||||
|
left, top, right, bottom = pInfo.GetDraw()
|
||||||
|
dc.TextOut(0, 2*cyChar, doc.GetTitle())
|
||||||
|
top = top + (7*cyChar)/2
|
||||||
|
dc.MoveTo(left, top)
|
||||||
|
dc.LineTo(right, top)
|
||||||
|
top = top + cyChar
|
||||||
|
# this seems to have not effect...
|
||||||
|
# get what I want with the dc.SetWindowOrg calls
|
||||||
|
pInfo.SetDraw((left, top, right, bottom))
|
||||||
|
dc.SetWindowOrg((0, -top))
|
||||||
|
|
||||||
|
self.OnDraw(dc)
|
||||||
|
dc.SetTextAlign(win32con.TA_LEFT|win32con.TA_BOTTOM)
|
||||||
|
|
||||||
|
rect = self.GetWindowRect()
|
||||||
|
rect = self.ScreenToClient(rect)
|
||||||
|
height = (rect[3]-rect[1])
|
||||||
|
dc.SetWindowOrg((0, -(top+height+cyChar)))
|
||||||
|
dc.MoveTo(left, 0)
|
||||||
|
dc.LineTo(right, 0)
|
||||||
|
|
||||||
|
x = 0
|
||||||
|
y = (3*cyChar)/2
|
||||||
|
|
||||||
|
dc.TextOut(x, y, doc.GetTitle())
|
||||||
|
y = y + cyChar
|
||||||
|
|
||||||
|
|
||||||
|
class PrintDemoApp(app.CApp):
|
||||||
|
def __init__(self):
|
||||||
|
app.CApp.__init__(self)
|
||||||
|
|
||||||
|
def InitInstance(self):
|
||||||
|
template = PrintDemoTemplate(None, None,
|
||||||
|
None, PrintDemoView)
|
||||||
|
self.AddDocTemplate(template)
|
||||||
|
self._obj_.InitMDIInstance()
|
||||||
|
self.LoadMainFrame()
|
||||||
|
doc = template.OpenDocumentFile(None)
|
||||||
|
doc.SetTitle('Custom Print Document')
|
||||||
|
|
||||||
|
|
||||||
|
class ImagePrintDialog(dialog.PrintDialog):
|
||||||
|
|
||||||
|
sectionPos = 'Image Print Demo'
|
||||||
|
|
||||||
|
def __init__(self, pInfo, dlgID, flags=win32ui.PD_USEDEVMODECOPIES):
|
||||||
|
dialog.PrintDialog.__init__(self, pInfo, dlgID, flags=flags)
|
||||||
|
mag = win32ui.GetProfileVal(self.sectionPos,
|
||||||
|
'Document Magnification',
|
||||||
|
0)
|
||||||
|
if mag <= 0:
|
||||||
|
mag = 2
|
||||||
|
win32ui.WriteProfileVal(self.sectionPos,
|
||||||
|
'Document Magnification',
|
||||||
|
mag)
|
||||||
|
|
||||||
|
self['mag'] = mag
|
||||||
|
|
||||||
|
def OnInitDialog(self):
|
||||||
|
self.magCtl = self.GetDlgItem(IDC_PRINT_MAG_EDIT)
|
||||||
|
self.magCtl.SetWindowText(repr(self['mag']))
|
||||||
|
return dialog.PrintDialog.OnInitDialog(self)
|
||||||
|
def OnOK(self):
|
||||||
|
dialog.PrintDialog.OnOK(self)
|
||||||
|
strMag = self.magCtl.GetWindowText()
|
||||||
|
try:
|
||||||
|
self['mag'] = int(strMag)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
win32ui.WriteProfileVal(self.sectionPos,
|
||||||
|
'Document Magnification',
|
||||||
|
self['mag'])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__=='__main__':
|
||||||
|
# Running under Pythonwin
|
||||||
|
def test():
|
||||||
|
template = PrintDemoTemplate(None, None,
|
||||||
|
None, PrintDemoView)
|
||||||
|
template.OpenDocumentFile(None)
|
||||||
|
test()
|
||||||
|
else:
|
||||||
|
app = PrintDemoApp()
|
||||||
|
|
@ -0,0 +1,52 @@
|
|||||||
|
# Utilities for the demos
|
||||||
|
|
||||||
|
import sys, win32api, win32con, win32ui
|
||||||
|
|
||||||
|
NotScriptMsg = """\
|
||||||
|
This demo program is not designed to be run as a Script, but is
|
||||||
|
probably used by some other test program. Please try another demo.
|
||||||
|
"""
|
||||||
|
|
||||||
|
NeedGUIMsg = """\
|
||||||
|
This demo program can only be run from inside of Pythonwin
|
||||||
|
|
||||||
|
You must start Pythonwin, and select 'Run' from the toolbar or File menu
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
NeedAppMsg = """\
|
||||||
|
This demo program is a 'Pythonwin Application'.
|
||||||
|
|
||||||
|
It is more demo code than an example of Pythonwin's capabilities.
|
||||||
|
|
||||||
|
To run it, you must execute the command:
|
||||||
|
pythonwin.exe /app "%s"
|
||||||
|
|
||||||
|
Would you like to execute it now?
|
||||||
|
"""
|
||||||
|
|
||||||
|
def NotAScript():
|
||||||
|
import win32ui
|
||||||
|
win32ui.MessageBox(NotScriptMsg, "Demos")
|
||||||
|
|
||||||
|
def NeedGoodGUI():
|
||||||
|
from pywin.framework.app import HaveGoodGUI
|
||||||
|
rc = HaveGoodGUI()
|
||||||
|
if not rc:
|
||||||
|
win32ui.MessageBox(NeedGUIMsg, "Demos")
|
||||||
|
return rc
|
||||||
|
|
||||||
|
def NeedApp():
|
||||||
|
import win32ui
|
||||||
|
rc = win32ui.MessageBox(NeedAppMsg % sys.argv[0], "Demos", win32con.MB_YESNO)
|
||||||
|
if rc==win32con.IDYES:
|
||||||
|
try:
|
||||||
|
parent = win32ui.GetMainFrame().GetSafeHwnd()
|
||||||
|
win32api.ShellExecute(parent, None, 'pythonwin.exe', '/app "%s"' % sys.argv[0], None, 1)
|
||||||
|
except win32api.error as details:
|
||||||
|
win32ui.MessageBox("Error executing command - %s" % (details), "Demos")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__=='__main__':
|
||||||
|
import demoutils
|
||||||
|
demoutils.NotAScript()
|
@ -0,0 +1,46 @@
|
|||||||
|
# dlgappdemo - a demo of a dialog application.
|
||||||
|
# This is a demonstration of both a custom "application" module,
|
||||||
|
# and a Python program in a dialog box.
|
||||||
|
#
|
||||||
|
# NOTE: You CAN NOT import this module from either PythonWin or Python.
|
||||||
|
# This module must be specified on the commandline to PythonWin only.
|
||||||
|
# eg, PythonWin /app dlgappdemo.py
|
||||||
|
|
||||||
|
from pywin.framework import dlgappcore, app
|
||||||
|
import win32ui
|
||||||
|
import sys
|
||||||
|
|
||||||
|
class TestDialogApp(dlgappcore.DialogApp):
|
||||||
|
def CreateDialog(self):
|
||||||
|
return TestAppDialog()
|
||||||
|
|
||||||
|
|
||||||
|
class TestAppDialog(dlgappcore.AppDialog):
|
||||||
|
def __init__(self):
|
||||||
|
self.edit = None
|
||||||
|
dlgappcore.AppDialog.__init__(self, win32ui.IDD_LARGE_EDIT)
|
||||||
|
def OnInitDialog(self):
|
||||||
|
self.SetWindowText('Test dialog application')
|
||||||
|
self.edit = self.GetDlgItem(win32ui.IDC_EDIT1)
|
||||||
|
print("Hello from Python")
|
||||||
|
print("args are:", end=' ')
|
||||||
|
for arg in sys.argv:
|
||||||
|
print(arg)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def PreDoModal(self):
|
||||||
|
sys.stdout = sys.stderr = self
|
||||||
|
|
||||||
|
def write(self, str):
|
||||||
|
if self.edit:
|
||||||
|
self.edit.SetSel(-2)
|
||||||
|
# translate \n to \n\r
|
||||||
|
self.edit.ReplaceSel(str.replace('\n','\r\n'))
|
||||||
|
else:
|
||||||
|
win32ui.OutputDebug("dlgapp - no edit control! >>\n%s\n<<\n" % str )
|
||||||
|
|
||||||
|
app.AppBuilder = TestDialogApp
|
||||||
|
|
||||||
|
if __name__=='__main__':
|
||||||
|
import demoutils
|
||||||
|
demoutils.NeedApp()
|
@ -0,0 +1,62 @@
|
|||||||
|
# dojobapp - do a job, show the result in a dialog, and exit.
|
||||||
|
#
|
||||||
|
# Very simple - faily minimal dialog based app.
|
||||||
|
#
|
||||||
|
# This should be run using the command line:
|
||||||
|
# pythonwin /app demos\dojobapp.py
|
||||||
|
|
||||||
|
import win32ui
|
||||||
|
import win32api
|
||||||
|
import win32con
|
||||||
|
import sys
|
||||||
|
from pywin.framework import app, dlgappcore
|
||||||
|
import string
|
||||||
|
|
||||||
|
class DoJobAppDialog(dlgappcore.AppDialog):
|
||||||
|
softspace=1
|
||||||
|
def __init__(self, appName = ""):
|
||||||
|
self.appName = appName
|
||||||
|
dlgappcore.AppDialog.__init__(self, win32ui.IDD_GENERAL_STATUS)
|
||||||
|
|
||||||
|
def PreDoModal(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def ProcessArgs(self, args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def OnInitDialog(self):
|
||||||
|
self.SetWindowText(self.appName)
|
||||||
|
butCancel = self.GetDlgItem(win32con.IDCANCEL)
|
||||||
|
butCancel.ShowWindow(win32con.SW_HIDE)
|
||||||
|
p1 = self.GetDlgItem(win32ui.IDC_PROMPT1)
|
||||||
|
p2 = self.GetDlgItem(win32ui.IDC_PROMPT2)
|
||||||
|
|
||||||
|
# Do something here!
|
||||||
|
|
||||||
|
p1.SetWindowText("Hello there")
|
||||||
|
p2.SetWindowText("from the demo")
|
||||||
|
def OnDestroy(self,msg):
|
||||||
|
pass
|
||||||
|
# def OnOK(self):
|
||||||
|
# pass
|
||||||
|
# def OnCancel(self): default behaviour - cancel == close.
|
||||||
|
# return
|
||||||
|
|
||||||
|
class DoJobDialogApp(dlgappcore.DialogApp):
|
||||||
|
def CreateDialog(self):
|
||||||
|
return DoJobAppDialog("Do Something")
|
||||||
|
|
||||||
|
class CopyToDialogApp(DoJobDialogApp):
|
||||||
|
def __init__(self):
|
||||||
|
DoJobDialogApp.__init__(self)
|
||||||
|
|
||||||
|
app.AppBuilder = DoJobDialogApp
|
||||||
|
|
||||||
|
def t():
|
||||||
|
t = DoJobAppDialog("Copy To")
|
||||||
|
t.DoModal()
|
||||||
|
return t
|
||||||
|
|
||||||
|
if __name__=='__main__':
|
||||||
|
import demoutils
|
||||||
|
demoutils.NeedApp()
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue