Git Product home page Git Product logo

pyxa's Introduction

The dark logo for PyXA

Python for Automation

Python for Automation, or PyXA for short, is a wrapper around several macOS frameworks that enables AppleScript- and JXA-like control over macOS applications from within Python. PyXA's objects and methods are based on applications' scripting dictionaries and coupled with additional automation features supported by Apple's macOS APIs.

PyXA was created with the goals of:

  1. Simplifying the way automation tasks can be accomplished via Python
  2. Introducing new features to macOS application scripting by simplifying complex procedures into simple, declarative methods
  3. Disambiguating the capabilities of application scripting on macOS by providing easy-to-follow documentation throughout the entire project

PyXA fills a gap where currently available frameworks ultimately fall short: it aims to be easy to learn for users accustomed to Python (or users who must use Python). To that end, the package's documentation contains numerous examples of how to use just about every method, and additional examples are provided covering specific use cases. PyXA's code also serves as a source of examples for how to use PyObjC to interact with various macOS frameworks.

PyXA is not intended to replace AppleScript or even to cover 100% of AppleScript's capabilities. Instead, PyXA is meant to provide general convenience in accomplishing AppleScript and other automation tasks via Python, for the most commonly used applications. If you need a complete Apple Event bridge, or if you find that PyXA cannot handle your particular use case, consider using appscript or one of its derivatives. If you just need something that works in most circumstances, that has abundant examples for you to reference, and supports some additional automation features (such as opening Maps to a specific address), then PyXA might be a good fit for you.

Feature Overview

  • Support for most AppleScript commands in built-in macOS applications and some third-party applications (in progress)
  • Scripting capabilities for several non-scriptable applications by using APIs, URI schemes, and other methods
  • Support for direct operations on non-scriptable applications (e.g. PyXA.application("Maps").front_window.collapse())
  • Command Chaining similar to JXA (e.g. PyXA.application("Reminders").lists()[0].reminders().title())
  • Properties of scriptable elements accessible via object attributes (e.g. note.name, tab.URL, or track.artist)
  • Support for UI scripting of non-scriptable applications
  • Fast enumeration of scriptable objects
  • Automatic translation of clipboard items to PyXA objects
  • Support for compiling and executing AppleScript scripts via NSAppleScript
  • Full access to and control over the system clipboard
  • Support for dialogs, alerts, file/color pickers, and notifications
  • Classes for speech and speech recognition
  • Ability to create custom menu bar items

Some Examples

Example 1: Open a URL in Safari and print the loaded page.

PyXA can control Safari and interact with its content. In this example, we use PyXA to obtain a reference to the Safari application, open a specific URL, then bring up the print dialog for the loaded page. If we wanted, we could pass additional parameters to the print() method to skip the print dialog and immediately print the page without any user interaction.

import PyXA
from time import sleep
safari = PyXA.Safari()
safari.open("https://www.apple.com")
sleep(1)
safari.current_document.print()

Example 2: Print Music track info whenever the track changes.

PyXA can also be used to interact with the Music app. In this example, we use PyXA to get a reference to the Music app, begin playback of the next-up song, then repeatedly print out some information about the track whenever the current track changes. The information will be printed regardless of how the track changes, so you can test this script by running it and skipping to the next song.

import PyXA
from time import sleep
music = PyXA.Application("Music")

# Wait for Music.app to be ready to play
music.activate()
while not music.frontmost:
    sleep(0.5)
music.play()

track_name = ""
while True:
    if music.current_track.name != track_name:
        track_name = music.current_track.name
        print(music.current_track.name)
        print(music.current_track.album)
        print(music.current_track.artist, "\n")

When run, this script produces an output similar to the following:

Die Hard
Mr. Morale & The Big Steppers
Kendrick Lamar, Blxst & Amanda Reifer 

I Like You (A Happier Song) [feat. Doja Cat]
Twelve Carat Toothache
Post Malone 

WAIT FOR U (feat. Drake & Tems)
I NEVER LIKED YOU
Future

...

Example 3: Create a new note with a list of all events and reminders for the day.

PyXA can also be used for more complex tasks. In this example, we use PyXA to get a summary of upcoming reminders and events for the day. We obtain references to the Notes, Calendars, and Reminders applications, then we iterate through our reminders and events, creating a new line of text to summarize each item due today. We apply some HTML formatting to the note to make it look nice, then we create a new note containing the summarized content.

import PyXA
from datetime import datetime, timedelta

# Activate Reminders to speed up communication of Apple Events
reminders = PyXA.Application("Reminders").activate()
notes = PyXA.Application("Notes")
calendar = PyXA.Application("Calendar")

# Get names of incomplete Reminders using a bulk method
names = reminders.reminders({ "completed": False }).name()

# Create a string listing incomplete reminders
note_text = "-- Reminders --"
for name in names:
    note_text += f"<br />Reminder: {name}"

# Get Calendar events starting within the next 2 days
start = datetime.now()
events = calendar.calendars().events().between("startDate", start, start + timedelta(days=2))

# Get event summaries (titles), start dates, and end dates using bulk methods
summaries = events.summary()
start_dates = events.start_date()
end_dates = events.end_date()

# Append the list of event information to the note text
note_text += "<br/><br />-- Events --"
for index, summary in enumerate(summaries):
    note_text += "<br />Event: " + summary + ", from " + str(start_dates[index]) + " to " + str(end_dates[index])

# Create and show the note
note = notes.new_note(f"<h1>Agenda for {start.strftime('%Y-%m-%d')}</h1>", note_text)
note.show()

When run, the above script creates a note in the Notes application similar to the following:

A note in the Notes app showing a summary of reminders and events for the day

Example 4: Copy your last sent file in iMessage to the clipboard

Lastly, PyXA has several convenient features for working with lists, interacting with the clipboard, and more soon to come. The example below highlights the simplicity of filtering lists of scriptable objects and setting the content of the clipboard. The filter method of PyXA's :class:XABase.XAList class enables straightforward construction of predicates to efficiently filter lists by. The content property of the :class:XABase.XAClipboard class can be set to both literal values and PyXA objects, allowing for concise scripts like the one below.

import PyXA
app = PyXA.Application("Messages")
last_file_transfer = app.file_transfers().filter("direction", "==", app.MessageDirection.OUTGOING)[-1]
PyXA.XAClipboard().content = last_file_transfer

Installation

To install the latest version of PyXA on macOS, use the following pip command:

python -m pip install mac-pyxa

Documentation

The best way to learn about PyXA is to read the documentation. From there, you can find tutorials, examples, in-depth class and method documentation, and additional resources.

For further help, consider joining the (PyXA Discord Server](https://discord.gg/Crypg65dxK) and asking your questions there.

Known Limitations

  • Currently, PyXA only supports macOS automation. There is a goal to expand support to other operating systems, but no concrete plan exists at this time.
  • Since PyXA uses hard-coded class and method definitions, instead of deriving them automatically from existing sdef files, support for third-party applications is limited to the applications that contributors deem relevant. This is a sacrifice made in order to have detailed, consistent documentation for all supported applications.

Limitations of specific applications and methods are noted in their respective reference documents.

Contributing

Contributions are welcome, big or small. Please refer to the Contributing Guidelines for any contributions larger than a spelling correction. For small fixes such as spelling corrections, no issue needs to be created; you can go right to making a pull request. Other small issues include general grammar fixes, short comment additions, and formatting (whitespace) changes.

Contact

If you have any questions about PyXA that are not addressed in the documentation, or if you just want to talk, feel free to email [email protected].

pyxa's People

Contributors

skaplanofficial avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

pyxa's Issues

install via conda: pyobjc & bs4 missing

as it says on the tin; pyobjc, bs4 seem
pip install pyobjc + pip install bs4 fixed things.
Somewhat surprisingly, since by the looks of it, the pyobjc related deps are listed in requirements.txt, but bs4 isn't

Super cool lib!

Support older Python versions (3.10, 3.9, ...)

Currently, PyXA uses particular type hints, match statements, and other features only available in Python 3.11. This simplifies the code in some areas, but it reduces the ability for people to use PyXA in general projects.

  • Is this is worthwhile change?
  • Can multiple versions be supported at a time?
  • How far back should we support (As far back as PyObjC supports?)

No calendars found

Hi,

I'm trying out PyXA to extract calendar events, but it does not seem to work. I've tested print(PyXA.Application("Safari").front_window.current_tab.url) which works fine, so my PyXA install should be working.

app = PyXA.Application("Calendar") print(app.calendars())

leads to an empty list

<<class 'PyXA.apps.Calendar.XACalendarCalendarList'>[]>

despite me having a ton of calendars visible in the Calendar app. All my calendars are tied to categorties or accounts like iCloud and Google and "other".

I have tried to look in the docs, but can't find a reference to finding calendars in any other way.

Tried creating a new calendar, but that also does not work:

`Traceback (most recent call last):
File "(My project path)/test.py", line 9, in
new_calendar = app.new_calendar("PyXA Development")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "(My project path)/.venv/lib/python3.11/site-packages/PyXA/apps/Calendar.py", line 244, in new_calendar
self.calendars().push(new_calendar)
File "(My project path)/.venv/lib/python3.11/site-packages/PyXA/XABase.py", line 884, in push
self.xa_elem.get().addObject_(element.xa_elem)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'addObject_'

Process finished with exit code 1`

Keynote Application crashes / dies after setting properties on new document

Hi there,
it sounds like a very specific problem and I also can't tell if it's not even due to the Keynote app itself.

When I run the sample code below, the keynote app dies about 20 seconds after I set properties of a new document.
With pure AppleScript I could not observe this problem.

Could this have something to do with PyXA or the PyObjC framework used in relation to the garbage collection algorhythm?

# -*- coding: utf-8 -*-

import PyXA
import time

keynote = PyXA.Application('Keynote')
keynote.activate()

newDocumentName = 'Untitled'
wrongDocument = keynote.new_document(newDocumentName)
# If a document was already open, not the newly created document is returned 
# here, but the previously existing document. Keynote seems to re-sort the 
# list with the creation of documents.

newDocument = keynote.documents()[0]
# setting the properties of the newly created document crashes keynote in 
# about 20 seconds...
newDocument.width = 2224
newDocument.height = 1668

time.sleep(20)

# by now, the keynote app should have crashed / died by accident.
# so the next statement should fail...
currentDocument = keynote.documents()[0]

pass

`PyXA.AppleScript.load` removes curly braces, breaking AppleScript lists read in from external files.

If I initialize a PyXA.AppleScript class with an AppleScript code string containing a list, the code is read in correctly as expected:

>>> import PyXA
>>> script = PyXA.AppleScript('set theList to {"foo", "bar"}')
>>> script.script
['set theList to {"foo", "bar"}']

However, if I have an AppleScript file with the same code, and read it in with PyXA.AppleScript.load, the curly braces get unexpectedly removed, causing the script to error out:

test.applescript

on run
  set theList to {"foo", "bar"}
end run

(Following on from the previous command prompts...)

>>> file_script = PyXA.AppleScript.load("test.scpt")
>>> file_script.script
['on run', '\tset theList to "foo", "bar"', 'end run', '']

Intermittent AttributeError when Invoking Shortcuts

A few times in the past week, my code that invokes Shortcuts starts failing. Unclear what is triggering the issue, but once it occurs, the only way to resolve (that I've uncovered so far) is to reboot.

Example code

shortcuts_app = PyXA.Application("Shortcuts")
add_url = shortcuts_app.shortcuts().by_name("Add iTunes Store URL to Playlist")

results in

Exception has occurred: AttributeError
'SBApplication' object has no attribute 'shortcuts'

ModuleNotFoundError: No module named 'PyXA.apps.Finder' - MacOS Application Issue

Hi @SKaplanOfficial

Thanks for sharing this code.

I have been using the jxa script and running it from Python and it was working fine.

I have tested PyXA code and I found some issues/challenges. When executing directly from VS Code, it is working fine, But when I create a Mac App using Pyinstaller and executing throws an error. The same thing is happening with jxa script as well. I am new to MacOS development and don't have much idea about what is missing.

Codesign:

dist/MyApp.app: valid on disk
dist/MyApp.app: satisfies its Designated Requirement

Error Message:

Traceback (most recent call last):
  File "myapp/app_observer.py", line 31, in get_window_title
  File "PyXA/XABase.py", line 1071, in __init__
  File "PyXA/XABase.py", line 1122, in __get_application
  File "importlib/__init__.py", line 126, in import_module
  File "<frozen importlib._bootstrap>", line 1204, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1140, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'PyXA.apps.Chromium'

...

ModuleNotFoundError: No module named 'PyXA.apps.Console'

...

ModuleNotFoundError: No module named 'PyXA.apps.SystemEvents'
...

ModuleNotFoundError: No module named 'PyXA.apps.Finder'

I have used the below code to get front_window and storing the result in a log file.

import PyXA
import re

def get_window_title(active_app):
    app = PyXA.Application(active_app)
    window_title_str = str(app.front_window)

    # Use regular expression to extract the window title
    pattern = r">(.+?)>\s*$"
    match = re.search(pattern, window_title_str)
    window_title = match.group(1).strip()
    return window_title

As the code is working fine while running from vscode, I guess it is something to do with Mac OS permission. Please help.

Request: Filter by whether an attribute exists

In AppleScript, there is an exists method to check if an attribute is defined on an object. A common use case for this is determining if a parent object exists in an object hierarchy (i.e. "if parent exists"), and it is usually more performant because the full object reference does not need to be resolved to evaluate it.

Would it be possible to add "exists" support to filters?

My particular use case is to get all "top-level" Music folder playlists, filtering out any folders that are sub-folders. I can retrieve this in AppScript as follows, but would prefer to do this natively in PyXA if possible.
[f for f in appscript.app('/System/Applications/Music.app').folder_playlists() if not f.parent.exists()]

Unable to run tutorial - dependency issues & more

I am following the instructions at https://skaplanofficial.github.io/PyXA/tutorial/index.html in a fresh virtual environment.

When I get to the first run of basics.py, I get the error:
ModuleNotFoundError: No module named 'macimg'

If I pip install macimg and re-run basics.py, then I get:
ModuleNotFoundError: No module named 'ApplicationServices'

If I then pip install pyobjc-framework-ApplicationServices and re-run basics.py, I get:
ModuleNotFoundError: No module named 'CoreText'

If I then pip install pyobjc-framework-CoreText and re-run basics.py, it seems to satisfy the dependencies. However, I then get a different error:

Traceback (most recent call last):
  File "/Users/me/temp/PyXA Examples/basics.py", line 3, in <module>
    print(PyXA.application("Safari").front_window.current_tab.url)
TypeError: 'NoneType' object is not callable

At this point, I don't know how to proceed - is this a code issue, or a documentation issue? Any guidance is very much appreciated.

Music App example request

As I am learning to use PyXA, I am playing with the Music App to further my knowledge.

One of the things I am struggling with is identifying how to access certain data elements...for example, I would like to get a list of all Apple Music playlists that I have subscribed to.

I see this is provided by the subscription_playlists() method in XAMusicSource

What I can't seem to derive from the documentation is how to obtain a XAMusicSource object. It's mentioned under "See also" in the XAMusicApplication. What does "See also" mean? How do I get from app -> source -> playlist?

On a whim I tried this: PyXA.Music().subscription_playlists() which apparently diverted me into appscript (I had to install the package to continue). It did give me a list, but the objects looked like app('/System/Applications/Music.app').sources.ID(64).subscription_playlists.ID(182724) - they were not PyXA objects (I expected XAMusicSubscriptionPlaylist). I seem to have stepped out of the realm of PyXA, which I'm guessing is not correct.

My IDE is VSCode, and I'm trying to use both IntelliSense and the debugger to navigate the API and make sense of things. But those tools are definitely not uncovering everything I need to solve this puzzle. I feel like I'm missing some sort of programming pattern.

Is there any guidance you can provide for solving this particular use case, and/or just navigating the API in general? Thank you for any help or examples you can provide.

2 issues

  1. Scripting Bridge is straight garbage. Defective by design: crippled, weak, incompetent, unsupported, dead. Should have opened Mac Automation to millions of enthusiastic new Python, Ruby, and ObjC coders; was first nail in its coffin instead. Worst. Foundation. Ever.

  2. Appscript already exists, and has done so for the last 18 years. The Python3 version still works too. Just use that.

Python appscript and its descendents speak Apple events right. Furthermore, appscript’s the only AE bridge in the last 30 years that gets Apple events right. Every other AE bridge: gensuitemodule, Mac::Glue, JavaScriptOSA, aeve, appscript (pre-0.6.0), RubyOSA, Scriptarian, JXA, ScriptingBridge, and now, by extension, PyXA has done it fatally wrong. Apple event IPC is not Object-Oriented Programming, it is RPC plus simple, first-class relational queries. You can’t force that into an OO mold, it doesn’t fit. And when users try to use those broken bridges with real-world “AppleScriptable” apps, some trivial things work alright but everything beyond that just breaks. Which is why all of these bridges are long-since defunct: they were all even worse than using AppleScript. Except for appscript, which eventually got better after a total rethink+redesign+rewrite, plus another 3 years of shaking out a myriad compatibility bugs with the aforementioned real-world apps until it was the world’s best Apple event bridge bar none and was poised for inclusion in 10.5… until my own laziness and naivety about how the world works saw it clobbered by Apple’s in-house nonsense instead. But that’s a separate story.

Do a little due diligence, studying all the prior art before you jump in yourself and figuring out why they all failed, and you’ll save hours of your life from reinventing the same broken useless wheel that a dozen others already made and abandoned before you.

If you want to automate macOS apps using Python: pip3 install appscript. Read the documentation (which is crusty and not great, though still leagues above all of Apple’s), download the ASDictionary and ASTranslate support apps which can answer 99% of “How do I…?” questions for you (then curse me if you’re on an ARM Mac as I’ve still not bothered to codesign them), and you’re golden [1]. I no longer offer (free) support—I don’t even build its new .whl wheels now (another user has taken on that)—although it is possible to get on my good side if there is anything you really want to know (i.e. just punt a tenner to charity).

Else, Python3 appscript is quite straightforward, incredibly competent (99.9% compatibility with real-world apps), and easily the best Mac Automation technology that Apple’s Mac Automation team ever tossed down the toilet cos they couldn’t give a crap. BTW, there isn’t even a Mac Automation team at Apple any more: they got themselves disbanded in 2016 at the end of a series of failures. The entire AE/OSA/AppleScript stack has been shoved into legacy maintenance ever since, left quietly to die out by itself. Apple has now begun discarding AS features, starting with dropping the AppleScript-ObjC project template in Xcode 14. I think it fair to expect more to go over the coming years as Apple moves from Mac-only AppleScript to cross-platform Shortcuts. To paraphrase Sancho Panza: there’s not much point still tilting at windmills when the windmill itself awaits demolition.

HTH

--

[1] Or, just use py-applescript which is a capable wrapper around AppleScript itself which allows you to call AppleScript handlers directly and takes care of converting standard Python types to AS types and back.

Specialized 'XAMusic' Objects not Returned by Filters

Similar to what was addressed in #8 , I am seeing more instances of XAMedia base objects being returned in the Music app, rather than their specialized XAMusic variants.

See example below. If I try to filter a XAMusicUserPlaylistList, the resulting objects are of the XAMediaUserPlaylist base class.

Example:

PyXA.Music().user_playlists()
<<class 'PyXA.apps.Music.XAMusicUserPlaylistList'>length: 1171>

PyXA.Music().user_playlists().first
<PyXA.apps.Music.XAMusicUserPlaylist object at 0x106c9e260>

PyXA.Music().user_playlists().by_name("MY PLAYLIST")
<PyXA.apps.MediaApplicationBase.XAMediaUserPlaylist object at 0x106c2ff70>

PyXA.Music().user_playlists().by_property("name", "MY PLAYLIST")
<PyXA.apps.MediaApplicationBase.XAMediaUserPlaylist object at 0x106c9fca0>

update pyobjc version

Is there a reason pyobjc is pinned to 8.5.1? The current version is 9.0.1 which adds support for macOS 13 specific frameworks. I'm happy to test & submit a PR but wanted to first check to see if there's a reason for the specific version pin.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.