Strange import behaviour with CoreFoundation

Issue #179 closed
Anonymous created an issue

Hello there. Firstly, I'd like to say a huge HUGE thank you for all your work on PyObjC. It really is an awesome project and has been really handy with automation tasks for me.

Ok, so onto the problem, this is a strange one.

Fotiss-MBP:~ fots$ ipython3
Python 3.6.0 (default, Dec 24 2016, 00:01:50) 
Type "copyright", "credits" or "license" for more information.

IPython 5.1.0 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: from CoreFoundation import SystemEventsLoginItem
---------------------------------------------------------------------------
ImportError                               Traceback (most recent call last)
<ipython-input-1-117d121c2ebb> in <module>()
----> 1 from CoreFoundation import SystemEventsLoginItem

ImportError: cannot import name 'SystemEventsLoginItem'

In [2]: import CoreFoundation

In [3]: len(dir(CoreFoundation))
Out[3]: 3614

In [4]: from ScriptingBridge import SBApplication

In [5]: system_events = SBApplication.applicationWithBundleIdentifier_('com.apple.systemevents')

In [6]: len(dir(CoreFoundation))
Out[6]: 4983

In [7]: from CoreFoundation import SystemEventsLoginItem

In [8]: SystemEventsLoginItem
Out[8]: <objective-c class SystemEventsLoginItem at 0x7f9e5deb42a0>

In [9]:                                     

As you can see, I'm unable to import SystemEventsLoginItem from CoreFoundation until I've used SBApplication. Further to this, many more importable items appear after I've used SBApplication.

In case it helps, I'm running Python 3.6.0 from Homebrew and I have installed the following PyObjC libraries straight from pip:

Fotiss-MBP:~ fots$ pip3 list --format columns | grep pyobjc
pyobjc-core                          3.2.1  
pyobjc-framework-ApplicationServices 3.2.1  
pyobjc-framework-Cocoa               3.2.1  
pyobjc-framework-CoreText            3.2.1  
pyobjc-framework-LaunchServices      3.2.1  
pyobjc-framework-Quartz              3.2.1  
pyobjc-framework-ScriptingBridge     3.2.1  

I hope I haven't missed something silly here and apologies in advance if I have.

Thanks so much for your time
Fotis

Comments (7)

  1. Ronald Oussoren repo owner

    SystemEventsLoginItem is not a class exposed by the CoreFoundation framework (and AFAIK is not a class documented by Apple at all). The behavior you're seeing is consistent with SBApplication creating or loading an Objective-C classes with that name.

    As a background: all framework wrappers export not just the symbols defined in the framework, but also all Objective-C classes. That's primarily done because tracking the framework that exports a specific Objective-C class is very expensive, an earlier version of PyObjC did contain code that only exported classes that belonged to the framework being wrapped but that code was removed because it slowed down application startup too much.

  2. Anonymous

    Thank you so much for the reply Ronald. Is there any more suitable way to invoke the loading of such extra components mate?

    In my case, I sadly do need SystemEventsLoginItem to create a new login item:

    e.g.

            # Obtain the System Events application
            system_events = SBApplication.applicationWithBundleIdentifier_('com.apple.systemevents')
    
            # Note that the import of SystemEventsLoginItem must occur after we initialise
            # system events or it simply won't work.
            # https://bitbucket.org/ronaldoussoren/pyobjc/issues/179/strange-import-behaviour-with
            from CoreFoundation import SystemEventsLoginItem
    
            # Find a specific login item
            login_items = system_events.loginItems()
    
            if state == 'present':
                # Search for the login item in the existing login items
                for login_item in login_items:
                    # The item path was found
                    if login_item.path() == path:
                        # Compare to confirm that the item has the same hidden attribute
                        if login_item.hidden() == hidden:
                            self.ok()
    
                        # Update the hidden attribute as they differ
                        login_item.setHidden_(hidden)
                        self.changed()
    
                # Create a new login item
                login_item = SystemEventsLoginItem.alloc().initWithProperties_({
                    'path': path,
                    'hidden': hidden
                })
    
                # Add the login item to the list
                login_items.addObject_(login_item)
                self.changed()
    

    Kindest Regards
    Fotis

  3. Ronald Oussoren repo owner

    I don't use the scripting bridge myself, but the "login_items" object, or the ScriptingBridge framework, should have some way to create new instances without explicitly referencing the class.

    That said, a nicer way to get the class object is:

    SystemEventsLoginItem = objc.lookUpClass("SystemEventsLoginItem")
    
  4. Anonymous

    Thank you so much for the reply and thank you SO much for your awesome library! 😄

    Unless there's anything further you wish to do, you're welcome to close this case.

    Cheers. Fotis

  5. Log in to comment