PyDT3 - The Missing Python API for DEVONthink

What is that touch via do shell meant to achieve that the echo isn’t achieving? Why is the first echo writing an empty string? Why isn’t the search limited to the desired database in the first place, instead of searching everywhere and then filtering the results? Why is the code using quoted form of for the same variable thrice instead of defining a new variable once?

I guess that are questions for the mechanical ape…

I can’t wait to see what mess ChatGPT is coming up for that.

Thank you for this incredibly useful API! One command that I need for my work that I noticed wasn’t yet included is the ‘merge’ command to merge records. If there is a way I can respectfully request it, I just wanted to put that out there. It is already a great API package, but the addition of that one command would mean that I could transfer a large applescript program that is always giving me issues with internal table overflow due to applescript’s scalability issues. Thanks to your package, I now have hope that I could run this in python.

1 Like

I have another question – since this API is a wrapper that communicates with applescript, will it always be slower to run than an applescript script that does the same commands? I have a code that is already a bit sluggish and I’m wondering if this is likely to get significantly worse if I convert it to python using this API. I don’t understand the backend well enough to get a sense.

Logically yes. As a wrapper executes, it eats CPU cycles. And then the AppleScript eats CPU cycles… no wrapper, less cycles. I doubt that it is significant though – if your script is already slow, you’ll probably not notice any slow down by a Python wrapper.

But given that PyDT3 does apparently not use AppleScript but JavaScript internally, I’d even bother less. As far as I understand it, you have to rewrite your script into Python. Which also means that you can and should use its advanced features for dates, array manipulation, objects etc. Which will probably make your code run finally faster than your AppleScript.

And you can, of course, rewrite your code in JavaScript/JXA which then will run without a wrapper :wink:

As to your previous question about the merge command: I can’t speak for the author of the API, but I guess that they left that out because the JXA implementation does not work as it should (at least it didn’t some months ago).

Thanks for the replies. I have another question: I can’t figure out how to restrict a search to a specific group, though the documentation suggests this is possible. In applescript, I would write:

set theResults to (search "name: test" in root of thisDatabase)

or

set theResults to (search "name: test" in searchRec)

where searchRec is a record of the group I want to search in.

In Python, I am trying:

from pydt3 import DEVONthink3
dt3 = DEVONthink3()
results = dt3.search("name=test")

But it always returns the results of searching the entire database. The only keywords allowed are comparison and excludeSubgroups. Does anyone know how to set a search scope in python? I tried using “name:test scope:selection” and “name:test scope:‘Test Database’” (where Test Database is the database name), but neither works.

Wrong syntax
"name: test scope: Test"
should work. I doubt that “selection” does anything in this context, though. Just type your search string in the text field in DT and then play around with the interface appearing below.

Aside: You could achieve the same results by simply using JavaScript directly instead of going through the Python wrapper. That removes one layer of complexity (but doesn’t give you merge either, as that’s still broken on the JXA level), and all the JavaScript methods, “objects” and properties are documented in DT’s scripting dictionary.

Thanks. However, (1) I can’t get your proposed syntax to work (see below) and (2) this doesn’t allow me to limit the scope to a specific group record.

On syntax:
I switched to a database called ‘Main’ to avoid any issue with the space. The lines

results = dt3.search("name: thing scope: Main")

and

results = dt3.search("name:thing scope:Main")

and

results = dt3.search("name:thing scope:'Main'"

and

results = dt3.search("name:thing") 

all produce the same number of results (and I know there are matches in other open databases). Did I misunderstand your response?

I’m sorry, that was my mistake. The scope is actually a parameter of the search method. In JavaScript, one would say
app.search("name: thing", {in: app.databases["Main"]});
or for a group
app.search("name: thing", {in: app.getRecordWithUuid("...")});

I have no idea how that is done in PyDT3, though. But as the source is available … oops, seems the author forgot about the in parameter. And they mis-spelled comparison – no idea if that will cause trouble or not.

As I said: simply use the original instead of a wrapper. You can’t lose anything, but possibly gain, as in this case, an in parameter :wink:

I am running into another error that perhaps someone more familiar with python and applescript dates can advise me on. I currently get an error when I try to use pydt3 to add custom meta data that is a date format in DT3. In python, for a record thisRecord, I use:

from pydt3 import DEVONthink3
import datetime
dt3 = DEVONthink3()
myDate=datetime.datetime.strptime("22.01.01", '%y.%m.%d')
dt3.add_custom_meta_data(myDate, for_=MDDATE, to=thisRecord)

Python gives the following error:

TypeError: Unsupported type: <class ‘datetime.datetime’>

I am able to set other custom meta data just fine, there is only an error when it is a date. I think it might be due to a difference in the python date object compared to the applescript date object, but I don’t know enough about these. Does anyone know how to convert from a python date object to one that DT3 can recognize? Thanks in advance.

Actually nevermind - it works when I add the date as a string instead of a date object in the format %d.%m.%y.

The applescript ‘log message’ command to report a message on the DT3 log does not seem to be implemented in this API. Does anyone know of another way to print text to the DT3 log using python? Thanks.

Somehow I cannot edit the op anymore. To make it clear, the lib actually uses JXA (Javascript for Automation) not exact AppleScript. Hope this would not confuse people as @chrillek reminded.

Also I’m working on improving the code readability and trying to expand it’s ability to other Applications like Mail, Reminders, etc.

It may be less performant compared to plain AppleScript when dealing with large number of objects (e.g. looping through the whole database with thousands of records) so that is worth noting.

1 Like

Hi! I have a simple question about using the API. When creating records in places other than the global inbox, how do I specify where to do that? Can I use the UUID of the group? Or a path?

# Create record in the group with UUID 38DC985A-F8F6-4008-8DAE-24CBE9C68B2B
record = dtp3.create_record_with({
    'name': "TESTING OUTPUT",
    'type': 'markdown',
    'plain text': "blah blah blah",
}, group='38DC985A-F8F6-4008-8DAE-24CBE9C68B2B')


The group='38DC985A-F8F6-4008-8DAE-24CBE9C68B2B' part is what I can’t figure out and isn’t based on anything in the documentation. But I would like to be able to specify it this way, if possible. Thank you!

Get a record object with something like getRecordWithUuid and pass that object instead of the UUID.
I don’t use PyDT3, that’s just what I’d do in JavaScript, on which PyDT3 is based.
Edit
Quoting from the GitHub repo:

def create_record_with(self, properties: dict, in_: Optional[‘Record’] = None) → Record:

And from the OP’s first post here:

Unlike many other API wrapper projects, PyDT3 is well documented thanks to the detailed AppleScript dictionary by DEVONthink team and the code generation ability of ChatGTP.

(there’s, BTW, the declaration of create_record_with just below this phrase!)
The alternative to a wrapper is, of course, to use the original. In this case, JXA.

@chrillek is totally right. The complete example:

from pydt3 import DEVONthink3
dtp3 = DEVONthink3()
dest = dtp3.get_record_with_uuid('38DC985A-F8F6-4008-8DAE-24CBE9C68B2B')
new_record = dtp3.create_record_with({
  'name': "TESTING OUTPUT",
  'type': 'markdown',
  'plain text': "blah blah blah"},
  in_=dest
)
print(new_record.name)
1 Like