SSL access2base.py
Interaktion und PortierbarkeitPython
# -*- coding: utf-8 -*-
# Copyright 2012-2020 Jean-Pierre LEDURE
# ===================================================================================================================== # === The Access2Base library is a part of the LibreOffice project. === # === Full documentation is available on http://www.access2base.com === # =====================================================================================================================
# Access2Base 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.
# Access2Base is free software; you can redistribute it and/or modify it under the terms of either (at your option):
# 1) The Mozilla Public License, v. 2.0. If a copy of the MPL was not # distributed with this file, you can obtain one at http://mozilla.org/MPL/2.0/ .
# 2) The GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. If a copy of the LGPL was not # distributed with this file, see http://www.gnu.org/licenses/ .
"""
The access2base.py module implements an interface between Python (user) scripts and the Access2Base Basic library.
Usage: from access2base import *
Additionally, if Python and LibreOffice are started in separate processes: If LibreOffice started from console ... (example for Linux)
./soffice --accept='socket,host=localhost,port=2019;urp;'
then insert next statement
A2BConnect(hostname = 'localhost', port = 2019)
from platform import system as _opsys import datetime import os import sys import traceback
_LIBRARY = ''# Should be 'Access2Base' or 'Access2BaseDev'
_VERSION = '7.4'# Actual version number
_WRAPPERMODULE = 'Python'# Module name in the Access2Base library containing Python interfaces
class _Singleton(type): """
A Singleton design pattern
Credits: « Python in a Nutshell » by Alex Martelli, O'Reilly """
instances = {} def __call__(cls, *args, **kwargs): if cls notin cls.instances:
cls.instances[cls] = super(_Singleton, cls).__call__(*args, **kwargs) return cls.instances[cls]
class acConstants(object, metaclass = _Singleton): """
VBA constants used in the Access2Base API.
Values derived from MSAccess, except when conflicts """ # Python special constants (used in the protocol between Python and Basic) # -----------------------------------------------------------------
Empty = '+++EMPTY+++'
Null = '+++NULL+++'
Missing = '+++MISSING+++'
FromIsoFormat = '%Y-%m-%d %H:%M:%S'# To be used with datetime.datetime.strptime()
# (Module) procedure types # -----------------------------------------------------------------
vbext_pk_Get = 1 # A Property Get procedure
vbext_pk_Let = 2 # A Property Let procedure
vbext_pk_Proc = 0 # A Sub or Function procedure
vbext_pk_Set = 3 # A Property Set procedure
def _ErrorHandler(type, value, tb): ''' Is the function to be set as new sys.excepthook to bypass the standard error handler
Derived fromhttps://stackoverflow.com/questions/31949760/how-to-limit-python-traceback-to-specific-files
Handler removes traces pointing to methods located in access2base.py when error is due to a user programming error
sys.excepthook = _ErrorHandler NOT APPLIED YET '''
def check_file(name): return'access2base.py'notin name
show = (fs for fs in traceback.extract_tb(tb) if check_file(fs.filename))
fmt = traceback.format_list(show) + traceback.format_exception_only(type, value)
print(''.join(fmt), end = '', file = sys.stderr) # Reset to standard handler
sys.excepthook = sys.__excepthook__
def A2BConnect(hostname = '', port = 0): """
To be called explicitly by user scripts when Python process runs outside the LibreOffice process.
LibreOffice started as (Linux):
./soffice --accept='socket,host=localhost,port=xxxx;urp;'
Otherwise called implicitly by the current module without arguments
Initializes COMPONENTCONTEXT, SCRIPTPROVIDER and DESKTOP
:param hostname: probably 'localhost'or''
:param port: port number or 0
:return: None """ global XSCRIPTCONTEXT, COMPONENTCONTEXT, DESKTOP, SCRIPTPROVIDER # Determine COMPONENTCONTEXT, via socket or inside LibreOffice if len(hostname) > 0 and port > 0: # Explicit connection request via socket # Code derived from Bridge.py by Alain H. Romedenne
local_context = XSCRIPTCONTEXT.getComponentContext()
resolver = local_context.ServiceManager.createInstanceWithContext( 'com.sun.star.bridge.UnoUrlResolver', local_context) try:
conn = 'socket,host=%s,port=%d' % (hostname, port)
connection_url = 'uno:%s;urp;StarOffice.ComponentContext' % conn
established_context = resolver.resolve(connection_url) except Exception: # thrown when LibreOffice specified instance isn't started raise ConnectionError('Connection to LibreOffice failed (host = ' + hostname + ', port = ' + str(port) + ')')
COMPONENTCONTEXT = established_context
DESKTOP = None elif len(hostname) == 0 and port == 0: # Usual interactive mode
COMPONENTCONTEXT = XSCRIPTCONTEXT.getComponentContext()
DESKTOP = COMPONENTCONTEXT.ServiceManager.createInstanceWithContext( 'com.sun.star.frame.Desktop', COMPONENTCONTEXT) else: raise SystemExit('The invocation of A2BConnect() has invalid arguments') # Determine SCRIPTPROVIDER
servicemanager = COMPONENTCONTEXT.ServiceManager
masterscript = servicemanager.createInstanceWithContext("com.sun.star.script.provider.MasterScriptProviderFactory", COMPONENTCONTEXT)
SCRIPTPROVIDER = masterscript.createScriptProvider("")
Script = _A2B.xScript('TraceLog', 'Trace') # Don't use invokeMethod() to force reset of error stack
Script.invoke(('===>', 'Python wrapper loaded V.' + _VERSION, False), (), ()) returnNone
class _A2B(object, metaclass = _Singleton): """
Collection of helper functions implementing the protocol between Python and Basic
Read comments in PythonWrapper Basic function """
@classmethod def xScript(cls, script, module): """
At first call checks the existence of the Access2Base library
Initializes _LIBRARY with the found library name
First and next calls execute the given script in the given module of the _LIBRARY library
The script and module are presumed to exist
:param script: name of script
:param module: name of module
:return: the script object. NB: the execution is done with the invoke() method applied on the returned object """ global _LIBRARY
Script = None def sScript(lib): return'vnd.sun.star.script:' + lib + '.' + module + '.' + script + '?language=Basic&location=application' if _LIBRARY == '': # Check the availability of the Access2Base library for lib in ('Access2BaseDev', 'Access2Base'): try: if Script isNone:
Script = SCRIPTPROVIDER.getScript(sScript(lib))
_LIBRARY = lib except Exception: pass if Script isNone: raise SystemExit('Access2Base basic library not found') else:
Script = SCRIPTPROVIDER.getScript(sScript(_LIBRARY)) return Script
@classmethod def A2BErrorCode(cls): """ Return the Access2Base error stack as a tuple
0 => error code
1 => severity level
2 => short error message
3 => long error message """
Script = cls.xScript('TraceErrorCode', 'Trace') return Script.invoke((), (), ())[0]
@classmethod def invokeMethod(cls, script, module, *args): """
Direct call to a named script/module pair with their arguments If the arguments do not match their definition at the Basic side, a TypeError is raised
:param script: name of script
:param module: name of module
:param args: list of arguments to be passed to the script
:return: the value returned by the script execution """ if COMPONENTCONTEXT isNone:
A2BConnect() # Connection from inside LibreOffice is done at first API invocation
Script = cls.xScript(script, module) try:
Returned = Script.invoke((args), (), ())[0] except Exception: raise TypeError("Access2Base error: method '" + script + "' in Basic module '" + module + "' call error. Check its arguments.") else: if Returned isNone: if cls.VerifyNoError(): returnNone return Returned
@classmethod def invokeWrapper(cls, action, basic, script, *args): """
Call the Basic wrapper to invite it to execute the proposed action on a Basic object If the arguments do not match their definition at the Basic side, a TypeError is raised
After execution, a check is done if the execution has raised an error within Basic If yes, a TypeError is raised
:param action: Property Get, Property Let, Property Set, invoke Method orreturn UNO object
:param basic: the reference of the Basic object, i.e. the index in the array caching the addresses of the objects
conventionally Application = -1 and DoCmd = -2
:param script: the property or method name
:param args: the arguments of the method, if any
:return: the value returned by the execution of the Basic routine """ if COMPONENTCONTEXT isNone:
A2BConnect() # Connection from inside LibreOffice is done at first API invocation # Intercept special call to Application.Events() if basic == Application.basicmodule and script == 'Events':
Script = cls.xScript('PythonEventsWrapper', _WRAPPERMODULE)
Returned = Script.invoke((args[0],), (), ()) else:
Script = cls.xScript('PythonWrapper', _WRAPPERMODULE)
NoArgs = '+++NOARGS+++'# Conventional notation for properties/methods without arguments if len(args) == 0:
args = (action,) + (basic,) + (script,) + (NoArgs,) else:
args = (action,) + (basic,) + (script,) + args try:
Returned = Script.invoke((args), (), ()) except Exception: raise TypeError("Access2Base error: method '" + script + "' call error. Check its arguments.")
if isinstance(Returned[0], tuple): # Is returned value a reference to a basic object, a scalar or a UNO object ? if len(Returned[0]) in (3, 4): if Returned[0][0] == 0: # scalar return Returned[0][1] elif Returned[0][0] == 1: # reference to objects cache
basicobject = cls.BasicObject(Returned[0][2]) if len(Returned[0]) == 3: return basicobject(Returned[0][1], Returned[0][2]) else: return basicobject(Returned[0][1], Returned[0][2], Returned[0][3]) elif Returned[0][0] == 2: # Null value returnNone else: # Should not happen returnNone else: # UNO object return Returned[0] elif Returned[0] isNone: if cls.VerifyNoError(): returnNone else: # Should not happen return Returned[0]
@classmethod def VerifyNoError(cls): # has Access2Base generated an error ?
errorstack = cls.A2BErrorCode() # 0 = code, 1 = severity, 2 = short text, 3 = long text if errorstack[1] in ('ERROR', 'FATAL', 'ABORT'): raise TypeError('Access2Base error: ' + errorstack[3]) returnTrue
class Application(object, metaclass = _Singleton): """ Collection of methods located in the Application (Basic) module """
W = _A2B.invokeWrapper
basicmodule = -1
class Basic(object, metaclass = _Singleton): """ Collection of helper functions having the same behaviour as their Basic counterparts """
M = _A2B.invokeMethod
class _BasicObject(object): """
Parent class of Basic objects
Each subclass is identified by its classProperties:
dictionary with keys = allowed properties, value = Trueif editable orFalse
Each instance is identified by its
- reference in the cache managed by Basic
- type ('DATABASE', 'COLLECTION', ...)
- name (form, control, ... name) - may be blank
Properties are got and set following next strategy:
1. Property names are controlled strictly ('Value'andnot'value')
2. Getting a property value for the first time is always done via a Basic call
3. Next occurrences are fetched from the Python dictionary of the instance if the property is read-only, otherwise via a Basic call
4. Methods output might force the deletion of a property from the dictionary ('MoveNext' changes 'BOF'and'EOF' properties)
5. Setting a property value is done via a Basic call, exceptif self.internal == True """
W = _A2B.invokeWrapper
internal_attributes = ('objectreference', 'objecttype', 'name', 'internal')
def __init__(self, reference = -1, objtype = None, name = ''):
self.objectreference = reference # reference in the cache managed by Basic
self.objecttype = objtype # ('DATABASE', 'COLLECTION', ...)
self.name = name # '' when no name
self.internal = False# True to exceptionally allow assigning a new value to a read-only property
self.localProperties = ()
def __getattr__(self, name): if name in ('classProperties', 'localProperties'): pass elif name in self.classProperties: # Get Property from Basic return self.W(_vbGet, self.objectreference, name) # Usual attributes getter return super(_BasicObject, self).__getattribute__(name)
def __setattr__(self, name, value): if name in ('classProperties', 'localProperties'): pass elif name in self.classProperties: if self.internal: # internal = True forces property setting even if property is read-only pass elif self.classProperties[name]: # True == Editable
self.W(_vbLet, self.objectreference, name, value) else: raise AttributeError("type object '" + self.objecttype + "' has no editable attribute '" + name + "'") elif name[0:2] == '__'or name in self.internal_attributes or name in self.localProperties: pass else: raise AttributeError("type object '" + self.objecttype + "' has no attribute '" + name + "'")
object.__setattr__(self, name, value) return
def _Reset(self, propertyname, basicreturn = None): """ force new value or erase properties from dictionary (done to optimize calls to Basic scripts) """ if propertyname in ('BOF', 'EOF'): # After a Move method invocation on a Recordset object, BOF or EOF likely to be got soon if isinstance(basicreturn, int):
self.internal = True # f.i. basicreturn = 0b10 means: BOF = True, EOF = False
self.BOF = basicreturn in (2, 3, -2, -3)
self.EOF = basicreturn in (1, 3, -1, -3)
self.internal = False return ( basicreturn >= 0 ) else: # Suppress possibly invalid property values: e.g. RecordCount after Delete applied on Recordset object if property in self.__dict__: del(self.propertyname) return basicreturn
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.