I've been trying to understand COM libraries but I'm still confused. If I want to make a python object com visible, the only instructions I can find are to make a python script that sets up a COM server which is invoked and used to generate class instances by name. IIUC, this is made possible by permanently adding some information to the registry that links a CLSID to the path of my server.py file; here's a minimum example:
class HelloWorld:
# pythoncom.CreateGuid()
_reg_clsid_ = "{7CC9F362-486D-11D1-BB48-0000E838A65F}"
_reg_desc_ = "Python Test COM Server"
_reg_progid_ = "PythonTestServer.HelloWorld"
_public_methods_ = ["Hello"]
_public_attrs_ = ["softspace", "noCalls"]
_readonly_attrs_ = ["noCalls"]
def __init__(self):
self.softspace = 1
self.noCalls = 0
def Hello(self, who):
self.noCalls = self.noCalls + 1
# insert "softspace" number of spaces
return "Hello" + " " * self.softspace + str(who)
if __name__ == "__main__": #run once to register the COM class
import win32com.server.register
win32com.server.register.UseCommandLine(HelloWorld) #this is what I want to avoid
Called like:
Dim a as Object = CreateObject("PythonTestServer.HelloWorld") 'late binding
a.softSpace = 10
?a.Hello("World") 'prints "Hello World"
However I'm sure in the past I've downloaded some file.dll that I can just add as a reference to my COM client project (VBA) and I never call --register/regsrvr/etc. I assume the dll contains all the information needed to modify and revert changes to the registry at runtime. I can use Early or Late binding to create class instances. I even get intellisense if the referenced dll contains a type library.
The latter method feels much simpler and more portable. Is there a way to emulate this in python?
Related
Eventually I want to run RTD server and retrieve the data in excel. However I have problems with the basics. So, I am presenting you a reduced problem of running a COM object in Excel First.
class HelloWorld:
_reg_clsid_ = "{7CC9F362-486D-11D1-BB48-0000E838A65F}"
_reg_desc_ = "Python Test COM Server"
_reg_progid_ = "Python.TestServer"
_public_methods_ = ['Hello']
_public_attrs_ = ['noCalls']
_readonly_attrs_ = ['noCalls']
def __init__(self):
self.noCalls = 0
def Hello(self):
self.noCalls = self.noCalls + 1
return "Hello World"
if __name__=='__main__':
import win32com.server.register
win32com.server.register.UseCommandLine(HelloWorld)
This is the basic code. It works fine the Python.TestServer is registered in HKEY_CLASSES_ROOT and I can run it through Python.
o=win32com.client.Dispatch("Python.TestServer")
o.Hello()
I tried to run through Excel(VB) as well.
Sub PythonTest()
Set PythonUtils = CreateObject("Python.TestServer")
r = PythonUtils.Hello()
MsgBox r
End Sub
I get:
My hypotheses about what is wrong are:
Something is not setup or enabled in Excel, although I have enabled Macros and ActiveX controls.
You need to define a Python interpreter?
To elaborate on the second hypothesis. I don't exactly know what Python interpreter does the COM object use to execute commands, as I have several environments. Or is everything compiled when you register??
I'm using Python 3.8.3 on a MacBook Pro (13-inch, 2016, Two Thunderbolt 3 ports)
I'm currently building an online chat and this is my person.py code:
class Person:
"""
Represents a person, hold name, socket client and client addr
"""
def __init__(self, addr, client):
self.addr = addr
self.client = client
self.name = None
def set_name(self, name):
self.name = name
def __repr__(self):
return f"Person({self.addr}, {self.name})"
And this is my server.py coding:
from threading import Thread
import time
from person import Person
# GLOBAL CONSTANTS
HOST = 'localhost'
PORT = 5500
ADDR = (HOST, PORT)
MAX_CONNECTIONS = 10
BUFSIZ = 512
# GLOBAL VARIABLES
persons = []
SERVER = socket(AF_INET, SOCK_STREAM)
SERVER.bind(ADDR) # set up server
def broadcast(msg, name):
"""
send new messages to all clients
:param msg: bytes["utf8"]
:param name: str
:return:
"""
for person in persons:
client = person.client
client.send(bytes(name, "utf8") + msg)
def client_communication(person):
"""
Thread to handle all messages from client
:param person: Person
:return: None
"""
client = person.client
# get persons name
name = client.recv(BUFSIZ).decode("utf8")
person.set_name(name)
msg = bytes(f"{name} has joined the chat!", "utf8")
broadcast(msg, "") # broadcast welcome message
while True:
try:
msg = client.recv(BUFSIZ)
if msg == bytes("{quit}", "utf8"):
client.close()
persons.remove(person)
broadcast(f"{name} has left the chat...", "")
print(f"[DISCONNECTED] {name} disconnected")
break
else:
broadcast(msg, name+": ")
print(f"{name}: ", msg.decode("utf8"))
except Exception as e:
print("[EXCEPTION]", e)
break
def wait_for_connection():
"""
Wait for connetion from new clients, start new thread once connected
:param SERVER: SOCKET
:return: None
"""
run = True
while run:
try:
client, addr = SERVER.accept()
person = Person(addr, client)
persons.append(person)
print(f"[CONNECTION] {addr} connected to the server at {time.time()}")
Thread(target=client_communication, args=(person,)).start()
except Exception as e:
print("[EXCEPTION]", e)
run = False
print("SERVER CRASHED")
if __name__ == "__main__":
SERVER.listen(MAX_CONNECTIONS) # listen for connections
print("[STARTED] Waiting for connections...")
ACCEPT_THREAD = Thread(target=wait_for_connection)
ACCEPT_THREAD.start()
ACCEPT_THREAD.join()
SERVER.close()
The problem is, everytime i try to run the program, it gives me this error:
from person import Person
ModuleNotFoundError: No module named 'person'
Does somebody know how to solve this problem?
The Problem
Most likely, this is a path-finding error. The Python Path will look in the installation's lib and site-packages folders. It will also look at the current working directory. So if you're running one file from another folder but trying to import something it will look in your running directory instead of where the file you're running. Here are two solutions to this problem.
First Solution
You can make the working directory the same as the file you're running. Giving the following file structure:
workingdir
+-- thecode
|-- server.py
+-- person.py
You have the current working directory aka where you're running your command as workingdir so a terminal might look like this:
workingdir % python thecode/server.py
Thus you need to change the working directory to thecode instead. Do cd thecode to get there.
Second Solution
You can instead add the file's directory to the python path. This is held in sys.path, and gets at the end of each python run. Thus, it is best to add to the path at the beginning of your server.py file so that person.py is in your path before you import it. Use something like the following code to do this.
import sys
import os.path
sys.path.append(os.path.split(os.path.abspath(__file__))[0])
# now you may import Person.
from person import Person
The first two modules, sys and os, are pretty standard and will give you the tools to append to the Python path. The os.path.abspath gets the absolute path of your current file, in this case it's server.py. Then os.path.split will get the trailing (all the directories) and the head (the filename) of your absolute path. Finally appending to the sys.path allows Python to find person.py in your file's directory.
Other Solutions (That probably won't be used in this case)
You can also create a custom Python package and install it using pip. This isn't very useful in this case since the import problem is just one file, however, if you ever want to create a custom Python project that other's may use this Python docs article will help.
The final solution (which admittedly I used to do and isn't the best workaround) is to put your file in a folder that's already in the Python path. In this case, it would probably be the lib folder. For me, running Python 3.8 the path is /Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8, for any Python 3.x version this will be the same but the version replaced. Python 2.x has different locations sometimes.
Say I have a variable that helps with a network connection (eg: API access token) that I want to use in a module that I'm importing (for use in expanding on existing functions), is there a way to pass that variable to the imported script without adding a new class or method to initialize it?
eg:
Script one (performer.py):
import telepot
import botActions # Possibly adding an argument here?
botActions.sendMessage("locationID", "message")
accessToken = "SAMPLE"
Script two (botActions.py):
import botApiWrapper
def sendMessage(locationID, text):
bot.sendMessage(locationID, text)
print("Message sent to %g: %t".format(g=locationID, t=text))
bot = botApiWrapper.Bot(sys.argv[0])
So is there a way to pass the variable to the second script from the first one, or would I have to define an initializing function that I call after importing the file?
The canonical way to do something like this is to define an initialization function from the other file. Instead of bot = botApiWrapper.Bot(sys.argv[0]), try something like
def newbot(arg):
bot = botApiWrapper.Bot(arg)
return bot
and then call that function from your other module.
If you need state to operate, define a class.
class Bot(object):
def __init__(self, arg):
self.arg = arg
def sendMessage(self, locationId, text):
print "Using arg %s to send message to location %s with text %s" % \
(self.arg, locationId, text)
...thereafter letting you initialize as many bots as you like:
import botApiWrapper
botA = botApiWrapper.Bot("A")
botA.sendMessage("foo", "bar")
botB = botApiWrapper.Bot("B")
botB.sendMessage("baz", "qux")
I am looking fo a way to set the language on the fly when requesting a translation for a string in gettext. I'll explain why :
I have a multithreaded bot that respond to users by text on multiple servers, thus needing to reply in different languages.
The documentation of gettext states that, to change locale while running, you should do the following :
import gettext # first, import gettext
lang1 = gettext.translation('myapplication', languages=['en']) # Load every translations
lang2 = gettext.translation('myapplication', languages=['fr'])
lang3 = gettext.translation('myapplication', languages=['de'])
# start by using language1
lang1.install()
# ... time goes by, user selects language 2
lang2.install()
# ... more time goes by, user selects language 3
lang3.install()
But, this does not apply in my case, as the bot is multithreaded :
Imagine the 2 following snippets are running at the same time :
import time
import gettext
lang1 = gettext.translation('myapplication', languages=['fr'])
lang1.install()
message(_("Loading a dummy task")) # This should be in french, and it will
time.sleep(10)
message(_("Finished loading")) # This should be in french too, but it wont :'(
and
import time
import gettext
lang = gettext.translation('myapplication', languages=['en'])
time.sleep(3) # Not requested on the same time
lang.install()
message(_("Loading a dummy task")) # This should be in english, and it will
time.sleep(10)
message(_("Finished loading")) # This should be in english too, and it will
You can see that messages sometimes are translated in the wrong locale.
But, if I could do something like _("string", lang="FR"), the problem would disappear !
Have I missed something, or I'm using the wrong module to do the task...
I'm using python3
While the above solutions seem to work, they don’t play well with the conventional _() function that aliases gettext(). But I wanted to keep that function, because it’s used to extract translation strings from the source (see docs or e.g. this blog).
Because my module runs in a multi-process and multi-threaded environment, using the application’s built-in namespace or a module’s global namespace wouldn’t work because _() would be a shared resource and subject to race conditions if multiple threads install different translations.
So, first I wrote a short helper function that returns a translation closure:
import gettext
def get_translator(lang: str = "en"):
trans = gettext.translation("foo", localedir="/path/to/locale", languages=(lang,))
return trans.gettext
And then, in functions that use translated strings I assigned that translation closure to the _, thus making it the desired function _() in the local scope of my function without polluting a global shared namespace:
def some_function(...):
_ = get_translator() # Pass whatever language is needed.
log.info(_("A translated log message!"))
(Extra brownie points for wrapping the get_translator() function into a memoizing cache to avoid creating the same closures too many times.)
You can just create translation objects for each language directly from .mo files:
from babel.support import Translations
def gettext(msg, lang):
return get_translator(lang).gettext(msg)
def get_translator(lang):
with open(f"path_to_{lang}_mo_file", "rb") as fp:
return Translations(fp=fp, domain="name_of_your_domain")
And a dict cache for them can be easily thrown in there too.
I took a moment to whip up a script that uses all the locales available on the system, and tries to print a well-known message in them. Note that "all locales" includes mere encoding changes, which are negated by Python anyway, and plenty of translations are incomplete so do use the fallback.
Obviously, you will also have to make appropriate changes to your use of xgettext (or equivalent) for you real code to identify the translating function.
#!/usr/bin/env python3
import gettext
import os
def all_languages():
rv = []
for lang in os.listdir(gettext._default_localedir):
base = lang.split('_')[0].split('.')[0].split('#')[0]
if 2 <= len(base) <= 3 and all(c.islower() for c in base):
if base != 'all':
rv.append(lang)
rv.sort()
rv.append('C.UTF-8')
rv.append('C')
return rv
class Domain:
def __init__(self, domain):
self._domain = domain
self._translations = {}
def _get_translation(self, lang):
try:
return self._translations[lang]
except KeyError:
# The fact that `fallback=True` is not the default is a serious design flaw.
rv = self._translations[lang] = gettext.translation(self._domain, languages=[lang], fallback=True)
return rv
def get(self, lang, msg):
return self._get_translation(lang).gettext(msg)
def print_messages(domain, msg):
domain = Domain(domain)
for lang in all_languages():
print(lang, ':', domain.get(lang, msg))
def main():
print_messages('libc', 'No such file or directory')
if __name__ == '__main__':
main()
The following example uses the translation directly, as shown in o11c's answer to allow the use of threads:
import gettext
import threading
import time
def translation_function(quit_flag, language):
lang = gettext.translation('simple', localedir='locale', languages=[language])
while not quit_flag.is_set():
print(lang.gettext("Running translator"), ": %s" % language)
time.sleep(1.0)
if __name__ == '__main__':
thread_list = list()
quit_flag = threading.Event()
try:
for lang in ['en', 'fr', 'de']:
t = threading.Thread(target=translation_function, args=(quit_flag, lang,))
t.daemon = True
t.start()
thread_list.append(t)
while True:
time.sleep(1.0)
except KeyboardInterrupt:
quit_flag.set()
for t in thread_list:
t.join()
Output:
Running translator : en
Traducteur en cours d’exécution : fr
Laufenden Übersetzer : de
Running translator : en
Traducteur en cours d’exécution : fr
Laufenden Übersetzer : de
I would have posted this answer if I had known more about gettext. I am leaving my previous answer for folks who really want to continue using _().
The following simple example shows how to use a separate process for each translator:
import gettext
import multiprocessing
import time
def translation_function(language):
try:
lang = gettext.translation('simple', localedir='locale', languages=[language])
lang.install()
while True:
print(_("Running translator"), ": %s" % language)
time.sleep(1.0)
except KeyboardInterrupt:
pass
if __name__ == '__main__':
thread_list = list()
try:
for lang in ['en', 'fr', 'de']:
t = multiprocessing.Process(target=translation_function, args=(lang,))
t.daemon = True
t.start()
thread_list.append(t)
while True:
time.sleep(1.0)
except KeyboardInterrupt:
for t in thread_list:
t.join()
The output looks like this:
Running translator : en
Traducteur en cours d’exécution : fr
Laufenden Übersetzer : de
Running translator : en
Traducteur en cours d’exécution : fr
Laufenden Übersetzer : de
When I tried this using threads, I only got an English translation. You could create individual threads in each process to handle connections. You probably do not want to create a new process for each connection.
I've written an IRC bot using Twisted and now I've gotten to the point where I want to be able to dynamically reload functionality.
In my main program, I do from bots.google import GoogleBot and I've looked at how to use reload to reload modules, but I still can't figure out how to do dynamic re-importing of classes.
So, given a Python class, how do I dynamically reload the class definition?
Reload is unreliable and has many corner cases where it may fail. It is suitable for reloading simple, self-contained, scripts. If you want to dynamically reload your code without restart consider using forkloop instead:
http://opensourcehacker.com/2011/11/08/sauna-reload-the-most-awesomely-named-python-package-ever/
You cannot reload the module using reload(module) when using the from X import Y form. You'd have to do something like reload(sys.modules['module']) in that case.
This might not necessarily be the best way to do what you want, but it works!
import bots.google
class BotClass(irc.IRCClient):
def __init__(self):
global plugins
plugins = [bots.google.GoogleBot()]
def privmsg(self, user, channel, msg):
global plugins
parts = msg.split(' ')
trigger = parts[0]
if trigger == '!reload':
reload(bots.google)
plugins = [bots.google.GoogleBot()]
print "Successfully reloaded plugins"
I figured it out, here's the code I use:
def reimport_class(self, cls):
"""
Reload and reimport class "cls". Return the new definition of the class.
"""
# Get the fully qualified name of the class.
from twisted.python import reflect
full_path = reflect.qual(cls)
# Naively parse the module name and class name.
# Can be done much better...
match = re.match(r'(.*)\.([^\.]+)', full_path)
module_name = match.group(1)
class_name = match.group(2)
# This is where the good stuff happens.
mod = __import__(module_name, fromlist=[class_name])
reload(mod)
# The (reloaded definition of the) class itself is returned.
return getattr(mod, class_name)
Better yet subprocess the plugins, then hypervise the subprocess, when the files change reload the plugins process.
Edit: cleaned up.
You can use the sys.modules to dynamically reload modules based on user-input.
Say that you have a folder with multiple plugins such as:
module/
cmdtest.py
urltitle.py
...
You can use sys.modules in this way to load/reload modules based on userinput:
import sys
if sys.modules['module.' + userinput]:
reload(sys.modules['module.' + userinput])
else:
' Module not loaded. Cannot reload '
try:
module = __import__("module." + userinput)
module = sys.modules["module." + userinput]
except:
' error when trying to load %s ' % userinput
When you do a from ... import ... it binds the object into the local namespace, so all you need to is re-import it. However, since the module is already loaded, it will just re-import the same version of the class so you would need to reload the module too. So this should do it:
from bots.google import GoogleBot
...
# do stuff
...
reload(bots.google)
from bots.google import GoogleBot
If for some reason you don't know the module name you can get it from GoogleBot.module.
def reload_class(class_obj):
module_name = class_obj.__module__
module = sys.modules[module_name]
pycfile = module.__file__
modulepath = string.replace(pycfile, ".pyc", ".py")
code=open(modulepath, 'rU').read()
compile(code, module_name, "exec")
module = reload(module)
return getattr(module,class_obj.__name__)
There is a lot of error checking you can do on this, if your using global variables you will probably have to figure out what happens then.