Forum Archive

Pythonista 1.6 Beta #160020

omz

Build #160020 is now available via TestFlight. Please use this thread for feedback.

I've just added a couple of new people to the TestFlight list – if you didn't get an invite, but would like to participate, please fill out this form: → Beta Signup.

Moe

Is there some sort of list which classes we can use? I tried to use example = ObjCClass('SCNScene') , but it tells me it cant find the class, so I assume the app has to specify in before what it wants to use and does not import Scenekit.

JonB

question on latest beta... does the fix of ctypes.FUNCTYPE mean we can now pass callback functions to objc classes?

omz

@Moe You can use classes from all frameworks that Pythonista links against (which doesn't include SceneKit at the moment), I don't have a full list of that handy right now, I'm afraid.

@JonB Yes, this should be fixed now. I'll probably post an example here soon (it's not completely trivial to do this correctly).

JonB

I see the option to not clear globals when running a script has gone away.... is this a change for good? i for one really liked having that feature off ... made hacking away at code much easier, since you could run the main script, then run some additional aux commands.

also, the problem with the lack of this option is that it breaks things like Stash, or really i think any ui.View that is presented in a panel, or which use threads inside a view. when the globals get cleared, the code still runs, because the view is in a panel, but the references are now junk.

if this is expected to be the new norm, we will have to investigate alternatives with stash (perhaps detecting this condition, and reexporting the needed globals... not sure if that is even possible.

JonB

by the way, i love the new ObjC utils! one question, would it be possible to add some of the introspection features from

https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ObjCRuntimeRef/index.html

such as class_copyMethodList, etc, so that the list of available selectors is visible, perhaps when doing a dir(object)? as far as i can tell, ctypes wont let us call runtime methods directly, will it?

edit: looking at the old ctypes examples, i think i see how this is done...will try later

JonB

one more... selectors which return structures (such as .frame() on a UIView ) crash. do i need to use the older ctypes mechanisms where return types are specified?

momorprods

need need ! :-)

Is there any expected official release date ?
Oh also, is there an associated Xcode template yet ?

omz

@JonB

one question, would it be possible to add some of the introspection features from
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ObjCRuntimeRef/index.html
such as class_copyMethodList, etc, so that the list of available selectors is visible, perhaps when doing a dir(object)? as far as i can tell, ctypes wont let us call runtime methods directly, will it?

Interesting idea... I think it might make sense to implement __dir__ for ObjCInstance and return the list of available methods from there... I've played around with class_copyMethodList a little bit, and came up with this:

from objc_util import *
import ctypes

def get_methods(objc_class):
    free = c.free
    free.argtypes = [c_void_p]
    free.restype = None
    class_copyMethodList = c.class_copyMethodList
    class_copyMethodList.restype = ctypes.POINTER(c_void_p)
    class_copyMethodList.argtypes = [c_void_p, ctypes.POINTER(ctypes.c_uint)]
    method_getName = c.method_getName
    method_getName.restype = c_void_p
    method_getName.argtypes = [c_void_p]
    py_methods = []
    num_methods = c_uint(0)
    method_list_ptr = class_copyMethodList(objc_class.ptr, ctypes.byref(num_methods))
    for i in xrange(num_methods.value):
        selector = method_getName(method_list_ptr[i])
        sel_name = sel_getName(selector)
        py_method_name = sel_name.replace(':', '_')
        py_methods.append(py_method_name)
    free(method_list_ptr)
    return py_methods

UIDevice = ObjCClass('UIDevice')
print get_methods(UIDevice)

It doesn't return methods from superclasses yet, but that shouldn't be too hard to do... The results also highlight a problem that I didn't think about before: It's currently not possible to call private APIs because all underscores in method names are replaced with colons internally, and the selector names of private methods usually begin with an underscore...

omz

@JonB

one more... selectors which return structures (such as .frame() on a UIView ) crash. do i need to use the older ctypes mechanisms where return types are specified?

Do you have an example of this? I just tried the following code, and it seemed to work fine:

from objc_util import *

UIView = ObjCClass('UIView')
v = UIView.alloc().initWithFrame_(CGRect(CGPoint(0, 0), CGSize(100, 100)))
print v.bounds() # => <objc_util.CGRect object at 0x10b7a59c8>
print v.bounds().size.width # => 100.0
omz

@JonB

as far as i can tell, ctypes wont let us call runtime methods directly, will it?

You sure can! If you're curious about how objc_util works internally, just turn on "Show Standard Library" in the settings, and open objc_util.py (in Standard Library/site-packages) – in fact, my favorite thing about objc_util is that I was able to write this directly on my iPad, within Pythonista (it would have been possible with the previous beta as well).

omz

I see the option to not clear globals when running a script has gone away.... is this a change for good? i for one really liked having that feature off ... made hacking away at code much easier, since you could run the main script, then run some additional aux commands.

Not sure yet, I might bring it back, though I think it's generally not a good idea to rely on it being enabled. For things like panel views, I would recommend that you import the modules you need directly within the (action) methods where you need them. The overhead should be minimal as the imported modules are cached anyway.

omz

Here's an example of using objc_util to query your Apple Music database. This script just prints the 25 artists you have the most songs of, but you could obviously extend this to get all sorts of statistics about your music... (yes, this does work with the new iCloud Music Library).

from objc_util import *
from collections import Counter

MPMediaQuery = ObjCClass('MPMediaQuery')

query = MPMediaQuery.songsQuery()
items = query.items()
print '%i songs found, collecting artist info...' % len(items)
song_counter = Counter()
for item in items:
    artist = str(item.valueForProperty_(ns('artist')))
    song_counter[artist] += 1

print '\n'.join('%i. %s: %i songs' % (i+1, x[0], x[1]) for i, x in enumerate(song_counter.most_common(25)))
dgelessus

@omz

Not sure yet, I might bring it back

Please do. Before the new beta I had an _init.py script that I would run every time I started Pythonista, which loaded various things into the global namespace that I would use a lot interactively. Those were mostly modules like sys, os and math, but I also had a nifty for loop that made every built-in Python class available as a global. Admittedly that isn't too useful, but it's nice to be able to write instancemethod instead of type(someobj.somemethod).

(_init.py was also a nice test subject for spontaneous file clearing, which by the way has not happened anymore in the recent betas, even with lots of open tabs.)

JonB

Do you have an example of this? I just tried the following code, and it seemed to work fine:

that example crashes my ipad 3, running 8.3! do you want me to send the crash report?

userista

Bug: had the ui editor opened and I put Pythonista in the background. Now when I try to open it it just crashes - doesn't open.

polymerchm

Seem the ui.load_view does not like my fairly complicated pyui file. Worked fine but now:

Traceback (most recent call last):
  File "/var/mobile/Containers/Data/Application/9EC48B68-E4CB-454A-B52E-3E92C934916F/Documents/chordcalc/chordCalc/chordcalc.py", line 3314, in <module>
    mainView = ui.load_view('chordcalc')
  File "/private/var/mobile/Containers/Bundle/Application/743EA4AB-23E2-4CB0-BE78-8FAE0E9A2A89/Pythonista.app/pylib/site-packages/ui.py", line 373, in load_view
    return load_view_str(json_str, bindings, stackframe)
  File "/private/var/mobile/Containers/Bundle/Application/743EA4AB-23E2-4CB0-BE78-8FAE0E9A2A89/Pythonista.app/pylib/site-packages/ui.py", line 359, in load_view_str
    return _view_from_dict(root_view_dict, g, l)
  File "/private/var/mobile/Containers/Bundle/Application/743EA4AB-23E2-4CB0-BE78-8FAE0E9A2A89/Pythonista.app/pylib/site-packages/ui.py", line 342, in _view_from_dict
    subview = _view_from_dict(d, f_globals, f_locals)
  File "/private/var/mobile/Containers/Bundle/Application/743EA4AB-23E2-4CB0-BE78-8FAE0E9A2A89/Pythonista.app/pylib/site-packages/ui.py", line 342, in _view_from_dict
    subview = _view_from_dict(d, f_globals, f_locals)
  File "/private/var/mobile/Containers/Bundle/Application/743EA4AB-23E2-4CB0-BE78-8FAE0E9A2A89/Pythonista.app/pylib/site-packages/ui.py", line 258, in _view_from_dict
    v.text = attrs.get('text')
TypeError: Expected a string

Help please.

omz

@JonB

Interesting, I guess I haven't really tested this on a 32 bit device. Looks like I have to use objc_msgSend_stret there (which doesn't even exist in the 64-bit runtime for some reason).

@polymerchm

Could you send me the pyui file somehow? It looks like an easy fix, but in the meantime you can use:

import _ui
_ui._load_view(...)

to get the old implementation. This will go away, but I left it in there for now, in case there are problems with the new one.

@hyshai

Could you send me the pyui file you had open (that probably caused the crash)? You can transfer it to your Mac/PC via iTunes file sharing. Let me know if you need more details.

userista

@omz email sent

polymerchm

@omz email sent. Works fine with legacy _ui._load_view.

dgelessus

This isn't specific to the new beta build, but zip archives with multiple files at root level extract into their parent directory, not a new subfolder like most other unzipping programs do. With only a single file at root level that's not a problem, but I had a zip file with a lot of files at root level, which are now all in the main Script Library folder.

JonB

Interesting, I guess I haven't really tested this on a 32 bit device. Looks like I have to use objc_msgSend_stret there (which doesn't even exist in the 64-bit runtime for some reason).

that worked. i updated __call__ to check for Structure restype, and called objc_msgSend_stret. i suspect one may actually need to check the size of the struct, but this seems to work for most structs ive encountered.

dgelessus

No idea if this would be of any use, but have you tried to use cffi with the CTypesBackend? Might or might not be a more convenient interface than raw ctypes.

JonB

back on clearing globals, thought id share the workaround for stash:

create launch_stash.py which imports, rather than runs, stash, then create a new instance.
since the module never gets cleared, it survives global clearing.

import stash
_stash = stash.StaSh()
_stash.run()
stash._stash=_stash
JonB

also, some may find this useful as an action menu script. instead pressing Play to run a script, run this from the action menu, which executes the current script without clearing globals. thats at least a workaround if the option to not clear globals doesnt come back.

# execute script in editor, in current interpreter session without clearing globals
import editor
execfile(editor.get_path())
Hackingroelz

New to the beta. Firstly, everything seems to run very smoothly on my iPad. However at some point the app stopped working completely: I couldn't open it anymore, I tried restarting my iPad but that didn't work. I had to reinstall it to use it again. It might've been caused by something in iOS 9 (beta 3) though. Also, I noticed that the MIDIPlayer doesn't seem to work properly in a UI action (even with ui.in_background, it's immediately stopped when the action function ends), although it can be fixed with an empty while loop looping until the end of the MIDI file is reached. Lastly, while it's not really a bug, it seems I have to open a new tab, tap Documentation and close the tab to open the documentation, which is a little bit unintuitive.

Anyway, nice work on the update, the tabbed editor and appex module are really useful!

ccc

When Pythonista won't launch, you can always try and launching the pythonista:// URL in Safari.

Phuket2

Lol, ccc. I was just about to write the same thing after my screw up yesterday ;)

polymerchm

@omz sorry Ollie but newest load_ view does to handle custom view. My fretboard view does not execute did_load(). Fine with legacy version.

momorprods

Got it and tested it, it's very promising - I really like the enhanced screen resolution and the new features so far.

Well now I have 2 problems:

  • I didn't manage to run my current Scene() totally fullscreen, there is still a close button & white bar displayed. BTW auto-rotation with device seems disabled (my app is running in landscape mode)
  • The SceneView() addition, in my UI-based app, crashes Pythonista upon running.
JonB

Hackingroelz, you probably need the MIDIPlayer to be a global, or at least an instance variable -- otherwise i think it goes out of scope and stops when the function ends. you just need to hang onto a reference. i dont remember if SoundPlayer had that problem.

JonB

Few more comments on latest beta.

1) NSDictionary get/set using python dictionary language doesnt seem to work. at least for dicts found in the wild, the isKindClass seems to not be returning true. i wonder if it would be better to look for whether the object responds to setValue_forKey_, or valueForKey_ etc, rather than checking class? similarly for iterables.

2) with the tabbed editor, the editor class needs updates to access tabs, or should have a reload command which reloads all tabs. when changing a file on disk, it was a good idea to check if it was thr current file in the editor, and reload, but this cant happen in current version, since reopening a file programatically causes a new tab to open, and the get_path only returns current tab.

3) the editor should probably disallow opening two copies of the same file, unless one is set to read only. this can cause confusion, where one copy is edited, then switching to the other copy effectively undoes the changes! maybe just bring existing file tab to the front if opening an already open file? alternatively, editor should detect if file on disk changed, and reload (or prompt for reload) as is done in many text editors. ... there are use cases for having two tabs open on the same file, say for editing very large files, but keeping edits in sync is necessary.

omz

@JonB

1) NSDictionary get/set using python dictionary language doesnt seem to work. at least for dicts found in the wild

It's likely that dictionaries you find "in the wild" are NSDictionary instances, not NSMutableDictionary, so they're immutable (this is different from Python where all dictionaries are mutable).

2) with the tabbed editor, the editor class needs updates to access tabs

Yes, that needs some work.

3) the editor should probably disallow opening two copies of the same file, unless one is set to read only. this can cause confusion, where one copy is edited, then switching to the other copy effectively undoes the changes!

The way it's supposed to work is that editing a file in one tab, then switching to a different tab with the same file open would reload the file in the second tab... I just did a quick test, and this mechanism seems to work fine here.

JonB

the problem comes when two copies of a file are opened, and then you write to the file programatically, then use editor.open_file. the new file is now open in one tab, but the old file is in the old tab. i think the problem is because each copy thinks it is "clean", since the changes were made outside of the editor. if you then edit one copy in the editor, then i think it gets reloaded in the other, but in some cases that is exactly the wrong thing, for instance i just want to close the "old" copy, but then it ends up overwriting the new version.

omz

@JonB Thanks for the clarification. Yes, the way external changes to files are handled could definitely be improved...

JonB

mostly this is an issue with tabs ... in 1.5 we knew to reload the file in the editor if modifying externally. having ability to programatically check what is in each tab, and reload specific tabs programatically would fix my particular use case.

JonB

in latest beta 160021

        if issubclass(restype, Structure) and not LP64: 

should probably be

if restype and issubclass(restype, Structure) and not LP64: 

when restype is None, this throws an exception.

JonB

re dictionaries, as an example:

ns({'test':'ing'})['test']

throws an exception. ns of a dict returns a __NSDictionaryM. maybe this is another 32bit quirk?

userista

Bug: did_load isn't called with CustomView in new ui

userista

Is the documentation for the beta (modules etc.) available online, or only in app? (I only have an iPhone - easier to read docs on larger screen...)

omz

@JonB

re dictionaries, as an example:

ns({'test':'ing'})['test']

Strange, that works just fine here. I'll have to test it on a 32-bit device...

in latest beta 160021

if issubclass(restype, Structure) and not LP64:

should probably be

if restype and issubclass(restype, Structure) and not LP64:

Yes, I noticed that as well.

@hyshai

Bug: did_load isn't called with CustomView in new ui

Thanks, should be fixed in the next build.

Is the documentation for the beta (modules etc.) available online, or only in app?

The beta documentation is currently only available in the app, sorry.

omz

@JonB

Okay, I know what's going on with the dictionary in 32-bit mode now. Apparently, the 32-bit runtime encodes boolean return types as char, and I used c_char to translate that... which results in a one-character string in Python that is evaluated as True in a boolean context. I think mapping 'c' to c_byte (in the type_encodings dict) should fix the problem (in case you want to experiment in your own version of the module before I get to upload a new build).

What does this have to do with NSDictionary? Well, __getitem__ etc. use isKindOfClass_ to determine whether an ObjCInstance wraps an NS(Mutable)Dictionary, and since isKindOfClass_ always returns "True", this check doesn't work properly.

JonB

ahh, i dont envy your job trying to support both 32 and 64 bit (but please do!). i did think it was strange that i had to use '\x01` etc for some bools.
went with the duck typing approach for getitem, setittem, delitem, etc, in my hacked module and that is working fine.

one other question: some objc objects get created with an alloc(), if we create the instance in python. do we need to explicitly dealloc those when we are done withe the object, or does the objc garbage collection take care of that assuming we delete the python reference?

JonB

hyashi,
os.path.join(os.path.split(os.__file__)[0],'../Documentation') contains the docs(html) so you could use your favorite archiver script to copy these off of the device, for leisurely consumption

omz

@JonB

In a nutshell: You have to balance every alloc-init with a release() or autorelease(). Objects that are returned from other methods are typically autoreleased already, so releasing them again would result in a crash.

ObjCInstance keeps its own strong reference to the wrapped object internally, so that the object isn't deallocated as long as the wrapper exists (whose memory is managed by Python's reference counting mechanism, aka Garbage Collection).

If you're unsure about the correct memory management, it's usually better to leak a little bit of memory than to over-release because the latter leads to crashes.

userista

Is it possible to use the new ui editor to add a button to a NavView using the custom attributes field?

I tried using this in the custom attributes box but it doesn't seem to work.

{
'left_button_items': 
[ui.ButtonItem(image=ui.Image.named('ionicons-close-24'))
]}
filippocld223

Referring to the latest beta:
I set the border of a slider to width=1,radius=15 via UI Editor
When i present the view it only takes the width parameter but the border is 0

omz

@filippocld

Thanks, definitely looks like a bug. As a workaround for now, you could put this into the "Custom Attributes" field:

{'corner_radius': 15}
omz

@hyshai

Is it possible to use the new ui editor to add a button to a NavView using the custom attributes field?

Unfortunately, this isn't possible right now. The problem is that the button items that a NavigationView shows depend on the view that is currently shown in the navigation view, not the navigation view itself. ui.load_view creates the root view of the navigation stack automatically, but there's currently no way to access it programmatically.

Gcarver

Ui.convert_point(touch.location, fmview, toview) no longer works. Touch.location is not a tuple and also does not have an as_tuple function as the documentation states.
I had to manually convert to a tuple grabbing the x and y. Touch.location now seems to be a subclass of vector2.

omz

@Gcarver Thanks, should be fixed in the next build.

Moe

ui.input_alert() does not accept input as keyword argument, though it says so in the documentation. However following workaround works: ui.input_alert('Title','',<actual-prefilled-input>)