omz:forum

    • Register
    • Login
    • Search
    • Recent
    • Popular

    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.


    Correct way to call Pythonista script from within a Shortcuts workflow?

    Pythonista
    6
    37
    15483
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • cvp
      cvp @mikeno last edited by cvp

      @mikeno Sorry, no idea how to define an ObjcBlock in rubicon (ObjectiveC in Pyto).
      Hoping that @JonB will read this and be able to help, as usual.

      # coding: utf-8
      from rubicon.objc import *
      from  ctypes import *
      
      def handler(_cmd, _data, _error):
          print(ObjCInstance(_data))
      
      handler_block = ObjCBlock(handler, None, [c_void_p, c_void_p, c_void_p])
      
      def main():
          CMAltimeter = ObjCClass('CMAltimeter')
          NSOperationQueue = ObjCClass('NSOperationQueue')
          if not CMAltimeter.isRelativeAltitudeAvailable():
              print('This device has no barometer.')
              return
          altimeter = CMAltimeter.new()
          main_q = NSOperationQueue.mainQueue
          altimeter.startRelativeAltitudeUpdatesToQueue_withHandler_(main_q, handler_block)
          print('Started altitude updates.')
          try:
              while True:
                  pass
          finally:
              altimeter.stopRelativeAltitudeUpdates()
              print('Updates stopped.')
      
      if __name__ == '__main__':
          main()
      

      Gives

      Traceback (most recent call last):
        File "iCloud/barometer.py", line 8, in <module>
          handler_block = ObjCBlock(handler, None, [c_void_p, c_void_p, c_void_p])
        File "Pyto.app/Lib/rubicon/objc/api.py", line 1834, in __init__
          self.struct = cast(self.pointer, POINTER(ObjCBlockStruct))
        File "Pyto.app/site-packages/python3.10/ctypes/__init__.py", line 510, in cast
          return _cast(obj, obj, typ)
      ctypes.ArgumentError: argument 1: <class 'TypeError'>: wrong type
      
      JonB 1 Reply Last reply Reply Quote 0
      • mikeno
        mikeno last edited by

        Thx for trying, I’ll wait!

        cvp 1 Reply Last reply Reply Quote 0
        • JonB
          JonB @cvp last edited by JonB

          @cvp I believe in Rubicon, the preferred usage is via type annotations and decorators. Also ObjCBlock wraps ObjC blocks so they can be called in python, while Block wraps python so it is calls me in objc-- so you want plain old Block.

          I think the way you'd do it in Rubicon is:

          (Edited)

          @Block
          def handler(altitudeData: ObjCInstance, err:NSError) -> None:
              print(altitudeData)
          

          Or, I think you can skip the annotation on ObjCInstances:

          @Block
          def handler(altitudeData, err:NSError) -> None:
              print(altitudeData)
          
          cvp 1 Reply Last reply Reply Quote 0
          • cvp
            cvp @JonB last edited by cvp

            @JonB problems
            1)

            @Block
            def handler(altitudeData , err:NSError) -> None:
                print(altitudeData)
            
            handler_block = ObjCBlock(handler, None, [c_void_p, c_void_p])
            

            Gives

            Traceback (most recent call last):
              File "iCloud/barometer.py", line 6, in <module>
                def handler(altitudeData, err:NSError) -> None:
            NameError: name 'NSError' is not defined. Did you mean 'OSError'?
            
            @Block
            def handler(altitudeData, err) -> None:
                print(altitudeData)
            
            handler_block = ObjCBlock(handler, None, [c_void_p, c_void_p])
            

            Gives

            Traceback (most recent call last):
              File "iCloud/barometer.py", line 6, in <module>
                def handler(altitudeData, err) -> None:
              File "Pyto.app/Lib/rubicon/objc/api.py", line 1939, in __init__
                raise ValueError(
            ValueError: Function has no argument type annotation for parameter 'altitudeData' - please add one, or
             pass return and argument types directly into Block
            
            @Block
            def handler(altitudeData:ObjCInstance, err:ObjCInstance) -> None:
               print(altitudeData)
            
            handler_block = ObjCBlock(handler, None, [c_void_p, c_void_p])
            

            Gives

            Traceback (most recent call last):
              File "iCloud/barometer.py", line 9, in <module>
                handler_block = ObjCBlock(handler, None, [c_void_p, c_void_p])
              File "Pyto.app/Lib/rubicon/objc/api.py", line 1846, in __init__
                self.struct.contents.invoke.argtypes = (objc_id, ) + tuple(ctype_for_type(arg_type) for arg_type i
            n argtypes)
              File "Pyto.app/Lib/rubicon/objc/api.py", line 1846, in <genexpr>
                self.struct.contents.invoke.argtypes = (objc_id, ) + tuple(ctype_for_type(arg_type) for arg_type i
            n argtypes)
              File "Pyto.app/Lib/rubicon/objc/types.py", line 103, in ctype_for_type
                return _ctype_for_type_map.get(tp, tp)
            TypeError: unhashable type: 'list'
            
            @Block
            def handler(altitudeData:ObjCInstance, err:ObjCInstance) :#-> None:
                print(altitudeData)
            
            handler_block = ObjCBlock(handler, None)#, [c_void_p, c_void_p])
            

            Gives

            Traceback (most recent call last):
              File "iCloud/barometer.py", line 6, in <module>
                def handler(altitudeData:ObjCInstance, err:ObjCInstance) :#-> None:
              File "Pyto.app/Lib/rubicon/objc/api.py", line 1930, in __init__
                raise ValueError(
            ValueError: Function has no return type annotation - please add one, or pass return and argument types
             directly into Block
            Traceback (most recent call last):
              File "iCloud/barometer.py", line 6, in <module>
                def handler(altitudeData:ObjCInstance, err:ObjCInstance) :#-> None:
              File "Pyto.app/Lib/rubicon/objc/api.py", line 1930, in __init__
                raise ValueError(
            ValueError: Function has no return type annotation - please add one, or pass return and argument types
             directly into Block
            
            @Block
            def handler(altitudeData:ObjCInstance, err:ObjCInstance) -> None:
                print(altitudeData)
                
            handler_block = ObjCBlock(handler, None, (c_void_p, c_void_p))
            

            Gives

            Traceback (most recent call last):
              File "iCloud/barometer.py", line 10, in <module>
                handler_block = ObjCBlock(handler, None, (c_void_p, c_void_p))
              File "Pyto.app/Lib/rubicon/objc/api.py", line 1846, in __init__
                self.struct.contents.invoke.argtypes = (objc_id, ) + tuple(ctype_for_type(arg_type) for arg_type i
            n argtypes)
            TypeError: item 2 in _argtypes_ has no from_param method
            
            1 Reply Last reply Reply Quote 0
            • bosco
              bosco last edited by bosco

              @mikeno This code works for me with the latest version of pyto.

              # coding: utf-8
              from rubicon.objc import Block, ObjCClass, ObjCInstance, py_from_ns
              from rubicon.objc.runtime import objc_id
              
              pressure = None
              
              def handler(_data) -> None:
                  nspressure = ObjCInstance(_data).pressure
                  global pressure
                  pressure = py_from_ns(nspressure)
              
              handler_block = Block(handler, None, (objc_id))
              
              def get_pressure():
                  CMAltimeter = ObjCClass('CMAltimeter')
                  NSOperationQueue = ObjCClass('NSOperationQueue')
                  if not CMAltimeter.isRelativeAltitudeAvailable():
                      print('This device has no barometer.')
                      return
                  altimeter = CMAltimeter.new()
                  main_q = NSOperationQueue.mainQueue
                  altimeter.startRelativeAltitudeUpdatesToQueue_withHandler_(main_q, handler_block)
                  print('Started altitude updates.')
                  try:
                      while pressure is None:
                          pass
                  finally:
                      altimeter.stopRelativeAltitudeUpdates()
                      print('Updates stopped.')
                      return pressure
              
              if __name__ == '__main__':
                  result = get_pressure()
                  print(result)
                  del pressure
              cvp 1 Reply Last reply Reply Quote 0
              • cvp
                cvp @bosco last edited by cvp

                @bosco Thanks for him, and for me, so I don't have to test anymore.
                Do you know why the handler does not have a 2nd parameter (error) like described in Apple doc?

                1 Reply Last reply Reply Quote 0
                • JonB
                  JonB last edited by JonB

                  @cvp, I think this one was correct:

                  @Block
                  def handler(altitudeData:ObjCInstance, err:ObjCInstance) -> None:
                     print(altitudeData)
                  

                  But then pass handler directly to ObjC -- don't call ObjCBlock on it. ObjCBlock makes an Objc block callable by python, which isn't needed here.

                  Or, to make no other changes:

                  @Block
                  def handler_block(altitudeData:ObjCInstance, err:ObjCInstance) -> None:
                     print(altitudeData)
                  
                  cvp 1 Reply Last reply Reply Quote 0
                  • cvp
                    cvp @mikeno last edited by

                    @mikeno With the barometer module of @bosco, where print lines are commented, this script works and continues to log the pressure even if I close the iPad cover. Not tested during a day.

                    import background as bg
                    import barometer
                    
                    with bg.BackgroundTask() as b:
                      while True:
                        result = barometer.get_pressure()
                        l= f"{b.execution_time()}:{result}\n"
                        with open("/private/var/mobile/Library/Mobile Documents/iCloud~is~workflow~my~workflows/Documents/bg.txt", mode='at') as fil:
                          fil.write(l)
                        #print(b.execution_time(), result)
                        b.wait(5)
                    
                    1 Reply Last reply Reply Quote 0
                    • mikeno
                      mikeno last edited by

                      Hi everybody, thx for helping, I’ll buy Pyto and try.

                      1 Reply Last reply Reply Quote 0
                      • cvp
                        cvp @JonB last edited by cvp

                        @JonB said

                        But then pass handler directly to ObjC

                        how do I do that?

                        In the bosco solution, there is no @Block line

                        Édit: ok, understood, use the handler_block directly in

                            altimeter.startRelativeAltitudeUpdatesToQueue_withHandler_(main_q, handler)
                        

                        Thanks for your explanations

                        Edit2: but I need

                        @Block
                        def handler(altitudeData:ObjCInstance, err:ObjCInstance) -> None:
                           print(ObjCInstance(altitudeData).pressure)
                        
                        1 Reply Last reply Reply Quote 0
                        • bosco
                          bosco last edited by bosco

                          I dropped the 2nd parameter (error) because it caused an exception: "item 2 in argtypes has no from_param method", so I tried running without the error parameter.

                          After reading the last comment by @JonB I now understand the proper use of @Block witch can be called directly.

                          This works for me.

                          # coding: utf-8
                          from rubicon.objc import Block, ObjCClass, ObjCInstance, py_from_ns
                          from rubicon.objc.runtime import objc_id
                          
                          pressure = None
                          
                          @Block
                          def handler(altitudeData:ObjCInstance, err:ObjCInstance) -> None:
                              nspressure = ObjCInstance(altitudeData).pressure
                              global pressure
                              pressure = py_from_ns(nspressure)
                          
                          """
                          def bhandler(_data) -> None:
                              nspressure = ObjCInstance(_data).pressure
                              global pressure
                              pressure = py_from_ns(nspressure)
                          
                          handler_block = Block(bhandler, None, (objc_id))
                          """
                          
                          def get_pressure():
                              CMAltimeter = ObjCClass('CMAltimeter')
                              NSOperationQueue = ObjCClass('NSOperationQueue')
                              if not CMAltimeter.isRelativeAltitudeAvailable():
                                  print('This device has no barometer.')
                                  return
                              altimeter = CMAltimeter.new()
                              main_q = NSOperationQueue.mainQueue
                              #altimeter.startRelativeAltitudeUpdatesToQueue_withHandler_(main_q, handler_block)
                              altimeter.startRelativeAltitudeUpdatesToQueue_withHandler_(main_q, handler)
                              print('Started altitude updates.')
                              try:
                                  while pressure is None:
                                      pass
                              finally:
                                  altimeter.stopRelativeAltitudeUpdates()
                                  print('Updates stopped.')
                                  return pressure
                          
                          if __name__ == '__main__':
                              result = get_pressure()
                              print(result)
                              del pressure
                          cvp 1 Reply Last reply Reply Quote 0
                          • cvp
                            cvp @bosco last edited by

                            @bosco said

                            After reading the last comment by @JonB I now understand the proper use of @Block witch can be called directly.

                            Yes, this is what I had also found and explained in my last post

                            1 Reply Last reply Reply Quote 0
                            • bosco
                              bosco last edited by

                              @cvp Correct. After my morning brain fog cleared, that is what you said in your last edit. :-)

                              cvp 1 Reply Last reply Reply Quote 0
                              • cvp
                                cvp @bosco last edited by

                                @bosco this, you bought Pyto and already tried...

                                1 Reply Last reply Reply Quote 0
                                • bosco
                                  bosco last edited by

                                  @cvp I bought Pyto in 2019. I am currently running Pyto 17.1.1 I have tested barometer.py on iPhone 12 mini and iPad mini 4th gen.

                                  cvp 1 Reply Last reply Reply Quote 0
                                  • cvp
                                    cvp @bosco last edited by

                                    @bosco I'm sincerely sorry, my "thus, you bought Pyto and already tried..." was erroneously for @mikeno. I guess that you, @bosco, you know Pyto because you have been part of the solution with @jonB.

                                    1 Reply Last reply Reply Quote 0
                                    • bosco
                                      bosco last edited by

                                      @cvp No problem. I thought maybe your question was intended for @mikeno.

                                      1 Reply Last reply Reply Quote 0
                                      • First post
                                        Last post
                                      Powered by NodeBB Forums | Contributors