0
# Utility Tools
1
2
Key-Value Coding, testing support, signal handling, and other utility functions. These tools provide additional functionality for common PyObjC development patterns and specialized use cases.
3
4
## Capabilities
5
6
### Key-Value Coding (KVC)
7
8
Functions for working with Objective-C's Key-Value Coding system, which provides a generic way to access object properties by name.
9
10
```python { .api }
11
def getKey(object, key: str):
12
"""
13
Get a value from an object using Key-Value Coding.
14
15
Args:
16
object: The object to query
17
key (str): The key path to retrieve
18
19
Returns:
20
The value at the specified key path
21
22
Usage:
23
from PyObjCTools.KeyValueCoding import getKey
24
value = getKey(my_object, "name")
25
nested_value = getKey(my_object, "address.street")
26
"""
27
28
def setKey(object, key: str, value):
29
"""
30
Set a value on an object using Key-Value Coding.
31
32
Args:
33
object: The object to modify
34
key (str): The key path to set
35
value: The value to set
36
37
Usage:
38
from PyObjCTools.KeyValueCoding import setKey
39
setKey(my_object, "name", "John Doe")
40
setKey(my_object, "address.street", "123 Main St")
41
"""
42
43
def getKeyPath(object, keyPath: str):
44
"""
45
Get a value using a key path (synonym for getKey).
46
47
Args:
48
object: The object to query
49
keyPath (str): The key path to retrieve
50
51
Returns:
52
The value at the specified key path
53
54
Usage:
55
from PyObjCTools.KeyValueCoding import getKeyPath
56
value = getKeyPath(my_object, "user.profile.name")
57
"""
58
59
def setKeyPath(object, keyPath: str, value):
60
"""
61
Set a value using a key path (synonym for setKey).
62
63
Args:
64
object: The object to modify
65
keyPath (str): The key path to set
66
value: The value to set
67
68
Usage:
69
from PyObjCTools.KeyValueCoding import setKeyPath
70
setKeyPath(my_object, "user.profile.name", "Jane Smith")
71
"""
72
73
def arrayOfVirtualKeys():
74
"""
75
Get an array of virtual keys for KVC operations.
76
77
Returns:
78
list: Array of virtual key strings
79
"""
80
81
def dictionaryOfVirtualKeys():
82
"""
83
Get a dictionary of virtual keys for KVC operations.
84
85
Returns:
86
dict: Dictionary mapping virtual keys to their operations
87
"""
88
```
89
90
### Testing Support
91
92
Classes and functions for PyObjC-specific testing patterns and test case management.
93
94
```python { .api }
95
class TestCase:
96
"""
97
Base test case class for PyObjC tests.
98
99
Provides PyObjC-specific testing utilities and assertions
100
for testing Objective-C bridge functionality.
101
"""
102
103
def assertIsInstance(self, obj, cls):
104
"""Assert that obj is an instance of cls."""
105
106
def assertIsObjCClass(self, cls):
107
"""Assert that cls is an Objective-C class."""
108
109
def assertHasAttr(self, obj, attr):
110
"""Assert that obj has the specified attribute."""
111
112
def main():
113
"""
114
Run the PyObjC test suite.
115
116
Discovers and runs all PyObjC tests in the current module
117
or package, with appropriate setup and teardown.
118
"""
119
120
def onlyIf(condition: bool, reason: str):
121
"""
122
Decorator to conditionally run a test.
123
124
Args:
125
condition (bool): Whether the test should run
126
reason (str): Reason for skipping if condition is False
127
128
Usage:
129
@onlyIf(sys.platform == 'darwin', "macOS only")
130
def test_cocoa_feature(self):
131
pass
132
"""
133
134
def skipUnless(condition: bool, reason: str):
135
"""
136
Decorator to skip a test unless a condition is met.
137
138
Args:
139
condition (bool): Condition that must be true to run test
140
reason (str): Reason for skipping if condition is False
141
142
Usage:
143
@skipUnless(hasattr(objc, 'VERSION'), "Requires objc.VERSION")
144
def test_version_feature(self):
145
pass
146
"""
147
148
def fourcc(code: str):
149
"""
150
Convert a four-character code string to an integer.
151
152
Args:
153
code (str): Four-character code string
154
155
Returns:
156
int: Integer representation of the four-character code
157
158
Usage:
159
from PyObjCTools.TestSupport import fourcc
160
type_code = fourcc('JPEG')
161
"""
162
163
def cast_int(value):
164
"""
165
Cast a value to an integer for testing purposes.
166
167
Args:
168
value: The value to cast
169
170
Returns:
171
int: The integer representation
172
"""
173
```
174
175
### Signal Handling
176
177
Functions for handling Unix signals and Mach signals in PyObjC applications.
178
179
```python { .api }
180
def signal(signum: int, handler):
181
"""
182
Install a signal handler for Unix signals.
183
184
Args:
185
signum (int): Signal number (e.g., signal.SIGTERM)
186
handler: Signal handler function
187
188
Usage:
189
from PyObjCTools.Signals import signal
190
import signal as sig
191
192
def handle_term(signum, frame):
193
print("Received SIGTERM")
194
195
signal(sig.SIGTERM, handle_term)
196
"""
197
198
def getsignal(signum: int):
199
"""
200
Get the current signal handler for a signal.
201
202
Args:
203
signum (int): Signal number
204
205
Returns:
206
The current signal handler function
207
208
Usage:
209
from PyObjCTools.Signals import getsignal
210
handler = getsignal(signal.SIGTERM)
211
"""
212
213
def dumpStackOnFatalSignal():
214
"""
215
Install signal handlers to dump stack traces on fatal signals.
216
217
Automatically installs handlers for common fatal signals (SIGSEGV,
218
SIGBUS, SIGFPE, etc.) that will dump a Python stack trace before
219
the program terminates.
220
221
Usage:
222
from PyObjCTools.Signals import dumpStackOnFatalSignal
223
dumpStackOnFatalSignal()
224
"""
225
226
def resetFatalSignals():
227
"""
228
Reset signal handlers for fatal signals to their default behavior.
229
230
Usage:
231
from PyObjCTools.Signals import resetFatalSignals
232
resetFatalSignals()
233
"""
234
235
def handleMachSignal(exception_port):
236
"""
237
Handle Mach signals (macOS-specific).
238
239
Args:
240
exception_port: Mach exception port to monitor
241
242
Mach signals are low-level kernel signals used for exception
243
handling and debugging on macOS.
244
"""
245
```
246
247
### Mach Signal Handling (PyObjCTools.MachSignals)
248
249
Mach-specific signal handling for advanced debugging on macOS.
250
251
```python { .api }
252
def dumpStackOnFatalSignal():
253
"""
254
Mach-specific implementation of stack dumping on fatal signals.
255
256
Uses Mach exception handling for more detailed crash information
257
than standard Unix signals.
258
259
Usage:
260
from PyObjCTools.MachSignals import dumpStackOnFatalSignal
261
dumpStackOnFatalSignal()
262
"""
263
264
def resetFatalSignals():
265
"""
266
Reset Mach-specific signal handlers to default behavior.
267
268
Usage:
269
from PyObjCTools.MachSignals import resetFatalSignals
270
resetFatalSignals()
271
"""
272
```
273
274
### Context and Thread Management
275
276
Functions for managing execution context and thread locking in PyObjC applications.
277
278
```python { .api }
279
def current_bundle():
280
"""
281
Get the current NSBundle object.
282
283
Returns:
284
NSBundle: The current application bundle, or None if not available
285
286
Usage:
287
from PyObjCTools import current_bundle
288
bundle = current_bundle()
289
if bundle:
290
resources_path = bundle.resourcePath()
291
"""
292
293
def object_lock(obj):
294
"""
295
Context manager for locking individual Objective-C objects.
296
297
Args:
298
obj: The Objective-C object to lock
299
300
Usage:
301
with object_lock(my_object):
302
# Thread-safe access to my_object
303
my_object.modifyState()
304
"""
305
306
def release_lock():
307
"""
308
Context manager to release the PyObjC global lock.
309
310
Temporarily releases the global interpreter lock to allow
311
other threads to execute Python or Objective-C code.
312
313
Usage:
314
with release_lock():
315
# Other threads can run while this block executes
316
long_running_objc_operation()
317
"""
318
```
319
320
### Category and Lazy Loading Support
321
322
Classes for creating categories and implementing lazy loading patterns.
323
324
```python { .api }
325
class Category:
326
"""
327
Helper class for creating Objective-C categories.
328
329
Categories allow adding methods to existing Objective-C classes
330
without subclassing or modifying the original class definition.
331
"""
332
333
def __init__(self, baseClass):
334
"""
335
Initialize a category for the specified base class.
336
337
Args:
338
baseClass: The Objective-C class to extend
339
"""
340
341
def inject(targetClass, categoryClass):
342
"""
343
Inject category methods into a target class.
344
345
Args:
346
targetClass: The class to receive new methods
347
categoryClass: The class containing methods to inject
348
"""
349
350
class LazyList:
351
"""
352
Lazy-loaded list implementation for deferred evaluation.
353
354
Useful for creating lists of objects that are expensive to create
355
and may not all be needed immediately.
356
"""
357
358
def __init__(self, values):
359
"""
360
Initialize with a list of values or value factories.
361
362
Args:
363
values: List of values or callable factories
364
"""
365
366
class OC_PythonObject:
367
"""
368
Python object wrapper for use in Objective-C contexts.
369
370
Allows Python objects to be passed to Objective-C methods
371
that expect Objective-C objects, with automatic bridging.
372
"""
373
```
374
375
## Usage Examples
376
377
### Key-Value Coding Operations
378
379
```python
380
from PyObjCTools.KeyValueCoding import getKey, setKey
381
from Foundation import NSMutableDictionary
382
383
# Create a mutable dictionary
384
person = NSMutableDictionary.alloc().init()
385
person.setObject_forKey_("John Doe", "name")
386
person.setObject_forKey_(30, "age")
387
388
# Create nested structure
389
address = NSMutableDictionary.alloc().init()
390
address.setObject_forKey_("123 Main St", "street")
391
address.setObject_forKey_("Anytown", "city")
392
person.setObject_forKey_(address, "address")
393
394
# Use KVC to access values
395
name = getKey(person, "name")
396
print(f"Name: {name}") # "John Doe"
397
398
# Access nested values with key paths
399
street = getKey(person, "address.street")
400
print(f"Street: {street}") # "123 Main St"
401
402
# Set values using KVC
403
setKey(person, "age", 31)
404
setKey(person, "address.zip", "12345")
405
406
print(f"Updated age: {getKey(person, 'age')}") # 31
407
print(f"Zip code: {getKey(person, 'address.zip')}") # "12345"
408
```
409
410
### PyObjC Testing
411
412
```python
413
from PyObjCTools.TestSupport import TestCase, main, onlyIf, skipUnless
414
import sys
415
import objc
416
from Foundation import NSString
417
418
class MyPyObjCTests(TestCase):
419
420
def test_basic_bridge_functionality(self):
421
"""Test basic PyObjC bridge operations."""
422
# Test string creation
423
s = NSString.stringWithString_("Hello")
424
self.assertIsInstance(s, NSString)
425
self.assertEqual(s.length(), 5)
426
427
@onlyIf(sys.platform == 'darwin', "macOS only")
428
def test_macos_specific_feature(self):
429
"""Test macOS-specific functionality."""
430
# Test that would only work on macOS
431
self.assertTrue(hasattr(objc, 'lookUpClass'))
432
433
@skipUnless(hasattr(objc, 'runtime'), "Requires runtime access")
434
def test_runtime_features(self):
435
"""Test runtime introspection features."""
436
self.assertIsNotNone(objc.runtime)
437
438
if __name__ == '__main__':
439
main()
440
```
441
442
### Signal Handling in PyObjC Applications
443
444
```python
445
from PyObjCTools.Signals import signal
446
import signal as sig
447
import sys
448
449
class MyApplication:
450
def __init__(self):
451
self.should_quit = False
452
self.setup_signal_handlers()
453
454
def setup_signal_handlers(self):
455
"""Set up signal handlers for graceful shutdown."""
456
signal(sig.SIGTERM, self.handle_termination)
457
signal(sig.SIGINT, self.handle_termination)
458
459
def handle_termination(self, signum, frame):
460
"""Handle termination signals."""
461
print(f"Received signal {signum}, shutting down...")
462
self.should_quit = True
463
464
def run(self):
465
"""Main application loop."""
466
while not self.should_quit:
467
# Application logic here
468
time.sleep(0.1)
469
print("Application shutdown complete")
470
471
# Usage
472
app = MyApplication()
473
app.run()
474
```
475
476
### Thread-Safe Object Access
477
478
```python
479
from PyObjCTools import object_lock, release_lock
480
from Foundation import NSMutableArray
481
import threading
482
483
# Shared mutable array
484
shared_array = NSMutableArray.alloc().init()
485
486
def worker_thread(thread_id):
487
"""Worker thread that modifies shared array."""
488
for i in range(10):
489
with object_lock(shared_array):
490
# Thread-safe modification
491
shared_array.addObject_(f"Thread {thread_id}, item {i}")
492
493
# Release lock to allow other operations
494
with release_lock():
495
# Other threads can run while we sleep
496
time.sleep(0.01)
497
498
# Start multiple threads
499
threads = []
500
for i in range(3):
501
t = threading.Thread(target=worker_thread, args=(i,))
502
threads.append(t)
503
t.start()
504
505
# Wait for completion
506
for t in threads:
507
t.join()
508
509
print(f"Final array count: {shared_array.count()}")
510
```