Python Win32com Excel. Issue with python threading and com object handling - python

For some days I've been breaking my head over some issues I'm having with a Win32Com Excel object in python.
What I'm trying to do is very simple; get the sheet names from a workbook and write a value to the bottom of a column. The workbook is often opened by the users and needs to remain accessible/editable, hence I decided to use Win32Com. Any other suggestions to achieve the same are very welcome as well.
The base-code is quite straightforward:
class excelCOM():
def __init__(self, file):
self.xl = win32com.client.gencache.EnsureDispatch('Excel.Application')
self.wb = self.xl.Workbooks(os.path.basename(file))
def getSheets(self):
return [s.Name for s in self.wb.Sheets]
def dataToCell(self, sheet, column, string):
sht = self.wb.Sheets(sheet)
maxLastCell = sht.Range('{}{}'.format(column, 1048576))
lastCell = maxLastCell.End(win32com.client.constants.xlUp)
lastCell.Offset(2, 1).Value = string
Issues occur when integrated with the rest of the software. I've gone through lots of documentation and trying different things but I've been unable to get a reliable result. I'll try my best to summarize what I've tried and what the observations were.
When I create a single instance of this class and run the methods, everything works as expected.
if __name__ == '__main__':
xlWb = excelCOM(r"TestBook.xlsx")
print(xlWb.getSheets())
xlWb.dataToCell("Sheet1", 'B', 'All Good')
The information to write to the excel file comes from a logfile that is being written to by another (external) program. My software monitors the file and writes to excel whenever there is a new line added. All of this is handled by the 'processor'. The formatting for the text that's written to excel is user defined. Those formatting settings (and others) are imported to the processor from a pickled (settings) object (allowing the user to save settings (using a GUI) and run the processor without needing the GUI). As code (extremely simplified, just to get the idea across):
class processor():
def __init__(self, settings_object):
self.com_object = excelCOM(settings_object.excel_filepath)
self.file_monitor(settings_object.input_filepath)
self.file_monitor.start()
def dispatch():
# called whenever file_monitor registers a change to the file at input_filepath
last_line = self.get_last_line(input_filepath)
sheet, column, formatted_text = self.process(last_line)
self.com_object.dataToCell(sheet, column, formatted_text)
Now, when text is supposed to be written to the excel file by the processor, I get Exception in thread Thread-1: pywintypes.com_error: (-2147221008, 'CoInitialize has not been called.', None, None) at sht = self.wb.Sheets(sheet)
When I call CoInitialize() before sht = self.wb.Sheets(sheet) I get Exception in thread Thread-1: pywintypes.com_error: (-2147417842, 'The application called an interface that was marshalled for a different thread.', None, None). Calling CoInitialize() anywhere else in the code doesn't seem to do anything.
Adding the same code as in init solves the issue from point 3, but this seems very wrong to me. Mainly because I don't exactly know why it fixes the issue and what's going on in the background with com objects in windows.
def dataToCell(self, sheet, column, string):
pythoncom.CoInitialize() # point 3.
self.xl = win32com.client.gencache.EnsureDispatch('Excel.Application') # point 4.
self.wb = self.xl.Workbooks(os.path.basename(self.file)) # point 4.
sht = self.wb.Sheets(sheet)
maxLastCell = sht.Range('{}{}'.format(column, 1048576))
lastCell = maxLastCell.End(win32com.client.constants.xlUp)
lastCell.Offset(2, 1).Value = string
Now switching to the GUI. As mentioned earlier, the settings file for the processor is created with help from a GUI. It basically gives the functionally to build the settings_object. In the GUI I also have a 'run' button which directly calls processor(settings_object) in a seperate Thread. When I do this, I don't even need to add the additional lines as described in point 3 and 4. It runs perfectly with just the base-code.
I've gone through dozens of pages of documentation, StackOverflow topics, tutorials, blogs hidden in the dark corners of the web, Python Programming on Win32 but I simply can't wrap my head around what's going on. I consider myself a half-decent programmer for 'get the job done' applications but I don't have any education in computer science (or even programming in general), which is what I suspect I'm lacking at the moment.
I'm hoping someone can point me in the right direction for understanding this behavior and maybe give some advice on the proper way of implementing this functionality for my scenario.
Please let me know if you require any more information.
Best regards and many thanks in advance,
RiVer

Related

PyWinAuto with out using "child_window"

I have NO return on child_window when the program is in its state i expect to work in.
I need a way to edit the text field but literally all examples and google searches i have done show no examples of implementation EXCEPT when using child_window
this should put Test into the edit field
from pywinauto.application import Application
app = Application(backend="uia").connect(title="DaVinci Resolve Studio - Template")
#app.DaVinciResolveStudioTemplate.print_control_identifiers()
Title = app.DaVinciResolveStudioTemplate.['TitleEdit', 'Edit8'].wrapper_object()
Title.type_keys("Test")
it returns a syntax error
I have read teh documentation and i HONESTLY have no idea how to initiate the return with out a child window. Googing "no child_window" with multiple iterations has yielded me hours wasted and NOT ONE solution.
if text is entered the child_window appears in returns, but that isnt how the program start's
This is the native return
please, explain it for me how im supposed to seach/grab/interact with out child window? with a example please because this has me at a loss
Syntax error is in line Title = app.DaVinciResolveStudioTemplate.['TitleEdit', 'Edit8'].wrapper_object().
This line should be written as either Title = app.DaVinciResolveStudioTemplate['TitleEdit', 'Edit8'].wrapper_object() or Title = app.DaVinciResolveStudioTemplate.Edit8.wrapper_object().

Visual Studio Code, Python, Alterations Not Running

I am trying to alter some code I have been given for my dissertation. However, any alterations to the modules (print statements, logging, and even trying to deliberately break the code with breaks) do not seem to affect the main program running as if those changes were not there, but changes to the main program does affect the output.
All the changes are saved and I have tried reimporting all the data files I am using and deleting all of the results before rerunning to try and start the program from "fresh". But no luck in fixing the issue.
Is there any fix to this problem? Apologies if it is an easy one, still starting out.
Main Program
eda = EDA()
cdr3_lengths = eda.get_length_of_repertoires(
repertoires=repertoires,
cdr3_col_name="aaSeqCDR3",
freq_col="cloneCount",
groups={
"01.TC8.14": "14days",
"02.TC4.14": "14days",
"03.SC8.14": "14days",
"04.SC4.14": "14days",
"05.TC8.21": "21days",
"06.TC4.21": "21days",
"07.SC8.21": "21days",
"08.SC4.21": "21days",
},
)
Module
class EDA:
# --init method etc --
def get_length_of_repertoires(
self,
repertoires: List,
groups: dict,
cdr3_col_name: str,
freq_col: str,
save_df: bool = False):
print("This isn't working")
cdr3_lengths = {}
self.logger.info("For loop fetching each repertorie.")
for repertoire in repertoires: #Repeat seqs len for count
self.logger.debug(repertoire)
.....
return 'value'
I have added some of the code that I am working with here, I believe it is being executed. I hope this helps a little more.

How to change username of job in print queue using python & win32print

I am trying to change the user of a print job in the queue, as I want to create it on a service account but send the job to another users follow-me printing queue. I'm using the win32 module in python. Here is an example of my code:
from win32 import win32print
JOB_INFO_LEVEL = 2
pclExample = open("sample.pcl")
printer_name = win32print.GetDefaultPrinter()
hPrinter = win32print.OpenPrinter(printer_name)
try:
jobID = win32print.StartDocPrinter(hPrinter, 1, ("PCL Data test", None, "RAW"))
# Here we try to change the user by extracting the job and then setting it again
jobInfoDict = win32print.GetJob(hPrinter, jobID , JOB_INFO_LEVEL )
jobInfoDict["pUserName"] = "exampleUser"
win32print.SetJob(hPrinter, jobID , JOB_INFO_LEVEL , jobInfoDict , win32print.JOB_CONTROL_RESUME )
try:
win32print.StartPagePrinter(hPrinter)
win32print.WritePrinter(hPrinter, pclExample)
win32print.EndPagePrinter(hPrinter)
finally:
win32print.EndDocPrinter(hPrinter)
finally:
win32print.ClosePrinter(hPrinter)
The problem is I get an error at the win32print.SetJob() line. If JOB_INFO_LEVEL is set to 1, then I get the following error:
(1804, 'SetJob', 'The specified datatype is invalid.')
This is a known bug to do with how the C++ works in the background (Issue here).
If JOB_INFO_LEVEL is set to 2, then I get the following error:
(1798, 'SetJob', 'The print processor is unknown.')
However, this is the processor that came from win32print.GetJob(). Without trying to change the user this prints fine, so I'm not sure what is wrong.
Any help would be hugely appreciated! :)
EDIT:
Using Python 3.8.5 and Pywin32 303
At the beginning I thought it was a misunderstanding (I was also a bit skeptical about the bug report), mainly because of the following paragraph (which apparently seems to be wrong) from [MS.Docs]: SetJob function (emphasis is mine):
The following members of a JOB_INFO_1, JOB_INFO_2, or JOB_INFO_4 structure are ignored on a call to SetJob: JobId, pPrinterName, pMachineName, pUserName, pDrivername, Size, Submitted, Time, and TotalPages.
But I did some tests and ran into the problem. The problem is as described in the bug: filling JOB_INFO_* string members (which are LPTSTRs) with char* data.
Submitted [GitHub]: mhammond/pywin32 - Fix: win32print.SetJob sending ANSI to UNICODE API (and none of the 2 errors pops up). It was merged to main on 220331.
When testing the fix, I was able to change various properties of an existing job, I was amazed that it didn't have to be valid data (like below), I'm a bit curious to see what would happen when the job would be executed (as now I don't have a connection to a printer):
Change pUserName to str(random.randint(0, 10000)) to make sure it changes on each script run (PrintScreens taken separately and assembled in Paint):
Ways to go further:
Wait for a new PyWin32 version (containing this fix) to be released. This is the recommended approach, but it will also take more time (and it's unclear when it will happen)
Get the sources, either:
from main
from b303 (last stable branch), and apply the (above) patch(1)
build the module (.pyd) and copy it in the PyWin32's site-packages directory on your Python installation(s). Faster, but it requires some deeper knowledge, and maintenance might become a nightmare
Footnotes
#1: Check [SO]: Run / Debug a Django application's UnitTests from the mouse right click context menu in PyCharm Community Edition? (#CristiFati's answer) (Patching UTRunner section) for how to apply patches (on Win).

How to assign a 2d libreoffice calc named range to a python variable. Can do it in Libreoffice Basic

I can't seem to find a simple answer to the question. I have this successfully working in Libreoffice Basic:
NamedRange = ThisComponent.NamedRanges.getByName("transactions_detail")
RefCells = NamedRange.getReferredCells()
Set MainRange = RefCells.getDataArray()
Then I iterate over MainRange and pull out the rows I am interested in.
Can I do something similar in a python macro? Can I assign a 2d named range to a python variable or do I have to iterate over the range to assign the individual cells?
I am new to python but hope to convert my iteration intensive macro function to python in hopes of making it faster.
Any help would be much appreciated.
Thanks.
LibreOffice can be manipulated from Python with the library pyuno. The documentation of pyuno is unfortunately incomplete but going through this tutorial may help.
To get started:
Python-Uno, the library to communicate via Uno, is already in the LibreOffice Python’s path. To initialize your context, type the following lines in your python shell :
import socket # only needed on win32-OOo3.0.0
import uno
# get the uno component context from the PyUNO runtime
localContext = uno.getComponentContext()
# create the UnoUrlResolver
resolver = localContext.ServiceManager.createInstanceWithContext(
"com.sun.star.bridge.UnoUrlResolver", localContext )
# connect to the running office
ctx = resolver.resolve( "uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext" )
smgr = ctx.ServiceManager
# get the central desktop object
desktop = smgr.createInstanceWithContext( "com.sun.star.frame.Desktop",ctx)
# access the current writer document
model = desktop.getCurrentComponent()
Then to get a named range and access the data as an array, you can use the following methods:
NamedRange = model.NamedRanges.getByName(“Test Name”)
MainRange = NamedRange.getDataArray()
However I am unsure that this will result in a noticeable preformance gain.

Assign string to QLineEdit with PySide/PyQt

I'm having a little bit of trouble assigning values to a QLineEdit. I've read the documentation and feel that the QLineEdit.SetText() command will be used at some point.
I've used Qt Designer to design a GUI for my software. On the main window (MainWindow.py, with an accompanying ui_MainWindow.py setup file), I have a LineEdit (lineEditScanBarcode) which has strong focus. I've managed to pull input from that LineEdit pretty well. What I'd like to do is this:
If the input in LineEditScanBarcode = x, then assign the name 'John Smith' to a secondary QLineEdit (lineEditUser) which has a zero focus policy. This is what I have so far:
def ScanBarcode(self):
barcode = self.lineEditScanBarcode.text()
self.lineEditScanBarcode.clear()
if barcode == '12345':
print("Welcome John")
self.lineEditUser.setText() = 'John'
else: print("Sorry, user not recognised.")
Upon running this, I get the following error:
Syntax Error: can't assign to function call
I've had a look at the above error, but I'm still unsure as to what's going on here. I still have no idea to open one window on top of another (this software package will have about 10 windows), but that's another story!
Is my logic here on track? I've never used Qt before, so my understanding of the intricacies involved is lacking to say the least.
Any input would be great!
As the comment states, the error is on this line:
self.lineEditUser.setText() = 'John'
You are attempting to assign the value 'John' to that functioncall (as the error states). If you review the documentation for QLineEdit in PyQT, you'll see that QLineEdit.setText() requires a string to be passed to it.
So, what you need to do instead is pass the value 'John' to the function like so:
self.lineEditUser.setText('John')
On another note your idea that your
software package will have about 10 windows
is definitely something that you want to reexamine. More windows, especially when undocked and floating independently will no doubt cause usability issues. I'd strongly recommend sharing your ideas over at UserExperience.SE.

Categories

Resources