pyobjc: the Python to Objective-C bridge, and the dozen framework wrappers that come with it
The Python <-> Objective-C Bridge with bindings for macOS frameworks
At a glance
- What is it?
- PyObjC lets Python talk to macOS frameworks directly, and the repository is not a single package but a bridge plus a wrapper per framework. It is CPython only, and the master branch is explicitly the unstable one, so releases are the artefact to depend on.
- Who is it for?
- pyobjc fits a Python developer on macOS who needs to reach a system framework without writing Objective-C, and who can accept CPython only and a per framework dependency list. It does not fit a project that must run on PyPy, Jython or IronPython, and it does not fit a team that wants the master branch as a dependency.
- Can I use it commercially?
- Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One bridge, one meta package, and a wrapper per framework
PyObjC is described as the Python to Objective-C Bridge with bindings for macOS frameworks, and the repository description makes the packaging clear.
There are three kinds of package in the tree. The first is pyobjc, a meta package used for easy installation with easy_install or pip, so that a user can get a working set in one step. The second is pyobjc-core, which is the actual bridge. The third is the pyobjc-framework-* family, wrappers for specific frameworks or sets of frameworks.
The scale of that third family is the thing to notice. The repository root lists wrappers for ARKit, AVFoundation, AVKit, Accessibility, Accounts, AdServices, AppTrackingTransparency, AppleScriptKit, ApplicationServices, AudioVideoBridging, AuthenticationServices, AutomaticAssessmentConfiguration, Automator, BackgroundAssets, BrowserEngineKit, BusinessChat, CalendarStore, CallKit, Carbon, Cinematic, ClassKit, CloudKit, Cocoa, Collaboration, ColorSync, CompositorServices, Contacts, ContactsUI, CoreAudio, CoreAudioKit, CoreBluetooth, CoreData, CoreHaptics, CoreLocation, CoreMIDI, CoreML, CoreMedia, CoreMediaIO and CoreMotion, and the list continues well past the visible portion of the directory listing.
That is the practical shape of the project. There is a small core that does the hard work of talking to the Objective-C runtime, and then a large body of generated, framework-specific code that exposes Apple's object model to Python. When you want a Cocoa application, you install the Cocoa wrapper; when you want to run a machine learning model, you install the CoreML wrapper. You are choosing which frameworks you pull in, not whether Python can reach the system at all.
The documentation lives on Read the Docs at pyobjc.readthedocs.io, and the last push to the repository was on 2026-10-16 with the repository not archived.
Master is the development branch, and it says so in the readme
The first note in the README is the one to read before anything else. The master branch is currently used for feature development, as such it should be considered unstable, and the instruction is to use the latest release instead.
That is a short note with a large consequence, because PyObjC is the kind of project people are tempted to install from a git URL. The release tags are frequent and well behaved: v12.2 on 2026-05-30, v12.2.1 on 2026-06-19 and v12.2.2 on 2026-08-11, which is the pattern of a project with a steady maintenance rhythm rather than a dormant one. The default branch, meanwhile, moves between releases and can break the import of a framework you depend on.
So the choice is between a versioned artefact and a moving target, and the project's own documentation picks the versioned one. For a project that other people depend on through a package index, that is the only sensible default, and it is also why the install route below ends up pointing at the repository rather than at a wheel.
The repository also carries the tooling a maintained Python project of this size accumulates: a pre-commit configuration, a .flake8 file, an .isort.cfg, a .mypy.ini, a .clang-format file, a .codespellrc, a .readthedocs.yaml for the documentation build, a Makefile, and a development-support/ directory with helper scripts.
Installing from a checkout means running install.py
The README gives one route for installing from the repository, and it is the install.py script that sits next to the readme file. The process starts with a check-out:
$ git clone https://github.com/ronaldoussoren/pyobjcand then the installation step, where X.Y stands for the Python version you want to install into:
$ pythonX.Y pyobjc/install.pyThe script exists because a project with a meta package, a core and dozens of framework wrappers cannot be installed by a single ordinary pip command from a source tree. The wrappers have to be generated or built, and the script is what drives that.
The alternative for ordinary users is the packaged route, since pyobjc is published as a meta package for installation with easy_install or pip, and the framework wrappers are available individually under their own names. The README describes the repository install as the easiest way to install PyObjC from the repository, which is a narrower claim than it being the easiest way to install PyObjC at all, and the distinction matters for a project with this many packages.
There is also a develop.py file at the root of the repository, which is the usual companion to an install script in a project of this shape: one for a developer working in the tree, one for setting up an environment.
CPython only, and the project says the alternative is unlikely to change
The supported Python versions section is short and includes a firm statement about the future. PyObjC is regularly tested using Python 3.9 and later. The author is also regularly testing using the development version of Python, although support for that might be broken when there are large changes in the implementation.
Then the exclusion: PyPy, Jython and IronPython are not supported, and it is unlikely that this will change anytime soon.
That last clause is the useful one for an adopter, because it is the difference between a limitation you might wait out and a decision you should plan around. The bridge is built on CPython's C API and its extension machinery, and the reason a different interpreter is unsupported is not that nobody has tried but that the work would have to be redone.
The consequence is that PyObjC inherits CPython's release rhythm in both directions. New CPython versions are worth watching, because the project tests against the development version of Python, and any large change in the interpreter implementation is exactly the case the README warns could break support. An application that has to run on PyPy cannot use this bridge at all, and a team that plans to move to a different interpreter should treat that as a constraint rather than a future migration.
Shared code has to be regenerated, and the Makefile does it for you
The top level Makefile is short, and it explains an important structural fact about the project:
all: update-shared extension-format python-format
update-shared:
development-support/update-shared-filesThe default target runs three steps in order. The first, update-shared, runs the development-support/update-shared-files helper, which regenerates the code that many of the framework wrappers share. The second formats the C sources with clang-format, and the third formats the Python with black.
The formatting targets are worth reading closely, because they encode the project's style rules rather than a generic default:
extension-format:
clang-format -i --style=file --assume-filename='foo.m' $(shell find */Modules -name '*.[chm]')The assume-filename trick is there because the Objective-C sources do not have a .m extension, so clang-format needs to be told what language it is looking at. The find pattern reaches into every framework's Modules directory, which tells you where the generated C lives: each pyobjc-framework-* package has its own Modules subdirectory.
The Python formatting target uses black with a line length of 90 rather than the default, and it excludes the generated files:
python-format:
black --py36 --line-length=90 --exclude=_metadata.py --exclude='.*\.pbfiletemplate' .The exclusions are the point: _metadata.py is generated, and the .pbfiletemplate files are templates, so neither should be reformatted. A repository that formats only what it owns, and regenerates the rest, is a repository whose diffs stay readable.
Where PyObjC is the wrong tool
The first wrong fit is an interpreter that is not CPython. That is not a workaround, it is an exclusion the project states plainly, so if your deployment target is PyPy, Jython or IronPython, the decision is made for you.
The second is a single narrow API. PyObjC gives you a framework, in the full sense, with the whole object model, the class hierarchy and the delegate patterns intact. If what you need is one C library with a handful of functions, a small hand written binding is a much smaller thing to maintain, with a much smaller dependency list and no generator step at all. The trade runs in both directions and it is about surface area rather than quality.
The third is a deployment that must be reproducible without a compiler in the loop. The installation path in the README builds from a checkout, and a project with this many generated packages implies build tooling on the machine. The published packages exist for the common case, but if your policy is that nothing is installed from source, you should check what the published artefacts actually contain before you commit to it.
The fourth is a team that wants a stable API surface and will track master. The README says the master branch should be considered unstable, and with a bridge of this size that is not a formality: a change in the runtime layer can change how every wrapper behaves. Pin a release.
The fifth is a cross platform requirement. Everything here is macOS, because that is where the frameworks being wrapped live. A project that has to run on Linux and Windows as well is not going to get there with this bridge, and no amount of configuration changes that.
Editorial conclusion
pyobjc fits a Python developer on macOS who needs to reach a system framework without writing Objective-C, and who can accept CPython only and a per framework dependency list. It does not fit a project that must run on PyPy, Jython or IronPython, and it does not fit a team that wants the master branch as a dependency. Before adopting it, install a tagged release rather than a clone, and check that the framework wrapper you need exists in the list rather than assuming the core package covers it.
Frequently asked questions
What is PyObjC?
It is the Python to Objective-C bridge for macOS, shipped as a set of packages: a meta package called pyobjc for easy installation, pyobjc-core which is the actual bridge, and a pyobjc-framework-* package for each macOS framework you want to reach.
How do I install PyObjC from the repository?
The readme points at the install.py script that sits next to it. Clone the project, then run pythonX.Y pyobjc/install.py with the version of Python you want to install into.
Which Python versions does PyObjC support?
Testing covers CPython 3.9 and later, plus the development version of Python, although large interpreter changes can break that. PyPy, Jython and IronPython are excluded, and the readme says that is unlikely to change.
Should I install PyObjC from the master branch?
No, the readme marks master as the feature development branch and tells you to take a release instead. Recent tags include v12.2.2 dated 2026-08-11.
Does PyObjC let me use macOS frameworks that have no wrapper package?
There is a wrapper package per framework, and the repository root lists them for ARKit, AVFoundation, Cocoa, CoreML, CoreData and many more, so you install the specific wrapper you need rather than the whole set.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/ronaldoussoren-pyobjc)Was this useful?