omz:forum

    • Register
    • Login
    • Search
    • Recent
    • Popular
    1. Home
    2. dgelessus

    Welcome!

    This is the community forum for my apps Pythonista and Editorial.

    For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.


    • Profile
    • Following 0
    • Followers 9
    • Topics 22
    • Posts 1145
    • Best 289
    • Controversial 0
    • Groups 0

    dgelessus

    @dgelessus

    402
    Reputation
    11644
    Profile views
    1145
    Posts
    9
    Followers
    0
    Following
    Joined Last Online
    Website github.com/dgelessus Location `__main__.__file__`

    dgelessus Unfollow Follow

    Best posts made by dgelessus

    • RE: Pythonista itself - how was it developed?

      Well-written C code is easy to compile for different architectures, so the fact that iOS devices use ARM instead of x86 processors isn't a huge problem. CPython supports some ARM platforms natively (mainly Linux), so all of the processor-specific problems have been solved already. iOS is also Unix-based and relatively similar to macOS, so even though CPython doesn't support it natively, the differences aren't huge. There are also projects like https://github.com/pybee/Python-Apple-support, which is basically a set of patches to get CPython working properly on iOS/watchOS/tvOS. (I believe this isn't exactly what Pythonista uses - @omz probably has his own custom version of CPython.)

      Interacting between C and Objective-C is also very easy, since Objective-C is more or less an extension of C. This works both ways - you can easily call C code from an Objective-C method, but you can also write regular C functions in Objective-C code, which can use Objective-C features in their implementation, but can also be called from regular C code.

      Many of Pythonista's custom modules (like ui, scene, console, etc.) are implemented using CPython's C API and extension interface, which allows writing Python modules in C. So in principle it's not too difficult to expose iOS Objective-C APIs to Python - you only need to write an extension module in C with some Python-exposed functions that call the appropriate Objective-C APIs. Things get more difficult of course when you also want to provide a nice Pythonic API on the Python side - that requires defining Python classes in C code and converting between Objective-C, C and Python data types where appropriate.

      Another way to interact between Python and C is using the ctypes module, which is part of the standard library, and lets you use (almost) any C API from Python at runtime, without having to compile custom C code. This can also be used to interact with Objective-C APIs, because Objective-C's runtime support library provides a pure C interface to most parts of the Objective-C language. Based on this, @omz wrote Pythonista's objc_util library, which lets you interact with Objective-C APIs and objects from Python. Some of Pythonista's modules (like editor) are implemented in pure Python using objc_util instead of using CPython's extension API. (There is also a non-Pythonista-specific library called Rubicon, which has similar goals and features as Pythonista's objc_util.)

      posted in Pythonista
      dgelessus
      dgelessus
    • RE: Update your Dropbox Python Core SDK

      @DNSGeek It would also mean that Pythonista would not be allowed into the App Store, because of Apple's rules against downloading of executable code. Pythonista had to stop being an "Open In" target already for that reason.

      posted in Pythonista
      dgelessus
      dgelessus
    • RE: Wish list for next release

      Speaking of forum-friendly, a way to view and copy a traceback in plain text form. The debugger is nice, but it cuts off long error messages and there's no way to copy the traceback (AFAICT).

      posted in Pythonista
      dgelessus
      dgelessus
    • Bits and pieces for pythonista_startup

      https://github.com/dgelessus/pythonista_startup

      I'm a big fan of the pythonista_startup feature (thanks @omz!) and use it a lot. Mine grew to a package with six submodules (is this normal?) so I decided to put it up on GitHub.

      Installation is simple - back up your existing pythonista_startup file/folder, then run these commands in stash:

      mkdir site-packages/pythonista_startup
      cd site-packages/pythonista_startup
      git clone https://github.com/dgelessus/pythonista_startup.git
      

      And to update:

      cd site-packages/pythonista_startup
      git pull
      

      Features:

      Almost all of them are turned off by default as not everyone will find them useful or because they're unstable. To turn them on, edit __init__.py and uncomment the entries that you want to have enabled.

      • Anti-globals-clearing mechanism as originally posted here. (Optional)
      • Custom sys.displayhook and sys.excepthook for fancy colors in the interactive prompt, an IPython-like Out history, and cleaner exception display (only for code run interactively). (Optional, colors unsuitable for dark themes)
      • Makes "hidden" built-in types (like function, instancemethod and code) accessible as globals. (Optional, possibly not very useful)
      • Enables the standard faulthandler module to record a Python traceback when Pythonista hard-crashes. (Optional, Python 3 only, slightly untested)
      • Patches for the sys streams to make some "normal terminal" scripts work. (Optional, enabled by default)
      • Proper exception handling for any code run in pythonista_startup and the submodules - by default Pythonista just ignores all exceptions that happen there.
      • Experimental "preflight hook" support to run code every time a script is run from the Pythonista editor. (Optional, very unstable, probably breaks with every Pythonista update)

      It is possible to add custom features in their own subfiles. Each submodule contains a run function, which is run by the main __init__.py. Submodules are not detected automatically, you need to add new ones to the __init__.py by hand.

      posted in Pythonista
      dgelessus
      dgelessus
    • RE: How to ask a good question

      Nicely written @zrzka!

      For image uploads I would suggest Imgur (https://imgur.com/), since it provides all essential image hosting features for free without requiring a login - you can even delete images uploaded anonymously (you get a special link for that after the upload).

      Although using Dropbox works, I don't think it's a very good place to host images. Once you use Dropbox to host an image, you have to keep it around in your files, or the link will break. Even if it's "just" for a support thread, broken images/links are a pain for people who read the support thread later because they have a similar issue. Also, on iOS the Dropbox app has this "feature" where any Dropbox link is opened in the app rather than the browser. This isn't a problem for embedded images, but with regular links it's quite annoying.

      About posting the complete code with which the problem happens - I agree that you shouldn't leave out important info, but posting the entire code isn't always good either, especially when it's a lot of code across multiple files. If possible, try to narrow down where in your code the problem happens, and post only the code needed to reproduce the problem - this is called a minimal, complete, and verifiable example. Doing this makes it much easier for others to help, and in the process you might even figure out the problem on your own.

      One other thing... we all agree that this person that you're linking to wasn't very nice, but I think they got the message. There's no need to keep bashing them, it's not going to make them a better person.

      posted in Pythonista
      dgelessus
      dgelessus
    • RE: Input Issue with Python

      Which Python version are you using to run your script, 2 or 3? This issue could because you're using Python 2 to run a script meant for Python 3. (In particular, the input function does different things in Python 2 and 3, which can cause some obscure problems.)

      In general, if you're encountering a problem when running a specific piece of code, it's always a good idea to include the code in question (as long as it doesn't contain anything secret of course). If you get an error message, copy and paste it into the post as well. (If the error pops up in a red window like in your screenshot, tap on "Print Traceback" to print the error to the console in a copyable format.) For code and error messages, use code blocks (with triple backticks) as @zrzka described above. Without the code and the full error message, it's often difficult to find out what the problem is.

      By the way, almost all of us on the forum here are users like you. The app is developed by a single person, everyone else on the forum is posting in their free time and isn't getting paid to provide support. Please keep that in mind when asking for help.

      posted in Pythonista
      dgelessus
      dgelessus
    • RE: How to upload file to Pythonista?

      To import a file into Pythonista, you can "share"/"open in" it from another app. In the share sheet, select the "Run Pythonista Script" action in the bottom row. (If you don't see it, you might have it disabled. To fix that, scroll all the way to the right, tap on "More", and enable Pythonista in that list.) Now you should get a small Pythonista window with a few icons. Tap on "Import File", this will copy the file into Pythonista.

      Note that you cannot import Python scripts this way (because of Apple's restrictions on importing code). If you want to import Python code from somewhere else, you can copy and paste it into Pythonista, or put it in a zip file and import that.

      posted in Pythonista
      dgelessus
      dgelessus
    • RE: Help - I corrupted my Pythonista somehow

      @Phuket2 Do you have an enum.py module or enum folder in your site-packages or site-packages-3? enum.IntFlag (the class that's missing and causing the exception) is new in Python 3.6, so if you manually installed a different version of the enum module previously, you need to delete it or update it.

      posted in Pythonista
      dgelessus
      dgelessus
    • RE: UI proxies for subclassing

      Please, I really doubt that omz is actively trying to stop you from subclassing certain classes. Most likely these things are just a consqeucene of how Python's C API works. First of all, subclassing needs to be explicitly enabled on the C side. When defining a Python class in C, you basically create a big PyTypeObject (which is a struct) containing all info about the class. One of the struct fields is tp_flags, which contains various flags (duh). For example the one from PyFloat_Type looks like this:

      Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
      

      And for comparison, the one from PySlice_Type:

      Py_TPFLAGS_DEFAULT,                         /* tp_flags */
      

      Note how the one for float has Py_TPFLAGS_BASETYPE while the one for slice does not. And if you try it in Python, you'll see that you can subclass float, but for slice Python will tell you that it's "not an acceptable base type".

      And why not just enable subclassing by default? Well, the C implementation needs to make a special case for when an instance of a subclass is created, rather than an instance of the class itself. In float_new this looks like this:

          if (type != &PyFloat_Type)
              return float_subtype_new(type, args, kwds); /* Wimp out */
      

      In this case the subclass handling is really simple, but that is because float on the C side is mostly a struct containing a double. (And if you check slice_new, there is no speical case for subclasses there.)

      As for the subclass checking, the equivalent of isinstance is implemented for each type by macros like these:

      #define PyFloat_Check(op) PyObject_TypeCheck(op, &PyFloat_Type)
      #define PyFloat_CheckExact(op) (Py_TYPE(op) == &PyFloat_Type)
      

      And PyObject_TypeCheck is also a macro, defined as:

      #define PyObject_TypeCheck(ob, tp) \
          (Py_TYPE(ob) == (tp) || PyType_IsSubtype(Py_TYPE(ob), (tp)))
      

      And PyType_IsSubtype basically returns tp in type(ob).__mro__, which obviously does not call any of __instancecheck__, __subclasscheck__ and __subclasshook__.

      And even if ui's functions accepted things that didn't actually subclass ui.View, it would probably crash the app. The reason is that on the C side, the pointer to the underlying UIView or controller or whatever is actually a struct field, and _objc_ptr is likely a read-only descriptor returning that field's value. So no, _objc_ptr is not sufficient, that's only a convenience attribute for objc_util.

      posted in Pythonista
      dgelessus
      dgelessus
    • RE: ctypes pythonapi version

      @ywangd That's a unicode/str/bytes issue. Short answer, the arguments to Python's C API need to be byte strings (b"...") unless stated otherwise in the docs. Long explanation below. :)

      In C, the type char represents a byte (which is generally agreed to be 8 bits nowadays). Most code uses char * (a pointer to a char, which is effectively used as an array of unknown size) as the data type for "strings". Because a char is only 8 bit wide, it can't hold a full Unicode code point. There is the wchar_t data type, which is not really standardized either, but it's wider than char and can usually hold a Unicode code point, so APIs that support Unicode properly use wchar_t * instead of char * for strings.

      In Python 2, the situation is similar. str is like C's char * - it's made of 8-bit bytes and can't hold Unicode text properly, and unicode is like C's wchar_t * and supports full Unicode. That's why ctypes converts str to char * and unicode to wchar_t * and vice versa.

      Now Python 3 comes along and cleans up a lot of Python 2's Unicode issues. In Python 3, you have the two data types bytes and str. Python 3's bytes is an 8-bit string like Python 2's str, and Python 3's str is a Unicode string like Python 2's unicode. And most importantly, in both Python versions the string "hello" is of type str, which means that under Python 2 it's 8-bit (i. e. char *) and under Python 3 it's Unicode (i. e. wchar_t *).

      Python's C API functions, such as PyRun_SimpleString use normal char * for source code. So under Python 2, your code works fine - "print(42)" is an 8-bit string, gets converted to char *, which is what PyRun_SimpleString wants. Perfect. Under Python 3, "print(42)" is a Unicode string, which gets converted to wchar_t *, and then things go wrong. Because wchar_t is 32 bits wide under iOS, the text print(42) represented as a wchar_t * has three null bytes between each character (which would be used if the character had a higher code point in Unicode). Null bytes are also the "end of string" marker in C. Python reads the start of the wchar_t * string, but expects a char * - it sees a p, then a null byte, and thinks "great, I'm done" and so it just runs p instead of print(42).

      posted in Pythonista
      dgelessus
      dgelessus

    Latest posts made by dgelessus

    • RE: rubicon.objc’s problem

      For reference, @JonB has opened an issue for this feature on the rubicon.objc issue tracker: https://github.com/beeware/rubicon-objc/issues/181

      posted in Pythonista
      dgelessus
      dgelessus
    • RE: help about return type

      Sorry for the late reply - I forgot to check the forum recently... The version of ctypes_patch that @JonB linked to is the original version from the pull request that added it. There have been some updates to it that fix a few problems, including the "The ctype {} already has a getfunc" error that @ryubai posted above. I would recommend using the current version of ctypes_patch from the repo and not the old one from the pull request.

      posted in Pythonista
      dgelessus
      dgelessus
    • RE: Is there an equivalent to objc_util, but for Swift?

      I'm also not aware of any existing project that allows calling Swift from Python. As others have said above, the other direction (calling Python from Swift) is quite well-supported, but that doesn't help us much here.

      In principle, it is entirely possible to call Swift from Python. It would require more work than with Objective-C though.

      Objective-C's calling conventions are completely compatible with C, which allows using the standard ctypes library to call Objective-C code. The Objective-C API also exposes a lot of information at runtime, which allows inferring method return/argument types so you don't have to set them manually in most cases.

      With Swift the situation is quite different. As far as I know, Swift itself does not provide nearly as many runtime API functions as Objective-C does, so there is no easy way to get function/method type information at runtime. Swift might not even store this type information in the compiled binaries at all (I haven't checked recently).

      There's also the more fundamental problem that Swift's type system and calling convention are completely different from C. In some simple cases the calling conventions overlap enough that you can call some Swift functions using ctypes, but IIRC all instance methods (anything with a self) and functions/methods with certain complex return types (tuples, etc.) are incompatible with the C calling convention.

      This means that calling Swift from Python would require a custom extension module similar to ctypes, but for the Swift calling convention. Internally, ctypes uses the libffi library, but libffi does not support the Swift ABI (yet?). In any case, since a custom native module is required, a library like this wouldn't be usable in Pythonista unless @omz compiles and includes it in the app.

      TLDR: It would be possible, but would require quite a bit of work, and couldn't be developed in Pythonista.

      posted in Pythonista
      dgelessus
      dgelessus
    • RE: how can i access dispatch_get_main_queue

      If I'm reading the error message correctly, rubicon.objc is installed properly. The error about ctypes.util/ctypes.macholib is a known issue in Pythonista: https://github.com/omz/Pythonista-Issues/issues/311

      Sadly it hasn't been fixed yet, but there is a workaround - you can copy the missing files from the CPython source code. See this comment on the issue for instructions: https://github.com/omz/Pythonista-Issues/issues/311#issuecomment-398715503

      posted in Pythonista
      dgelessus
      dgelessus
    • RE: how can i access dispatch_get_main_queue

      The second argument of dispatch_async needs to be a block object. You can't pass in a normal Python function, because ctypes won't know how to convert it. Instead, you have to create the block object manually:

      def block_code(_self):
          print("ok")
      block = ObjCBlock(block_code, None, [c_void_p])
      dispatch_async(dispatch_get_main_queue(), byref(block._as_parameter_))
      

      For more details about how ObjCBlock works, you can look at the ObjCBlock section of the objc_util documentation.

      By the way, dispatch_async doesn't return anything, so you should set its restype to None and not c_void_p. (If you use c_void_p it will probably still work, but the return value won't be anything useful.)

      posted in Pythonista
      dgelessus
      dgelessus
    • RE: how can i access dispatch_get_main_queue

      dispatch_get_main_queue is not a normal C function, so you can't access it normally via ctypes/objc_util. Instead, you need to reproduce dispatch_get_main_queue's functionality in Python using some custom code:

      from ctypes import Structure, byref, cast, c_void_p
      from objc_util import ObjCInstance, c
      
      class struct_dispatch_queue_s(Structure):
          pass
      
      _dispatch_main_q = struct_dispatch_queue_s.in_dll(c, "_dispatch_main_q")
      
      def dispatch_get_main_queue():
          return ObjCInstance(cast(byref(_dispatch_main_q), c_void_p))
      

      You don't need to understand what this code does exactly - you can just copy-paste it into your code and use dispatch_get_main_queue() like you would normally.

      (If anyone is interested in the exact details of what the above code does and where it comes from, see this page. The original C code is very complex, so you need to be familiar with both ctypes and the C preprocessor to understand all the translation steps. But again, you don't have to understand all of this - you can just copy-paste and use the finished Python code.)

      posted in Pythonista
      dgelessus
      dgelessus
    • RE: Help finding the value of an objc string constant...

      @shinyformica Correct - these string constants are actually global variables of type NSString *, which means that they contain a pointer to the actual string object.

      To read the pointer from the global variable, you need to use ctypes.c_void_p.in_dll(objc_util.c, "ConstantName"), as @mikael already found above. objc_util.c.ConstantName will "work" (it doesn't crash), but that treats ConstantName as a function and not a variable, so it returns a function pointer (which will crash when you try to call it, because normal variables are not valid functions).

      The value returned by in_dll is a regular Objective-C object pointer (which points to the NSString object), but as a c_void_p object, so you need to cast it to ObjCInstance manually so that you can use it. Once you do that, you have a regular NSString object with the value you're looking for.

      In short:

      >>> objc_util.ObjCInstance(ctypes.c_void_p.in_dll(objc_util.c, 'UICollectionElementKindSectionHeader'))
      <b'__NSCFConstantString': UICollectionElementKindSectionHeader>
      >>> objc_util.ObjCInstance(ctypes.c_void_p.in_dll(objc_util.c, 'UICollectionElementKindSectionFooter'))
      <b'__NSCFConstantString': UICollectionElementKindSectionFooter>
      

      Strangely, the value of both string constants is literally their name, so the approach suggested by @mikael (just pass the name of the constant as a string) should have worked. Maybe UIKit expects you to pass in the exact pointer, and not just any string with the same contents? (That would be quite strange though.)

      (By the way, please do @-mention me if there's something where you think I can help. Sometimes I only skim the forums very quickly, so I might miss some questions by accident.)

      posted in Pythonista
      dgelessus
      dgelessus
    • RE: iPhone shortcut script

      This is probably not possible.

      In general, iOS does not allow one app to control another app. Apps can provide Siri shortcuts or a URL scheme to allow limited control by other apps, but this has to be specifically supported by the app, and the developer decides what actions can be triggered by other apps.

      If the Sonos app provides Siri shortcuts or a URL scheme to control music playback, you can use that, otherwise you're out of luck. It doesn't make a difference whether you use Pythonista or the Shortcuts app to do the automation - both apps only have access to the control endpoints provided by the other app, and nothing more.

      posted in Pythonista
      dgelessus
      dgelessus
    • RE: Need help for CFURLRef in Objectivec

      From an Objective-C perspective, CFURLRef is basically identical to NSURL *, because of "toll-free bridging" between Core Foundation and Objective-C/Foundation. (This also applies to other CF types, like CFStringRef/NSString *, CFArrayRef/NSArray *, etc.) So you should be able to create a NSURL from a string normally (using objc_util.nsurl) and pass the ObjCInstance into the parameter that expects a CFURLRef.

      posted in Pythonista
      dgelessus
      dgelessus
    • RE: Calling superclass method in objc_util subclass?

      @mikael I'm not very creative with these things - do you have some example code based on objc_util that I could translate to Rubicon?

      In general the syntax for classes looks like this:

      from rubicon.objc import NSInteger, NSObject, ObjCProtocol, objc_method
      
      WhateverDelegate = ObjCProtocol("WhateverDelegate")
      
      class MyCustomDelegate(NSObject, protocols=[WhateverDelegate]):
          # the protocols=[...] part is optional of course, if you don't need to implement any protocol
          @objc_method
          def someThing_didAThingWithCount_(self, something, count: NSInteger) -> None:
              print(something, count)
      
      something.delegate = MyCustomDelegate.new()
      

      The Python 3 type annotation syntax is used to set each method's return and argument types. You use regular ctypes type objects for the types, and Rubicon predefines some common typedefs like NSInteger. Anything you don't annotate is assumed to be an Objective-C object (ObjCInstance).

      Blocks are similar:

      from rubicon.objc import Block, NSInteger
      
      @Block
      def thing_callback(number: NSInteger) -> None:
          print(number)
      
      whatever.doThing("abc", withCallback=callback)
      
      posted in Pythonista
      dgelessus
      dgelessus