I'm trying to create a custom column in Python
(I know that you're not fans of it, but as far as I understood from reading a few threads on this forum, it's impossible to do IPC with DLLs (e.g. directly communicate with the Everything service or MediaInfo) in JScript, while it's easy in Python — at least the hard work has been done: Everything has Python SDK example and MediaInfo has an old version of the script on this very forum)
I'd like to use global variables, which I usually store in Script.vars. However, I get an error NameError: name 'Script' is not defined when I try to execute e.g. sV = Script.vars (similarly, Script.config is out of the question)
Interestingly enough, I get no such error for Dopus.vars.
In win32trace debugging I see both objects created in the same way:
in <win32com.axscript.client.pyscript.PyScript object at 0x00000243A8630BB0>._InvokeEx_-AddNamedItem('DOpus', 2) [1,0,None]
in <win32com.axscript.client.pyscript.PyScript object at 0x00000243A8630BB0>._InvokeEx_-AddNamedItem('Script', 2) [1,0,None]
Is there a way to also fix the Script object accessibility somehow?
(and in general it'd be great if you changed your mind re. Python support given that it's more powerful )
(I'm using regular windows Python 3.8 x64 with pwin32 (globally registered to Win/System32) and with a registry fix so that DO sees Python as a scripting engine)
As I'm sure you've gathered we are reluctant to go down the rabbit hole of supporting Python, given that the active scripting implementation seems to go out of its way to be as different as possible to the JScript/VBScript engines
I've made a change to how the Script object is initialised in some cases so that now it's set up exactly the same way as the DOpus object (in most cases it was already identical anyway). Give the next beta a try and see if it helps, but if not then I'm not sure we'll be able to do anything more.
Thanks for the fix, looking forward to the next beta!
Yep, I've noticed your prejudices against Python in those long threads. I'd just like to add one more voice of support to the opposite side with a mention of two simply too awesome utilities to ignore that the default scripting engines can't handle
For all I know now, there is no easy way out regarding the current situation, because DOpus follows broken*) VBasic/JScript approach and it cannot be made compatible with pywin32 without significant refactoring of DOpus scripting API.
Over the years I have tried many practical approaches to handling DOpus python, notably:
Modifying pywin32 enables completely transparent handling of DOpus scripting, but obviously can conflict with any other scripting server used on the same computer. And I don't like this approach (not to mention messing with things I don't completely understand).
One promising solution which I have actually working, but never had time to complete, is DOpus proxy, which works like this:
instead of FlagAsMethod ugliness (note the proper () after Create and Command). The issue is that I have never had incentive to make it a full-fledged, well tested piece of code (maybe I have a bit more now). Plus this approach has its issues as well (no real introspection into what is hidden behind the proxy). I also tried fe. marrying MagicMock with DOpus scripting object, but boy, what fun it was.
)* Broken - because if function has the same effect as function(), you are not able to get the function object itself, which really breaks a lot of stuff in languages (hint: python) with first-class functions. I guess such approach is a leftover from VBasic times and hacked somehow into JScript.
That's a bummer, but I'm still hoping that a few fixes like the one @Jon mentions might suffice for the relatively simple tasks that I need, but that only Python can solve. Though of course I'd love to live in the dream world of DOpus manual "The advantage of this is that Opus scripting is language neutral" and not have to scratch my head looking at those FlagAsMethods in your script
yeah, another part of the legacy biting hard from its grave
Because they are standard, well understood parts of Windows which everyone is guaranteed to have.
Even if the ActiveScripting versions of Phyton followed the standards set by the first party languages and worked perfectly, we'd still discourage using them because everyone who wanted to use a script written in another language would have to install extra stuff, worry about version conflicts, etc., which just isn't worth it for what is usually a small difference in syntax that shouldn't matter for the typical length and complexity of a script.
(Being able to call DLLs is a valid thing, of course, but very niche. If it came to it, we could provide that capability to JScript and VBScript via our own objects.)
I've tried the latest beta and got a different error
:Error at line 43, position 0
sV = Script.vars
^
Traceback (most recent call last):
File "<Script Block >", line 43, in OnInit
sV = Script.vars
...\win32comext\axscript\client\pyscript.py", line 105, in __getattr__
return getattr(self._scriptItem_.dispatchContainer,attr)
...\win32com\client\dynamic.py", line 527, in __getattr__
raise AttributeError("%s.%s" % (self._username_, attr))
AttributeError: Script.vars
(0x80020009)
Is there a way to fix it?
FYI extracts of the code from the files mentioned:
Extract from pyscript
def __getattr__(self, attr):
# If a known subitem, return it.
try:
return self._scriptItem_.subItems[attr.lower()].attributeObject
except KeyError:
# Otherwise see if the dispatch can give it to us
if self._scriptItem_.dispatchContainer:
#Next Line is #105
return getattr(self._scriptItem_.dispatchContainer,attr)
raise AttributeError(attr)
Extract from dynamic.py
# Delegate to property maps/cached items
retEntry = None
if self._olerepr_ and self._oleobj_:
# first check general property map, then specific "put" map.
retEntry = self._olerepr_.propMap.get(attr)
if retEntry is None:
retEntry = self._olerepr_.propMapGet.get(attr)
# Not found so far - See what COM says.
if retEntry is None:
try:
if self.__LazyMap__(attr):
if attr in self._olerepr_.mapFuncs: return self._make_method_(attr)
retEntry = self._olerepr_.propMap.get(attr)
if retEntry is None:
retEntry = self._olerepr_.propMapGet.get(attr)
if retEntry is None:
retEntry = build.MapEntry(self.__AttrToID__(attr), (attr,))
except pythoncom.ole_error:
pass # No prop by that name - retEntry remains None.
if not retEntry is None: # see if in my cache
try:
ret = self._mapCachedItems_[retEntry.dispid]
debug_attr_print ("Cached items has attribute!", ret)
return ret
except (KeyError, AttributeError):
debug_attr_print("Attribute %s not in cache" % attr)
# If we are still here, and have a retEntry, get the OLE item
if not retEntry is None:
invoke_type = _GetDescInvokeType(retEntry, pythoncom.INVOKE_PROPERTYGET)
debug_attr_print("Getting property Id 0x%x from OLE object" % retEntry.dispid)
try:
ret = self._oleobj_.Invoke(retEntry.dispid,0,invoke_type,1)
except pythoncom.com_error as details:
if details.hresult in ERRORS_BAD_CONTEXT:
# May be a method.
self._olerepr_.mapFuncs[attr] = retEntry
return self._make_method_(attr)
raise
debug_attr_print("OLE returned ", ret)
return self._get_good_object_(ret)
# no where else to look.
# Next line is 527
raise AttributeError("%s.%s" % (self._username_, attr))
They do, and Script.vars.get("ConfigSettingName") allows me to use user config values (with this helper script), which is what I'm trying to do by pasting some of the plumbing from my JS scripts
Hm, I think I haven't made any typos, but this does nothing on the latest beta (12.20.8, but also tried with the pre-script beta 12.20.5), so Doesn'tWOMPC .
I also don't really understand why it would do anything since in the column scripts the initData._FlagAsMethod('AddColumn') works, i.e. the col.method is invoked, only when columns are refershed
Is the initData._FlagAsMethod('AddCommand') supposed to be called on every script refresh automatically (or is there some other event there that triggers it)? And if I add a direct call in the OnInit function OnTestCmd(""), I get the same attribute error mentioned above
However, when I change Script.vars to DOpus.vars (but still I need to add an OnTestCmd("") in the OnInit function, I get this log
...TestPy.pys: script_cmd_data
...TestPy.pys: test
OK, first, there is stupid mistake in my sample, it should be for "v in test". It does not print out anything then, but I guess this is expected (or not?) - there is no error though.
Second, OnInit is called automatically by DOpus on script refresh (that is when loading DOpus or when script file is saved - maybe there are some other occasions as well). You don't call it manually.
Third, the sample below works without errors, it does not print anything either from Script.vars or DOpus.vars, but I think this is expected. It does not fail silently either. DO version: Directory Opus Pro 12.20 Build 7394 x64
Note win32traceutil import. It allows your to print() in order to debug - you can see output in PythonWin> Tools> Trace Collector Debugging Tool
import win32traceutil
# Called by Directory Opus to initialize the script
def OnInit(initData):
initData.name = "test";
initData.version = "1.0";
initData.copyright = "(c) 2020 Łukasz";
# initData.url = "https://resource.dopus.com/viewforum.php?f=35";
initData.desc = "test";
initData.default_enable = True;
initData.min_version = "12.0";
initData._FlagAsMethod('AddCommand')
cmd = initData.AddCommand()
cmd.name = 'TestCmd'
cmd.method = 'OnTestCmd'
cmd.desc = initData.desc
cmd.label = 'TestCmd'
def OnTestCmd(script_cmd_data):
DOpus.Output(1)
test = Script.vars
DOpus.Output(2)
for v in test:
DOpus.Output(v)
DOpus.Output(3)
test = DOpus.vars
DOpus.Output(4)
for v in test:
DOpus.Output(v)
DOpus.Output(5)
there is no error because the script simply doesn't execute that part. Once I add a call to that function OnTestCmd(""), then it shows an error
Yeah, that part I know, I just had to connection to how that is relevant to another, not OnInit, function (now that you mention a button, it's clear — the button calls on this part)
Let me check your script later and see if it works...
Yeah, I always include it, that's where I see the vars created and such that I mentioned earlier, but didn't know about the fact taht print() outputs there, thanks, that's very helpful in trying to understand how things work!!!
The script executes to the end, as you can see via numbers 1-5 printed to DOpus log panel.
How do you call OnTestCmd? It is a scripted DOpus command, so it should be called like a built-in command.
What is the actual code you run and how do you run it?
It does work now, thanks! I was just saying that previously I simply forgot I needed a button to run your script example
I've also already been able to do some successful testing:
setting/getting script.vars
writing to Script.config.Name1 and then reading from it
creating a Map to store config description, although had to confusingly use different types of parentheses (assign myMap["MapKey"] vs read myMap("MapKey"))
DOpus._FlagAsMethod('Create')
factory = DOpus.Create()
factory._FlagAsMethod('Map')
myMap = factory.Map()
myMap("MapKey") = "MapValue" # cannot assign to function call (0x80020009)
myMap["MapKey"] = "MapValue" # Works
DOpus.Output(myMap["MapKey"]) # in32com\client\dynamic.py", line 257, in __getitem__: raise TypeError("This object does not support enumeration")
DOpus.Output(myMap("MapKey")) # Works
Here is a post where some scripting surprises are explained (like using () for collections) Scripts (Python): Getting up to speed with Python + sample script
You may want to avoid DOpus pseudo-types (like Map), however I am not sure whether dict or list would be accepted by DOpus in place of them (you would need to do a conversion anyway).