IronPython & the Wing IDE

Using the Wing Python IDE with IronPython

IronPython and CPython are friends really

 

 

Introduction

The Wing IDE is my favourite IDE for developing with Python. It has all the features you expect in a modern editor:

  • Syntax highlighting
  • Goto definition
  • Fully scriptable for integration with external tools (scripted with Python of course)
  • Integrated Python shell
  • Powerful debugger and testing support
  • Intellisense (autocomplete)
  • Calltips (the source assistant) showing method / function parameters and docstrings

Features like calltips and autocomplete rely on the IDE being able to statically analyse the code and infer the types in use. This is much harder for dynamic languages than it is for statically typed languages. This is because types aren't determined until runtime.

Despite this there are plenty of tools available for dynamic languages in general (refactoring tools were first created for Smalltalk) and Python in particular. I wrote about some of the tools available for Python and IronPython in:

Not all of the features I listed above work for IronPython in Wing, but you can use the scripting API to plugin compatible tools instead. See Integrating Other Tools into Wing for an example of how to do this.

Autocomplete

One of the reasons I like Wing is that the autocomplete is the best amongst the Python editor's I've tried. This feature works perfectly with IronPython because IronPython code is Python code. Where is doesn't do so well is when you use any types provided by the .NET framework.

Wing does provide a mechanism for teaching it about new libraries where it doesn't have access to the source to analyse it. I've worked with the Wingware guys to adapt this script for IronPython so that we can pre-generate the information that Wing needs for .NET libraries.

It works by importing .NET namespaces and introspecting all the classes (and their) methods that they contain. It creates one PI file per namespace. A PI file is just a Python file with a skeleton definition of all the classes - with the correct base classes, methods and their parameters. It even annotates the skeleton Python code with the return types so that when you call methods in your code Wing will know the type of the objects returned by them.

Because these PI files are valid Python files Wing can just reuse its knowledge of Python code to analyse them.

Generating PI Files

The script used to generate the PI files for the .NET framework is an enhanced version of generate_pi.py which comes with Wing. The changes I made initially were integrated into this script so it will run under CPython and IronPython.

Note

Wing runs on all three major operating systems: Windows, Mac OS X and Linux.

This script should run fine with IronPython on Mono. If it doesn't you can generate the files under .NET on Windows and copy them across to the machine you are using Mono on.

Here's how you use generate_pi.py:

  1. Create a new directory to hold the PI files. I use C:\Wing-pi (the image below shows my configuration on OS X however).

  2. You need to add this to the 'Interface File Path' so Wing knows to look here for PI files. You do this from the preferences dialog: Edit menu -> Preferences -> Source Analysis -> Advanced -> Insert

    Adding a directory for PI files in the preferences dialog.
  3. From the command line switch to this directory.

  4. Execute the following command line:

    ipy.exe generate_pi.py --ironpython

This generates the PI files. It takes around ten minutes or so generating the default set of PI files. As it uses the docstrings, which IronPython pulls in from the XML .NET documentation, it doesn't work so well on 64bit Windows (on 64bit .NET the documentation is kept in a different place and IronPython doesn't know to look there).

There are a few caveats and limitations with this currently - but it is still very impressive. The details on how to use this to generate PI files for assemblies not covered in the default set, along with these limitations, are covered below. First some screenshots of the results.

In Action

Once the PI files are generated you should find this happening as you use .NET types in your IronPython code:

Autocomplete in action with the Guid class

Because Wing knows that calling Guid.NewGuid() returns a System.Guid it offers the right members for autcomplete on the object returned by that method call.

Not only that but in the source assistant (bottom right of the UI by default) it will show us the documentation for this method:

The Wing source assistant

Customizing

The script currently generates pi files for the following assemblies and namespaces (and all contained namespaces):

System, System.Data, System.Windows.Forms, System.Drawing, System.Xml, Microsoft, clr

These are hardcoded in lines 969-971 of the script. You can easily add new ones but eventually we'll enable this from command line arguments. This results in PI files for 105 namespaces taking up about 30mb. Using these doesn't result in a slowdown in Wing which is impressive. (They're loaded on use and cached - many of them cross-reference each other where they return types defined in another namespace.)

Note

The PI generator script introspects the .NET types to get method parameter names (including distinguishing instance methods from static methods), return types and so on. Where there are multiple overloads of a method it always uses the first one. The IronPython code to do this is shown at: Introspecting .NET Types and Methods from IronPython

Limitations

There are several limitations / caveats. Some of these can be fixed by improving the script and some by improving Wing.

  • When you do "import System." you aren't offered a list of sub-namespaces to import. The solution to this is to have PI files as packages, which doesn't yet work but will be implemented soon.

  • Methods called None are valid in IronPython (and common as enumeration fields) but are invalid syntax in Python / PI files. These are renamed to None_.

  • The following member types are not recognised by the script and are set to None in the PI files. This means that some potentially useful information is lost but it isn't immediately obvious how best to represent this information for some of these member types:

    • field descriptors (enumerations)

      In .NET enumerations behave much more like instances than classes. The field descriptor docstrings are usually not helpful but I'm also sure that None is not helpful as a value. It should really be a property returning an instance of the enumeration.

      The enumaration fireld has an underlying value (accessed as .value__) but I don't think setting it to that would be useful.

    • events (very common and there may be multiple types of events)

    • indexers (Item)

    • attributes that are classes or enumerations. The docstrings can sometimes be useful. e.g. System.ActivationContext.ContextForm

      >>> ActivationContext.__dict__['ContextForm'].__doc__
      '\r\n\r\nenum ContextForm, values: Loose (0), StoreBounded (1)\r\n\r\n'
      
  • Wing doesn't yet understand the return types of properties. A feature request for this has been made.

  • Currently Wing doesn't show the docstrings for properties in the source assistant (it shows the docstring for the property builtin instead). This looks like a bug in Wing.

  • For .NET types with public constructors an __init__ method could be constructed. Currently they all show in the source assistant as *args.

  • Where a class inherits from something in another namespace (often delegates do this) then the base class needs to be imported into the PI file first. This is not done yet.

  • For most .NET methods the parameter names are determined using reflection. This doesn't work for some - particularly methods like ReferenceEquals, ToObject and so on. (Are these inherited methods or just added by IronPython? Needs investigating.) For these we fallback to __GetCallableSignature which doesn't get the argument names from .NET methods (not really a big deal). It also doesn't cope with indexing in type signatures of parameter types (use of square brackets). It interprets these as default values.

If you have fixes for any of these issues then please contribute them back.

General Tips on Using Wing with IronPython

When you create a new project with Wing it allows you to set the Python interpreter for the project. This interpreter is used for the Python shell, the testing support and for debugging / executing Python files you are working on. This allows you to specify the specific version of Python you are developing for.

Note

One thing I haven't tried is setting the project interpreter to use Python.NET. Python.NET is a modified CPython interpreter with .NET integration. Whilst its behaviour is not always identical to IronPython (under the hood it is extremely different) it is compatible in many ways. As it is a version of CPython it may not be subject to the same limitations described here.

When developing an IronPython project it is tempting to set the interpreter to be IronPython. This does allow the 'Execute Current File' to work correctly. Unfortunately the Python interactive shell, which is a really useful component in Wing for experimenting, is tied to any Python interpreter you set here. This is because the shell and the debugger are very tightly integrated. As Wing IDE is written in Python (and C) it can't use the IronPython interpreter; so you lose the interactive shell. The Wing team hope to fix this so that setting the project interpreter to IronPython doesn't cause you to lose the interactive shell. In the long term they hope to implement debugger support for IronPython (changes coming in IronPython 2.6 will make this possible), but I suspect this will take a long time.

I prefer to keep the interactive interpreter and integrate separate tools for running Python files, executing tests and so on.

Integrating Other Tools into Wing

Wing has a rich and powerful scripting API allowing you to integrate external tools; either launching them as entirely separate processes or piping the results back into a Window in the Wing user interface. The documentation for this is included in Wing, or can be found online at Scripting and Extending Wing IDE.

Adding new commands is very easy. Like adding additional PI files the first step is to add a directory where you will keep your Wing scripts. This is done through the preferences dialog: File menu -> Preferences -> IDE Extension Scripting -> Insert

Adding a directory for Wing extension scripts in the preferences dialog.

By default Wing is configured to reload scripts automatically when they change on disk. This is useful when you are working on them. Functions you define in your scripts become commands that you can then bind to key combinations to trigger them. This is done by adding custom key bindings from the preferences dialog: Edit Menu -> Preferences -> User Interface -> Keyboard -> Custom Key Bindings -> Insert

Binding a script function to a key combination.

Through the Wing API you have access to the current file, you can create new Windows and so on. Importantly you can also launch external commands.

The following command demonstrates executing the current file with IronPython. I usually bind it to the F5 key.

import wingapi
import os

from wingutils import spawn

def custom_execute(app=wingapi.kArgApplication):
  wingapi.gApplication.ExecuteCommand('save-all')
  editor = app.GetActiveEditor()
  filename = editor.GetDocument().GetFilename()
  directory = os.path.dirname(filename)
  env = app.GetProject().GetEnvironment(filename)
  cmd = env.get('COMSPEC', 'cmd.exe')
  argv = [cmd, '/c', 'ipy.bat', filename]
  spawn.win32_start_process(cmd, argv, env=env, child_pwd=directory, new_console=True)

It executes as an external process through cmd (and so is specific to Windows) and relies on ipy.bat being on your path. I use ipy.bat so that it can pause after execution. It also calls the 'save-all' command which saves all open files prior to execution. You may want to remove this...

Note

When you bind a key to execute the command you bind the key to the function name, not the script name. In this case you would bind the key to the custom_execute function.

I launch it as an external process in this way so as not to block Wing whilst the file is executing. It is simple to have an alternative function which blocks and pipes the output of the file executed back into a scratch buffer in Wing. Using these techniques it is easy to plug any variety of external tools into Wing. I'm particularly fond of PyFlakes which can quickly pick up on common errors in Python code (undefined and unused variables for example - often caused by typos).

Have fun with Wing and IronPython. If you have any questions the Wing mailing list is a friendly and responsive place.

For buying techie books, science fiction, computer hardware or the latest gadgets: visit The Voidspace Amazon Store.

Hosted by Webfaction

Return to Top

Page rendered with rest2web the Site Builder

Last edited Mon Jan 16 00:11:25 2012.

Counter...