How to properly initialize win32com.client.constants? - python

I'm writing a simple email filter to work upon Outlook incoming messages on Windows 10, and seek to code it up in Python using the win32com library, under Anaconda. I also seek to avoid using magic numbers for the "Inbox" as I see in other examples, and would rather use constants that should be defined under win32com.client.constants. But I'm running into simple errors that are surprising:
So, I concocted the following simple code, loosely based upon https://stackoverflow.com/a/65800130/257924 :
import sys
import win32com.client
try:
outlookApp = win32com.client.Dispatch("Outlook.Application")
except:
print("ERROR: Unable to load Outlook")
sys.exit(1)
outlook = outlookApp.GetNamespace("MAPI")
ofContacts = outlook.GetDefaultFolder(win32com.client.constants.olFolderContacts)
print("ofContacts", type(ofContacts))
sys.exit(0)
Running that under an Anaconda-based installer (Anaconda3 2022.10 (Python 3.9.13 64-bit)) on Windows 10 errors out with:
(base) c:\Temp>python testing.py
Traceback (most recent call last):
File "c:\Temp\testing.py", line 11, in <module>
ofContacts = outlook.GetDefaultFolder(win32com.client.constants.olFolderContacts)
File "C:\Users\brentg\Anaconda3\lib\site-packages\win32com\client\__init__.py", line 231, in __getattr__
raise AttributeError(a)
AttributeError: olFolderContacts
Further debugging indicates that the __dicts__ property is referenced by the __init__.py in the error message above. See excerpt of that class below. For some reason, that __dicts__ is an empty list:
class Constants:
"""A container for generated COM constants."""
def __init__(self):
self.__dicts__ = [] # A list of dictionaries
def __getattr__(self, a):
for d in self.__dicts__:
if a in d:
return d[a]
raise AttributeError(a)
# And create an instance.
constants = Constants()
What is required to have win32com properly initialize that constants object?
The timestamps on the init.py file show 10/10/2021 in case that is relevant.

The short answer is to change:
outlookApp = win32com.client.Dispatch("Outlook.Application")
to
outlookApp = win32com.client.gencache.EnsureDispatch("Outlook.Application")
The longer answer is that win32com can work with COM interfaces in one of two ways: late- and early-binding.
With late-binding, your code knows nothing about the Dispatch interface ie. doesn't know which methods, properties or constants are available. When you call a method on the Dispatch interface, win32com doesn't know if that method exists or any parameters: it just sends what it is given and hopes for the best!
With early-binding, your code relies on previously-captured information about the Dispatch interface, taken from its Type Library. This information is used to create local Python wrappers for the interface which know all the methods and their parameters. At the same time it populates the Constants dictionary with any constants/enums contained in the Type Library.
win32com has a catch-all win32com.client.Dispatch() function which will try to use early-binding if the local wrapper files are present, otherwise will fall back to using late-binding. My problem with the package is that the caller doesn't always know what they are getting, as in the OP's case.
The alternative win32com.client.gencache.EnsureDispatch() function enforces early-binding and ensures any constants are available. If the local wrapper files are not available, they will be created (you might find them under %LOCALAPPDATA%\Temp\gen_py\xx\CLSID where xx is the Python version number, and CLSID is the GUID for the Type Library). Once these wrappers are created once then the generic win32com.client.Dispatch() will use these files.

Related

Pythonic way to set module-wide settings from external file

Some background (not mandatory, but might be nice to know): I am writing a Python command-line module which is a wrapper around latexdiff. It basically replaces all \cite{ref1, ref2, ...} commands in LaTeX files with written-out and properly formatted references before passing the files to latexdiff, so that latexdiff will properly mark changes to references in the text (otherwise, it treats the whole \cite{...} command as a single "word"). All the code is currently in a single file which can be run with python -m latexdiff-cite, and I have not yet decided how to package or distribute it. To make the script useful for anybody else, the citation formatting needs to be configurable. I have implemented an optional command-line argument -c CONFIGFILE to allow the user to point to their own JSON config file (a default file resides in the module folder and is loaded if the argument is not used).
Current implementation: My single-file command-line Python module currently parses command-line arguments in if __name__ == '__main__', and loads the config file (specified by the user in -c CONFIGFILE) here before running the main function of the program. The config variable is thus available in the entire module and all is well. However, I'm considering publishing to PyPI by following this guide which seems to require me to put the command-line parsing in a main() function, which means the config variable will not be available to the other functions unless passed down as arguments to where it's needed. This "passing down by arguments" method seems a little cluttered to me.
Question: Is there a more pythonic way to set some configuration globals in a module or otherwise accomplish what I'm trying to? (I don't want to rely on 3rd party modules.) Am I perhaps completely off the tracks in some fundamental way?
One way to do it is to have the configurations defined in a class or a simple dict:
class Config(object):
setting1 = "default_value"
setting2 = "default_value"
#staticmethod
def load_config(json_file):
""" load settings from config file """
with open(json_file) as f:
config = json.load(f)
for k, v in config.iteritems():
setattr(Config, k, v)
Then your application can access the settings via this class: Config.setting1 ...

How to access Protocol Buffers custom option in python?

I'm following instructions from Google Developers guide in order to create custom message option. I have used their example but I've received an error:
Traceback (most recent call last):
File "test_my_opt.py", line 2, in <module>
value = my_proto_file_pb2.MyMessage.DESCRIPTOR.GetOptions().Extensions[my_proto_file_pb2.my_option]
File "(...)\google\protobuf\internal\python_message.py", line 1167, in __getitem__
_VerifyExtensionHandle(self._extended_message, extension_handle)
File "(...)\google\protobuf\internal\python_message.py", line 170, in _VerifyExtensionHandle
message.DESCRIPTOR.full_name))
KeyError: 'Extension "my_option" extends message type "google.protobuf.MessageOptions", but this message is of type "google.protobuf.MessageOptions".'
I simply used following code:
import my_proto_file_pb2
value = my_proto_file_pb2.MyMessage.DESCRIPTOR.GetOptions().Extensions[my_proto_file_pb2.my_option]
And this proto file:
import "beans-protobuf/proto/src/descriptor.proto";
extend google.protobuf.MessageOptions {
optional string my_option = 51234;
}
message MyMessage {
option (my_option) = "Hello world!";
}
Everything like in guide... so how should I access this option without error?
import "beans-protobuf/proto/src/descriptor.proto";
I think this is the problem. The correct import statement for descriptor.proto is:
import "google/protobuf/descriptor.proto";
The path string is important because you need to be extending the original definitions of the descriptor types, not some copy of them. google/protobuf/descriptor.proto becomes the module google.protobuf.descriptor_pb2 in Python, and the Protobuf library expects that any custom options are extensions to the types in there. But you are actually extending beans-protobuf/proto/src/descriptor.proto, which becomes beans_protobuf.proto.src.descriptor_pb2 in Python, which is a completely different module! Hence, the protobuf library gets confused and doesn't think these extensions are applicable to protobuf descriptors.
I think if you just change the import statement, everything should work. When protobuf is correctly installed, google/protobuf/descriptor.proto should always work as an import -- there's no need to provide your own copy of the file.

Using Python ctypes to access a Visual Foxpro COM DLL

I am trying to use the python ctypes library to access various functions in a COM DLL created in Visual Fox Pro (from a .prg file).
Here is an example in fox pro (simplified from the actual code)
DEFINE CLASS Testing AS CUSTOM OLEPUBLIC
PROCEDURE INIT
ON ERROR
SET CONSOLE OFF
SET NOTIFY OFF
SET SAFETY OFF
SET TALK OFF
SET NOTIFY OFF
ENDPROC
FUNCTION get_input_out(input AS STRING) AS STRING
output = input
RETURN output
ENDFUNC
ENDDEFINE
In python i am doing something along the lines of:
import ctypes
link = ctypes.WinDLL("path\to\com.dll")
print link.get_input_out("someinput")
The dll registers fine and is loaded but i just get the following when I try to call the function.
AttributeError: function 'get_input_out' not found
I can verfiy the dll does work as i was able to access the functions with a php script using the COM libary.
I would really like to get this working in python but so far my attempts have all been in vain, will ctypes even work with VFP? Any advice would be appreciated.
In the case of VFP COM objects using the OLEPUBLIC clause, you should use the python Win32 extensions, not the ctypes module. Python Win32 extensions provide a full featured set of components that allow Python to be a COM client, which is what you want in this case.
Your code should look something like this:
from win32com.client import Dispatch
oFox = Dispatch("com.testing")
# where the file name compiled by VFP is com.dll and the olepublic class is testing.
# in Windows this stuff is not case sensitive.
print oFox.get_input_out("something")
# to close things down..
oFox = None
If all you want to do is access VFP tables, Microsoft supplies a free ADO compliant component vfpoledb which can be used to access tables via ADODB, which in turn can be accessed through this same Dispatch() capability in the Win32 extensions.
Try removing the parentheses from the call to the function. Change print link.get_input_out("someinput") to print link.get_input_out "someinput".

PyWin32 get network information/statistics

I am trying to get Network Statistics for my Windows 7 system using PyWin32.
The steps I followed:
1) Run COM MakePy utility and than select network list manager 1.0
type library under type library.
2) Above process generated this python file.
Now the problem I am facing is after the above two steps what should be my next step. I tried a couple of things like:
I copied the CLSID = IID('{DCB00000-570F-4A9B-8D69-199FDBA5723B}') line from the above generated python file and used it like
>>> import win32com
>>> obj = win32com.client.gencache.GetClassForCLSID("{DCB00000-570F-4A9B-8D69-199FDBA5723B}")
>>> obj.GetConnectivity()
Traceback (most recent call last):
File "<interactive input>", line 1, in <module>
TypeError: unbound method GetConnectivity() must be called with INetworkListManager instance as first argument (got nothing instead)
When I do obj.method() it show a list of all available method.
So, now I have no idea what to do or how to proceed and what is the general process of using Type library with pywin32.
The above task is just a part of learning process on how to use PyWin32,COM MakePy utility.
Is this even achievable using pywin32.?
You'll need to use win32com.client.Dispatch to actually create the object.
Also, the class you start with is the CoClass, in this case
class NetworkListManager(CoClassBaseClass): # A CoClass
is the one you want.
win32com.client.Dispatch('{DCB00C01-570F-4A9B-8D69-199FDBA5723B}')
works here.
Many of these Dispatch classes have a human readable dotted name as an alias, although
this particular one doesn't seem to.

Accessing samba shares with gio in python

I am trying to make a simple command line client for accessing shares via the Python bindings of gio (yes, the main requirement is to use gio).
I can see that comparing with it's predecessor gnome-vfs, it provides some means to do authentication stuff (subclassing MountOperation), and even some methods which are quite specific to samba shares, like set_domain().
But I'm stuck with this code:
import gio
fh = gio.File("smb://server_name/")
If that server needs authentication, I suppose that a call to fh.mount_enclosing_volume() is needed, as this methods takes a MountOperation as a parameter. The problem is that calling this methods does nothing, and the logical fh.enumerate_children() (to list the available shares) that comes next fails.
Anybody could provide a working example of how this would be done with gio ?
The following appears to be the minimum code needed to mount a volume:
def mount(f):
op = gio.MountOperation()
op.connect('ask-password', ask_password_cb)
f.mount_enclosing_volume(op, mount_done_cb)
def ask_password_cb(op, message, default_user, default_domain, flags):
op.set_username(USERNAME)
op.set_domain(DOMAIN)
op.set_password(PASSWORD)
op.reply(gio.MOUNT_OPERATION_HANDLED)
def mount_done_cb(obj, res):
obj.mount_enclosing_volume_finish(res)
(Derived from gvfs-mount.)
In addition, you may need a glib.MainLoop running because GIO mount functions are asynchronous. See the gvfs-mount source code for details.

Categories

Resources