pymacos 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pymacos-1.0.0/.flake8 +12 -0
- pymacos-1.0.0/.gitignore +83 -0
- pymacos-1.0.0/.readthedocs.yaml +19 -0
- pymacos-1.0.0/CONTRIBUTING.md +49 -0
- pymacos-1.0.0/LICENSE +21 -0
- pymacos-1.0.0/Makefile +158 -0
- pymacos-1.0.0/PKG-INFO +83 -0
- pymacos-1.0.0/README.md +44 -0
- pymacos-1.0.0/docs/_static/custom.css +68 -0
- pymacos-1.0.0/docs/_templates/layout.html +60 -0
- pymacos-1.0.0/docs/api.md +85 -0
- pymacos-1.0.0/docs/appearance.md +29 -0
- pymacos-1.0.0/docs/apps.md +115 -0
- pymacos-1.0.0/docs/clipboard.md +49 -0
- pymacos-1.0.0/docs/conf.py +86 -0
- pymacos-1.0.0/docs/contributing.md +5 -0
- pymacos-1.0.0/docs/errors.md +31 -0
- pymacos-1.0.0/docs/index.md +60 -0
- pymacos-1.0.0/docs/installation.md +35 -0
- pymacos-1.0.0/docs/keychain.md +69 -0
- pymacos-1.0.0/docs/license.md +12 -0
- pymacos-1.0.0/docs/notifications.md +49 -0
- pymacos-1.0.0/docs/permissions.md +45 -0
- pymacos-1.0.0/docs/quickstart.md +67 -0
- pymacos-1.0.0/docs/requirements.txt +5 -0
- pymacos-1.0.0/docs/screenshots.md +56 -0
- pymacos-1.0.0/docs/speech.md +50 -0
- pymacos-1.0.0/docs/why.md +71 -0
- pymacos-1.0.0/macos/__init__.py +54 -0
- pymacos-1.0.0/macos/_cf.py +150 -0
- pymacos-1.0.0/macos/_objc.py +130 -0
- pymacos-1.0.0/macos/_system.py +47 -0
- pymacos-1.0.0/macos/appearance.py +47 -0
- pymacos-1.0.0/macos/apps.py +337 -0
- pymacos-1.0.0/macos/clipboard.py +71 -0
- pymacos-1.0.0/macos/errors.py +59 -0
- pymacos-1.0.0/macos/keychain.py +140 -0
- pymacos-1.0.0/macos/notifications.py +44 -0
- pymacos-1.0.0/macos/py.typed +0 -0
- pymacos-1.0.0/macos/screen.py +119 -0
- pymacos-1.0.0/macos/speech.py +86 -0
- pymacos-1.0.0/pyproject.toml +96 -0
- pymacos-1.0.0/tests/conftest.py +43 -0
- pymacos-1.0.0/tests/test_commands.py +236 -0
- pymacos-1.0.0/tests/test_live.py +160 -0
pymacos-1.0.0/.flake8
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
[flake8]
|
|
2
|
+
|
|
3
|
+
max-line-length = 130
|
|
4
|
+
# E203: whitespace before ':' (black-compatible — black puts spaces around the
|
|
5
|
+
# colon in slices like data[i : i + n], which conflicts with PEP 8).
|
|
6
|
+
# E701: multiple statements on one line (colon) — used pervasively as a style choice.
|
|
7
|
+
# W503: line break before binary operator (black-compatible).
|
|
8
|
+
ignore = E203, E701, W503
|
|
9
|
+
|
|
10
|
+
per-file-ignores =
|
|
11
|
+
# __init__.py files are allowed to have unused imports and lines-too-long.
|
|
12
|
+
*/__init__.py:F401, E501
|
pymacos-1.0.0/.gitignore
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Byte-compiled / cached
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Build / packaging
|
|
7
|
+
build/
|
|
8
|
+
.build/
|
|
9
|
+
dist/
|
|
10
|
+
*.egg-info/
|
|
11
|
+
*.egg
|
|
12
|
+
.eggs/
|
|
13
|
+
*.whl
|
|
14
|
+
*.tar.gz
|
|
15
|
+
pip-log.txt
|
|
16
|
+
pip-delete-this-directory.txt
|
|
17
|
+
MANIFEST
|
|
18
|
+
|
|
19
|
+
# Virtual environments
|
|
20
|
+
venv/
|
|
21
|
+
.venv/
|
|
22
|
+
env/
|
|
23
|
+
ENV/
|
|
24
|
+
|
|
25
|
+
# Testing & coverage
|
|
26
|
+
.pytest_cache/
|
|
27
|
+
*.pytest_cache
|
|
28
|
+
.coverage
|
|
29
|
+
.coverage.*
|
|
30
|
+
htmlcov/
|
|
31
|
+
coverage.xml
|
|
32
|
+
*.cover
|
|
33
|
+
.tox/
|
|
34
|
+
.nox/
|
|
35
|
+
.hypothesis/
|
|
36
|
+
|
|
37
|
+
# Type checkers & linters
|
|
38
|
+
.mypy_cache/
|
|
39
|
+
.ruff_cache/
|
|
40
|
+
.pyre/
|
|
41
|
+
.pytype/
|
|
42
|
+
|
|
43
|
+
# IDEs / editors
|
|
44
|
+
.idea/
|
|
45
|
+
.vscode/
|
|
46
|
+
*.code-workspace
|
|
47
|
+
*.sublime-*
|
|
48
|
+
.spyderproject
|
|
49
|
+
.spyproject
|
|
50
|
+
|
|
51
|
+
# Editor swap / backup files
|
|
52
|
+
*.swp
|
|
53
|
+
*.swo
|
|
54
|
+
*~
|
|
55
|
+
.\#*
|
|
56
|
+
\#*\#
|
|
57
|
+
|
|
58
|
+
# OS-specific cruft
|
|
59
|
+
.DS_Store
|
|
60
|
+
.AppleDouble
|
|
61
|
+
.LSOverride
|
|
62
|
+
Thumbs.db
|
|
63
|
+
Desktop.ini
|
|
64
|
+
.directory
|
|
65
|
+
|
|
66
|
+
# Toolchain / version pinning state
|
|
67
|
+
.tool-versions
|
|
68
|
+
.python-version
|
|
69
|
+
|
|
70
|
+
# Local environment files (never commit secrets)
|
|
71
|
+
.env
|
|
72
|
+
.env.local
|
|
73
|
+
.env.*.local
|
|
74
|
+
|
|
75
|
+
# Logs / temporary
|
|
76
|
+
*.log
|
|
77
|
+
*.tmp
|
|
78
|
+
|
|
79
|
+
# Sphinx / docs build output
|
|
80
|
+
docs/_build/
|
|
81
|
+
|
|
82
|
+
# Project-local
|
|
83
|
+
.claude/
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Read the Docs configuration file for Sphinx + MyST.
|
|
2
|
+
# See https://docs.readthedocs.io/en/stable/config-file/v2.html
|
|
3
|
+
|
|
4
|
+
version: 2
|
|
5
|
+
|
|
6
|
+
build:
|
|
7
|
+
os: ubuntu-22.04
|
|
8
|
+
tools:
|
|
9
|
+
python: "3.11"
|
|
10
|
+
|
|
11
|
+
sphinx:
|
|
12
|
+
configuration: docs/conf.py
|
|
13
|
+
fail_on_warning: true
|
|
14
|
+
|
|
15
|
+
python:
|
|
16
|
+
install:
|
|
17
|
+
- requirements: docs/requirements.txt
|
|
18
|
+
- method: pip
|
|
19
|
+
path: .
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Bug reports, ideas and pull requests are welcome on
|
|
4
|
+
[GitHub](https://github.com/JeanExtreme002/pymacos).
|
|
5
|
+
|
|
6
|
+
## Setup
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
git clone https://github.com/JeanExtreme002/pymacos.git
|
|
10
|
+
cd pymacos
|
|
11
|
+
python -m venv .venv && source .venv/bin/activate
|
|
12
|
+
pip install -e ".[dev]"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The `Makefile` wraps every command below: run `make help` to list the targets,
|
|
16
|
+
for example `make check` (lint, type check and tests) or `make docs`.
|
|
17
|
+
|
|
18
|
+
## Tests
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pytest # everything, including live tests on your Mac
|
|
22
|
+
pytest -m "not live" # unit tests only (these also run on Linux)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The live tests talk to the real system. They restore your clipboard and delete
|
|
26
|
+
the Keychain items they create.
|
|
27
|
+
|
|
28
|
+
## Lint and type check
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
flake8 macos tests
|
|
32
|
+
mypy macos
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Documentation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install -r docs/requirements.txt
|
|
39
|
+
python -m sphinx -W -b html docs docs/_build/html
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Then open `docs/_build/html/index.html`.
|
|
43
|
+
|
|
44
|
+
## Pull requests
|
|
45
|
+
|
|
46
|
+
- Use [Conventional Commits](https://www.conventionalcommits.org) for the title
|
|
47
|
+
(`feat: ...`, `fix: ...`, `docs: ...`).
|
|
48
|
+
- Add a test for every bug fix and new feature.
|
|
49
|
+
- Keep the package dependency-free.
|
pymacos-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jean Loui Bernard Silva de Jesus
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pymacos-1.0.0/Makefile
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Makefile for the pymacos Python package
|
|
2
|
+
|
|
3
|
+
# Variables
|
|
4
|
+
# The import name (the PyPI distribution is "pymacos").
|
|
5
|
+
PACKAGE_NAME = macos
|
|
6
|
+
PYTHON = python3
|
|
7
|
+
PIP = $(PYTHON) -m pip
|
|
8
|
+
VENV_DIR = .venv
|
|
9
|
+
TEST_DIR = tests
|
|
10
|
+
DIST_DIR = dist
|
|
11
|
+
DOCS_DIR = docs
|
|
12
|
+
DOCS_BUILD_DIR = $(DOCS_DIR)/_build/html
|
|
13
|
+
|
|
14
|
+
# Colors for output
|
|
15
|
+
GREEN = \033[0;32m
|
|
16
|
+
YELLOW = \033[0;33m
|
|
17
|
+
NC = \033[0m # No Color
|
|
18
|
+
|
|
19
|
+
# Default target
|
|
20
|
+
.PHONY: help
|
|
21
|
+
help:
|
|
22
|
+
@echo "$(GREEN)pymacos Python package Makefile$(NC)"
|
|
23
|
+
@echo ""
|
|
24
|
+
@echo "Setup:"
|
|
25
|
+
@echo " $(YELLOW)venv$(NC) - Create a virtual environment in $(VENV_DIR)"
|
|
26
|
+
@echo " $(YELLOW)install$(NC) - Install the package in development mode"
|
|
27
|
+
@echo " $(YELLOW)install-dev$(NC) - Install the package with development dependencies"
|
|
28
|
+
@echo " $(YELLOW)install-docs$(NC) - Install the dependencies to build the documentation"
|
|
29
|
+
@echo ""
|
|
30
|
+
@echo "Checks:"
|
|
31
|
+
@echo " $(YELLOW)test$(NC) - Run all tests, including live tests against this Mac"
|
|
32
|
+
@echo " $(YELLOW)test-unit$(NC) - Run only the unit tests (no live tests)"
|
|
33
|
+
@echo " $(YELLOW)test-coverage$(NC) - Run all tests with a coverage report"
|
|
34
|
+
@echo " $(YELLOW)lint$(NC) - Run the linter (flake8)"
|
|
35
|
+
@echo " $(YELLOW)type-check$(NC) - Run the type checker (mypy)"
|
|
36
|
+
@echo " $(YELLOW)check$(NC) - Run lint, type-check and test"
|
|
37
|
+
@echo ""
|
|
38
|
+
@echo "Documentation:"
|
|
39
|
+
@echo " $(YELLOW)docs$(NC) - Build the HTML documentation (output: $(DOCS_BUILD_DIR))"
|
|
40
|
+
@echo " $(YELLOW)docs-serve$(NC) - Live-reload docs server at http://127.0.0.1:8000"
|
|
41
|
+
@echo " $(YELLOW)docs-clean$(NC) - Remove the docs build directory"
|
|
42
|
+
@echo ""
|
|
43
|
+
@echo "Packaging:"
|
|
44
|
+
@echo " $(YELLOW)build$(NC) - Build the wheel and source distribution"
|
|
45
|
+
@echo " $(YELLOW)validate$(NC) - Build and check the distributions with twine"
|
|
46
|
+
@echo " $(YELLOW)version$(NC) - Show the current version"
|
|
47
|
+
@echo " $(YELLOW)clean$(NC) - Remove build, test and cache artifacts"
|
|
48
|
+
|
|
49
|
+
# Create virtual environment
|
|
50
|
+
.PHONY: venv
|
|
51
|
+
venv:
|
|
52
|
+
@echo "$(GREEN)Creating virtual environment...$(NC)"
|
|
53
|
+
$(PYTHON) -m venv $(VENV_DIR)
|
|
54
|
+
@echo "$(YELLOW)To activate: source $(VENV_DIR)/bin/activate$(NC)"
|
|
55
|
+
|
|
56
|
+
# Install package in development mode (it has no runtime dependencies)
|
|
57
|
+
.PHONY: install
|
|
58
|
+
install:
|
|
59
|
+
@echo "$(GREEN)Installing package in development mode...$(NC)"
|
|
60
|
+
$(PIP) install -e .
|
|
61
|
+
|
|
62
|
+
# Install development dependencies (pytest, flake8, mypy, build, twine)
|
|
63
|
+
.PHONY: install-dev
|
|
64
|
+
install-dev:
|
|
65
|
+
@echo "$(GREEN)Installing development dependencies...$(NC)"
|
|
66
|
+
$(PIP) install -e ".[dev]"
|
|
67
|
+
|
|
68
|
+
# Install the documentation dependencies, plus the package so autodoc can import it
|
|
69
|
+
.PHONY: install-docs
|
|
70
|
+
install-docs:
|
|
71
|
+
@echo "$(GREEN)Installing documentation dependencies...$(NC)"
|
|
72
|
+
$(PIP) install -r $(DOCS_DIR)/requirements.txt
|
|
73
|
+
$(PIP) install -e .
|
|
74
|
+
|
|
75
|
+
# Run all tests. The live ones talk to the real system: they restore the
|
|
76
|
+
# clipboard and delete the Keychain items they create.
|
|
77
|
+
.PHONY: test
|
|
78
|
+
test:
|
|
79
|
+
@echo "$(GREEN)Running tests...$(NC)"
|
|
80
|
+
$(PYTHON) -m pytest $(TEST_DIR) -v
|
|
81
|
+
|
|
82
|
+
# Run only the unit tests, which don't touch the system (they also run on Linux)
|
|
83
|
+
.PHONY: test-unit
|
|
84
|
+
test-unit:
|
|
85
|
+
@echo "$(GREEN)Running unit tests...$(NC)"
|
|
86
|
+
$(PYTHON) -m pytest $(TEST_DIR) -v -m "not live"
|
|
87
|
+
|
|
88
|
+
# Run tests with coverage
|
|
89
|
+
.PHONY: test-coverage
|
|
90
|
+
test-coverage:
|
|
91
|
+
@echo "$(GREEN)Running tests with coverage...$(NC)"
|
|
92
|
+
$(PYTHON) -m pytest $(TEST_DIR) --cov=$(PACKAGE_NAME) --cov-report=html --cov-report=term
|
|
93
|
+
@echo "$(YELLOW)HTML report available at htmlcov/index.html$(NC)"
|
|
94
|
+
|
|
95
|
+
# Run linter
|
|
96
|
+
.PHONY: lint
|
|
97
|
+
lint:
|
|
98
|
+
@echo "$(GREEN)Running linter (flake8)...$(NC)"
|
|
99
|
+
$(PYTHON) -m flake8 $(PACKAGE_NAME) $(TEST_DIR)
|
|
100
|
+
|
|
101
|
+
# Run type checker (config in pyproject.toml)
|
|
102
|
+
.PHONY: type-check
|
|
103
|
+
type-check:
|
|
104
|
+
@echo "$(GREEN)Running type checker (mypy)...$(NC)"
|
|
105
|
+
$(PYTHON) -m mypy $(PACKAGE_NAME)
|
|
106
|
+
|
|
107
|
+
# Everything CI runs on a pull request, except the docs build
|
|
108
|
+
.PHONY: check
|
|
109
|
+
check: lint type-check test
|
|
110
|
+
@echo "$(GREEN)All checks passed!$(NC)"
|
|
111
|
+
|
|
112
|
+
# Build the HTML documentation with the same flags as CI and Read the Docs:
|
|
113
|
+
# warnings, including broken cross-references (-n), fail the build.
|
|
114
|
+
.PHONY: docs
|
|
115
|
+
docs:
|
|
116
|
+
@echo "$(GREEN)Building HTML documentation...$(NC)"
|
|
117
|
+
$(PYTHON) -m sphinx -n -W --keep-going -b html $(DOCS_DIR) $(DOCS_BUILD_DIR)
|
|
118
|
+
@echo "$(GREEN)Documentation generated at $(DOCS_BUILD_DIR)/index.html$(NC)"
|
|
119
|
+
|
|
120
|
+
# Live-reload docs server. sphinx-autobuild is installed on demand. After
|
|
121
|
+
# changing a toctree, restart it: it only rebuilds changed pages, so the
|
|
122
|
+
# sidebar on the other pages goes stale.
|
|
123
|
+
.PHONY: docs-serve
|
|
124
|
+
docs-serve:
|
|
125
|
+
@echo "$(GREEN)Starting live-reload docs server at http://127.0.0.1:8000$(NC)"
|
|
126
|
+
@$(PYTHON) -c "import sphinx_autobuild" 2>/dev/null || $(PIP) install sphinx-autobuild
|
|
127
|
+
$(PYTHON) -m sphinx_autobuild $(DOCS_DIR) $(DOCS_BUILD_DIR) --open-browser
|
|
128
|
+
|
|
129
|
+
# Wipe the built docs
|
|
130
|
+
.PHONY: docs-clean
|
|
131
|
+
docs-clean:
|
|
132
|
+
@echo "$(GREEN)Cleaning docs build directory...$(NC)"
|
|
133
|
+
rm -rf $(DOCS_DIR)/_build
|
|
134
|
+
|
|
135
|
+
# Build package. Releases are published by the GitHub workflow when a
|
|
136
|
+
# release is created, so there is no publish target here.
|
|
137
|
+
.PHONY: build
|
|
138
|
+
build: clean
|
|
139
|
+
@echo "$(GREEN)Building package...$(NC)"
|
|
140
|
+
$(PYTHON) -m build
|
|
141
|
+
|
|
142
|
+
# Validate package
|
|
143
|
+
.PHONY: validate
|
|
144
|
+
validate: build
|
|
145
|
+
@echo "$(GREEN)Validating package...$(NC)"
|
|
146
|
+
$(PYTHON) -m twine check $(DIST_DIR)/*
|
|
147
|
+
|
|
148
|
+
# Show current version
|
|
149
|
+
.PHONY: version
|
|
150
|
+
version:
|
|
151
|
+
@$(PYTHON) -c "import $(PACKAGE_NAME); print($(PACKAGE_NAME).__version__)"
|
|
152
|
+
|
|
153
|
+
# Clean build artifacts
|
|
154
|
+
.PHONY: clean
|
|
155
|
+
clean:
|
|
156
|
+
@echo "$(GREEN)Cleaning build artifacts...$(NC)"
|
|
157
|
+
rm -rf build $(DIST_DIR) *.egg-info .pytest_cache .mypy_cache htmlcov .coverage coverage.xml
|
|
158
|
+
find . -path ./$(VENV_DIR) -prune -o -type d -name "__pycache__" -exec rm -rf {} +
|
pymacos-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pymacos
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A Pythonic interface to macOS — notifications, clipboard, dark mode, apps, Keychain, speech and screenshots in one dependency-free import.
|
|
5
|
+
Project-URL: Homepage, https://github.com/JeanExtreme002/pymacos
|
|
6
|
+
Project-URL: Documentation, https://macos.readthedocs.io
|
|
7
|
+
Project-URL: Repository, https://github.com/JeanExtreme002/pymacos
|
|
8
|
+
Project-URL: Issues, https://github.com/JeanExtreme002/pymacos/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/JeanExtreme002/pymacos/releases
|
|
10
|
+
Project-URL: Funding, https://github.com/sponsors/JeanExtreme002
|
|
11
|
+
Author-email: Jean Loui Bernard Silva de Jesus <contact@jeanloui.dev>
|
|
12
|
+
Maintainer-email: Jean Loui Bernard Silva de Jesus <contact@jeanloui.dev>
|
|
13
|
+
License-Expression: MIT
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Keywords: appkit,automation,clipboard,cocoa,ctypes,dark-mode,keychain,mac,macos,notifications,osx,screenshot,text-to-speech
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Topic :: Desktop Environment
|
|
26
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
27
|
+
Classifier: Topic :: System
|
|
28
|
+
Classifier: Topic :: Utilities
|
|
29
|
+
Classifier: Typing :: Typed
|
|
30
|
+
Requires-Python: >=3.9
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: build; extra == 'dev'
|
|
33
|
+
Requires-Dist: flake8; extra == 'dev'
|
|
34
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
36
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
37
|
+
Requires-Dist: twine; extra == 'dev'
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
|
|
40
|
+
# pymacos
|
|
41
|
+
|
|
42
|
+
**A Pythonic interface to macOS.** Notifications, clipboard, dark mode, apps, Keychain, speech and screenshots, all from one import with zero dependencies.
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
import macos
|
|
46
|
+
|
|
47
|
+
macos.notify("Build finished", title="CI")
|
|
48
|
+
macos.clipboard.copy("hello")
|
|
49
|
+
macos.appearance.is_dark() # True
|
|
50
|
+
macos.apps.open("Safari") # App(name='Safari', ...)
|
|
51
|
+
macos.keychain.get("my-app", "alice") # 's3cret'
|
|
52
|
+
macos.say("Done!")
|
|
53
|
+
macos.screenshot("screen.png")
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Install
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install pymacos
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The package is installed as `pymacos` and imported as `macos`.
|
|
63
|
+
|
|
64
|
+
Requires macOS and Python 3.9+.
|
|
65
|
+
|
|
66
|
+
## Why
|
|
67
|
+
|
|
68
|
+
Doing any of this from Python usually means shelling out to `osascript`, remembering `defaults` keys, or pulling in PyObjC and learning Cocoa. `pymacos` gives you one small, typed API instead:
|
|
69
|
+
|
|
70
|
+
- **No dependencies.** Native features call the system frameworks through `ctypes`; the rest wraps tools that ship with every Mac.
|
|
71
|
+
- **Pythonic.** Plain functions, dataclasses and real exceptions, not Objective-C selectors or exit codes.
|
|
72
|
+
- **Safe by default.** Your text is never spliced into shell or AppleScript source, and passwords never show up in the process list.
|
|
73
|
+
- **Helpful errors.** A missing privacy permission raises an error that says where to enable it, instead of failing silently.
|
|
74
|
+
|
|
75
|
+
## Documentation
|
|
76
|
+
|
|
77
|
+
The full guide and API reference are at **[macos.readthedocs.io](https://macos.readthedocs.io)**.
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
Released under the [MIT License](https://github.com/JeanExtreme002/pymacos/blob/main/LICENSE) — free for personal and commercial use.
|
|
82
|
+
|
|
83
|
+
<sub>Not affiliated with or endorsed by Apple Inc. macOS is a trademark of Apple Inc.</sub>
|
pymacos-1.0.0/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# pymacos
|
|
2
|
+
|
|
3
|
+
**A Pythonic interface to macOS.** Notifications, clipboard, dark mode, apps, Keychain, speech and screenshots, all from one import with zero dependencies.
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
import macos
|
|
7
|
+
|
|
8
|
+
macos.notify("Build finished", title="CI")
|
|
9
|
+
macos.clipboard.copy("hello")
|
|
10
|
+
macos.appearance.is_dark() # True
|
|
11
|
+
macos.apps.open("Safari") # App(name='Safari', ...)
|
|
12
|
+
macos.keychain.get("my-app", "alice") # 's3cret'
|
|
13
|
+
macos.say("Done!")
|
|
14
|
+
macos.screenshot("screen.png")
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install pymacos
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The package is installed as `pymacos` and imported as `macos`.
|
|
24
|
+
|
|
25
|
+
Requires macOS and Python 3.9+.
|
|
26
|
+
|
|
27
|
+
## Why
|
|
28
|
+
|
|
29
|
+
Doing any of this from Python usually means shelling out to `osascript`, remembering `defaults` keys, or pulling in PyObjC and learning Cocoa. `pymacos` gives you one small, typed API instead:
|
|
30
|
+
|
|
31
|
+
- **No dependencies.** Native features call the system frameworks through `ctypes`; the rest wraps tools that ship with every Mac.
|
|
32
|
+
- **Pythonic.** Plain functions, dataclasses and real exceptions, not Objective-C selectors or exit codes.
|
|
33
|
+
- **Safe by default.** Your text is never spliced into shell or AppleScript source, and passwords never show up in the process list.
|
|
34
|
+
- **Helpful errors.** A missing privacy permission raises an error that says where to enable it, instead of failing silently.
|
|
35
|
+
|
|
36
|
+
## Documentation
|
|
37
|
+
|
|
38
|
+
The full guide and API reference are at **[macos.readthedocs.io](https://macos.readthedocs.io)**.
|
|
39
|
+
|
|
40
|
+
## License
|
|
41
|
+
|
|
42
|
+
Released under the [MIT License](https://github.com/JeanExtreme002/pymacos/blob/main/LICENSE) — free for personal and commercial use.
|
|
43
|
+
|
|
44
|
+
<sub>Not affiliated with or endorsed by Apple Inc. macOS is a trademark of Apple Inc.</sub>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/* pymacos docs: tweaks on top of the Read the Docs theme. */
|
|
2
|
+
|
|
3
|
+
/* --- Repository link (injected in _templates/layout.html) --------------- */
|
|
4
|
+
/* A quiet card at the top of the sidebar: GitHub icon, repo name and live
|
|
5
|
+
star/fork counts. Scoped under `.wy-menu-vertical a` so it beats the RTD
|
|
6
|
+
theme's own `.wy-menu-vertical a { display: block; ... }` rule. */
|
|
7
|
+
.wy-menu-vertical a.repo-link {
|
|
8
|
+
display: flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
gap: 0.65rem;
|
|
11
|
+
margin: 0.6rem 0.8rem 0.4rem;
|
|
12
|
+
padding: 0.55rem 0.7rem;
|
|
13
|
+
border-radius: 0.35rem;
|
|
14
|
+
color: #d9d9d9;
|
|
15
|
+
line-height: 1.25;
|
|
16
|
+
text-decoration: none;
|
|
17
|
+
transition: background 0.15s ease, color 0.15s ease;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.wy-menu-vertical a.repo-link:hover,
|
|
21
|
+
.wy-menu-vertical a.repo-link:focus-visible {
|
|
22
|
+
background: rgba(255, 255, 255, 0.08);
|
|
23
|
+
color: #fff;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.repo-link__icon {
|
|
27
|
+
flex-shrink: 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.repo-link__text {
|
|
31
|
+
display: flex;
|
|
32
|
+
flex-direction: column;
|
|
33
|
+
min-width: 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.repo-link__name {
|
|
37
|
+
overflow: hidden;
|
|
38
|
+
font-size: 0.8rem;
|
|
39
|
+
font-weight: 600;
|
|
40
|
+
text-overflow: ellipsis;
|
|
41
|
+
white-space: nowrap;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.repo-link__stats {
|
|
45
|
+
display: flex;
|
|
46
|
+
gap: 0.75rem;
|
|
47
|
+
margin-top: 0.15rem;
|
|
48
|
+
font-size: 0.72rem;
|
|
49
|
+
opacity: 0.75;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.repo-link__stats[hidden] {
|
|
53
|
+
display: none;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.repo-link__stat {
|
|
57
|
+
display: inline-flex;
|
|
58
|
+
align-items: center;
|
|
59
|
+
gap: 0.25rem;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* The RTD theme's JavaScript injects a `<button class="toctree-expand">` as the
|
|
63
|
+
first child of every link inside `.wy-menu-vertical` (the expand/collapse
|
|
64
|
+
caret for nav items with children). The repo link isn't a nav item, so that
|
|
65
|
+
button would render as a stray white pill: hide it. */
|
|
66
|
+
.repo-link button.toctree-expand {
|
|
67
|
+
display: none;
|
|
68
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{#- Extend the Read the Docs theme with a link to the GitHub repository at the
|
|
2
|
+
top of the sidebar, on every page: the repo name plus its live star and fork
|
|
3
|
+
counts, instead of a "please star" button. The counts come from the public
|
|
4
|
+
GitHub API; if that fails (offline, rate limit) only the name is shown. -#}
|
|
5
|
+
{% extends "!layout.html" %}
|
|
6
|
+
|
|
7
|
+
{% block menu %}
|
|
8
|
+
<a class="repo-link"
|
|
9
|
+
href="https://github.com/{{ github_user }}/{{ github_repo }}"
|
|
10
|
+
target="_blank" rel="noopener noreferrer"
|
|
11
|
+
title="Source code on GitHub">
|
|
12
|
+
<svg class="repo-link__icon" aria-hidden="true" viewBox="0 0 16 16" width="22" height="22">
|
|
13
|
+
<path fill="currentColor" fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
|
|
14
|
+
</svg>
|
|
15
|
+
<span class="repo-link__text">
|
|
16
|
+
<span class="repo-link__name">{{ github_user }}/{{ github_repo }}</span>
|
|
17
|
+
<span class="repo-link__stats" hidden>
|
|
18
|
+
<span class="repo-link__stat" title="Stars">
|
|
19
|
+
<svg aria-hidden="true" viewBox="0 0 16 16" width="12" height="12"><path fill="currentColor" d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Z"></path></svg>
|
|
20
|
+
<span data-repo-stars></span>
|
|
21
|
+
</span>
|
|
22
|
+
<span class="repo-link__stat" title="Forks">
|
|
23
|
+
<svg aria-hidden="true" viewBox="0 0 16 16" width="12" height="12"><path fill="currentColor" d="M5 5.372v.878c0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75v-.878a2.25 2.25 0 1 1 1.5 0v.878a2.25 2.25 0 0 1-2.25 2.25h-1.5v2.128a2.251 2.251 0 1 1-1.5 0V8.5h-1.5A2.25 2.25 0 0 1 3.5 6.25v-.878a2.25 2.25 0 1 1 1.5 0ZM5 3.25a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0Zm6.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Zm-3 8.75a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0Z"></path></svg>
|
|
24
|
+
<span data-repo-forks></span>
|
|
25
|
+
</span>
|
|
26
|
+
</span>
|
|
27
|
+
</span>
|
|
28
|
+
</a>
|
|
29
|
+
<script>
|
|
30
|
+
(function () {
|
|
31
|
+
var repo = "{{ github_user }}/{{ github_repo }}";
|
|
32
|
+
var key = "repo-stats:" + repo;
|
|
33
|
+
|
|
34
|
+
function show(data) {
|
|
35
|
+
var stats = document.querySelector(".repo-link__stats");
|
|
36
|
+
if (!stats || typeof data.stars !== "number") return;
|
|
37
|
+
stats.querySelector("[data-repo-stars]").textContent = data.stars.toLocaleString();
|
|
38
|
+
stats.querySelector("[data-repo-forks]").textContent = data.forks.toLocaleString();
|
|
39
|
+
stats.hidden = false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Cache per browser session: GitHub allows 60 unauthenticated requests
|
|
43
|
+
// an hour per IP, and every page view would otherwise spend one.
|
|
44
|
+
try {
|
|
45
|
+
var cached = sessionStorage.getItem(key);
|
|
46
|
+
if (cached) { show(JSON.parse(cached)); return; }
|
|
47
|
+
} catch (e) {}
|
|
48
|
+
|
|
49
|
+
fetch("https://api.github.com/repos/" + repo)
|
|
50
|
+
.then(function (response) { return response.ok ? response.json() : Promise.reject(); })
|
|
51
|
+
.then(function (json) {
|
|
52
|
+
var data = { stars: json.stargazers_count, forks: json.forks_count };
|
|
53
|
+
try { sessionStorage.setItem(key, JSON.stringify(data)); } catch (e) {}
|
|
54
|
+
show(data);
|
|
55
|
+
})
|
|
56
|
+
.catch(function () {});
|
|
57
|
+
})();
|
|
58
|
+
</script>
|
|
59
|
+
{{ super() }}
|
|
60
|
+
{% endblock %}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# API
|
|
2
|
+
|
|
3
|
+
The complete public API, generated from the source code. The feature pages
|
|
4
|
+
explain how to use each part.
|
|
5
|
+
|
|
6
|
+
## Top-level functions
|
|
7
|
+
|
|
8
|
+
```{eval-rst}
|
|
9
|
+
.. autofunction:: macos.notify
|
|
10
|
+
.. autofunction:: macos.say
|
|
11
|
+
.. autofunction:: macos.screenshot
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## macos.clipboard
|
|
15
|
+
|
|
16
|
+
```{eval-rst}
|
|
17
|
+
.. module:: macos.clipboard
|
|
18
|
+
|
|
19
|
+
.. autofunction:: macos.clipboard.copy
|
|
20
|
+
.. autofunction:: macos.clipboard.paste
|
|
21
|
+
.. autofunction:: macos.clipboard.clear
|
|
22
|
+
.. autofunction:: macos.clipboard.change_count
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## macos.appearance
|
|
26
|
+
|
|
27
|
+
```{eval-rst}
|
|
28
|
+
.. module:: macos.appearance
|
|
29
|
+
|
|
30
|
+
.. autofunction:: macos.appearance.is_dark
|
|
31
|
+
.. autofunction:: macos.appearance.mode
|
|
32
|
+
.. autofunction:: macos.appearance.is_auto
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## macos.apps
|
|
36
|
+
|
|
37
|
+
```{eval-rst}
|
|
38
|
+
.. module:: macos.apps
|
|
39
|
+
|
|
40
|
+
.. autofunction:: macos.apps.running
|
|
41
|
+
.. autofunction:: macos.apps.frontmost
|
|
42
|
+
.. autofunction:: macos.apps.get
|
|
43
|
+
.. autofunction:: macos.apps.open
|
|
44
|
+
.. autoclass:: macos.apps.App
|
|
45
|
+
:members: is_running, is_active, is_hidden, activate, hide, unhide, quit
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## macos.keychain
|
|
49
|
+
|
|
50
|
+
```{eval-rst}
|
|
51
|
+
.. module:: macos.keychain
|
|
52
|
+
|
|
53
|
+
.. autofunction:: macos.keychain.set
|
|
54
|
+
.. autofunction:: macos.keychain.get
|
|
55
|
+
.. autofunction:: macos.keychain.delete
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## macos.speech
|
|
59
|
+
|
|
60
|
+
```{eval-rst}
|
|
61
|
+
.. module:: macos.speech
|
|
62
|
+
|
|
63
|
+
.. autofunction:: macos.speech.voices
|
|
64
|
+
.. autoclass:: macos.speech.Voice
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## macos.screen
|
|
68
|
+
|
|
69
|
+
```{eval-rst}
|
|
70
|
+
.. module:: macos.screen
|
|
71
|
+
|
|
72
|
+
.. autofunction:: macos.screen.has_permission
|
|
73
|
+
.. autofunction:: macos.screen.request_permission
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Exceptions
|
|
77
|
+
|
|
78
|
+
```{eval-rst}
|
|
79
|
+
.. autoexception:: macos.MacOSError
|
|
80
|
+
.. autoexception:: macos.NotSupportedError
|
|
81
|
+
.. autoexception:: macos.PermissionDeniedError
|
|
82
|
+
.. autoexception:: macos.AppNotFoundError
|
|
83
|
+
.. autoexception:: macos.KeychainError
|
|
84
|
+
.. autoexception:: macos.CommandError
|
|
85
|
+
```
|