 Tuesday, October 10, 2006
Just hailed like hell here, thought a window was going to break ;o)
 Sunday, October 08, 2006
Well, I've started working with the latest drop of Base4.net ( version 2.1) - in fact I've decided to take the plunge and actually start using it for the project I'm currently working on (a work project, rather then a home project) so I should be giving it a pretty good thrashing over the next 3-6 months. I already had a rough schema in place, so I generated types from it using the bundled web admin... It seems pretty cool... though I've struck a few things:
- nvarchar(256) fields seems to show up as being 512 characters in length within base4.net... I'm not sure if this is a bug with the schema interrogation, guess we'll find out as a I dig further in ;o) - though it's only skin deep of course, as you can change them to 256 with a quick edit.
- I managed to create an assembly that was invalid... not sure how ;o) I could look at in reflector .Net, but base4 wasn't having a bar of it (even though it had generated it) - I must have screwed up a relationship somewhere, but I wasn't really sure... ended up just dumping it and starting from scratch again.
- It doesn't seem that easy to refactor existing schema's using the web designer... while I was learning it would have been good to generate a schema, play with the assembly, then start editing and generate again... instead I ended up having to save the schema to disk, delete, edit, and recreate. Still, it's pretty slick :)
I think though it probably all comes down to:
- My urge to make things happen without understanding the in's and outs of base4.net
- Horses for courses, the web admin is great for getting off to ground with base4.net, but I think I'll have to get down and dirty with the xml pretty soon...
Though I can't discuss the project, I can discuss what I'll be building it in... so we have:
- Base4.Net
- SQL 2005
- Castle (Monorail & IoC)
- NLog (or log4net... though I'm loosing faith in the latter)
- NUnit
- RhinoMocks
- Splicer.Net
- Some COM & Unmanaged code for audio processing
This is also the first time I've used monorail for a commercial project (we already use a lot of the facilities and the windsor container in the Syzmk RMP product) so it's certainly some interesting times... as we progress I'll give some feedback on both the Monorail and Base4.Net "user experience" :)
Back again!
Here is the source for the last post: ironpython-and-nunit.zip (23.01 KB)
- it's pretty rough around the edges (and in the middle :P) because I
was just trying to see if it was possible, rather then to produce some
production-level toolkit for integrating IronPython and NUnit. Edit 2008-03-14: An updated version of the code target NUnit 2.4 and VS2008 can be found here.
Usage
First off... usage is pretty straightforward.
- Test fixtures must have NUnitFixture as one of their base classes.
- Test methods must be prefixed with "test".
- The setup method, if you need one, must be called "setUp"
- The teardown method, if you need one, must be called "tearDown"
- Fixture setup and teardown methods are also supported, they are called "setUpFixture" and "tearDownFixture".
- Assert.XXX methods are the same as NUnit, unless you want to assert an exception is thrown.
- For asserting an exception is thrown, make a call to
"self.failUnlessRaises(...)" - it has the same syntax as the PyUnit equivalent,
however it's tweaked slightly so it will work with Python exceptions,
or CLR exceptions.
- Do not perform an "Import * from System" in your test fixtures - it will clober the native Python Exception type, and may give you odd behaviour.
Implementation
There are 8 classes and one python script which make up the
implementation, the 8 classes are split between the "model" (basically
the python-side of the equation and the "NUnit extension" which are the
necessary extensions to expose the model to NUnit...) - let's run through
it quickly:
First we have the AbstractPythonSuite which hosts the
python engine, and handles configuring and running the scripts
containing the test fixtures. Then we have a couple of classes
which form the "model" consisting of a
PythonFixture, representing a fixture, and PythonTestCase, representing
a single test method in the fixture.
To build the model we have a PythonFixtureBuilder which is passed the
python engine (from within the AbstractPythonSuite)
and spits out all
the fixture models, with the test cases assigned... this also assigns
the various delegates for the
setup/teardown/setupFixture/teardownFixture methods.
At this point the model is complete, it's just a matter of integrating it with NUnit - for this we have some more classes.
First of we need the PythonSuiteExtension, which represents the
overall test suite, this contains PythonFixtureExtension's, for the
fixtures, and finally PythonTestMethod for the individual test methods
- the python extensions take care of running the tests in the model and
calling the setup and tear down methods at the right times, and recording the results of course.
Last of all we have the PythonSuiteExtensionBuilder as mentioned in the last post, which takes care of creating the PythonSuiteExtension's for each class derived from AbstractPythonSuite it discovers in the target assembly.
Last of all we have a small python script which provides the necessary implementation for the base NUnitFixture class that all test fixtures are derived from... and a helper method we use in the PythonFixtureBuilder to identify the test methods in a class.
class NUnitFixture:
def __init__(self):
pass
def setUp(self):
pass
def tearDown(self):
pass
def setUpFixture(self):
pass
def tearDownFixture(self):
pass
def failUnlessRaises(self, excClass, callableObj, *args, **kwargs):
if issubclass(excClass, System.Exception):
try:
callableObj(*args, **kwargs)
except Exception, e:
if hasattr(e, "clsException") and
(type(e.clsException) == excClass):
return
else:
try:
callableObj(*args, **kwargs)
except excClass:
return
if hasattr(excClass,'__name__'):
excName = excClass.__name__
else:
excName = str(excClass)
raise AssertionException("%s not raised" % excName)
def getTestCaseNames(testCaseClass, prefix):
def isTestMethod(attrname, testCaseClass=testCaseClass, prefix=prefix):
return attrname.startswith(prefix) and callable(getattr(testCaseClass, attrname))
testFnNames = filter(isTestMethod, dir(testCaseClass))
for baseclass in testCaseClass.__bases__:
for testFnName in getTestCaseNames(baseclass, prefix):
if testFnName not in testFnNames:
testFnNames.append(testFnName)
testFnNames.sort(cmp)
return testFnNames
And that's all there is to it... If anyone actually finds this
useful and wants to build on from it, please feel free, and if at all
possible keep it open source (though you don't have to...)
Something to keep in mind is that at the moment if your test fixture
can't actually be parsed and executed by the python engine, that
exception will bubble up, eventually stopping the test assembly from
being loaded at all... I think the nicer solution to this would be to
add a custom NUnit test case for each script that failed to load, which
in turn fails when executed as part of the assembly - dumping out the
exception raised during construction.
At least then a tool like TestDriven.Net would quickly notify the developer of their mistake, instead of just falling over.
And very last of all, though I've been manually specifying the names of the script files.. using a class declaration like this:
public class MyPythonSuite : AbstractPythonSuite
{
public MyPythonSuite()
: base("MyPythonFixture.py")
{
}
}
You could make it a little more dynamic and do something like this:
public class DynamicPythonSuite : AbstractPythonSuite
{
public DynamicPythonSuite()
: base(FindSuitablePythonScripts())
{
}
private static string[] FindSuitablePythonScripts()
{
List<string> scripts = new List<string>();
Assembly assembly = typeof (DynamicPythonSuite).Assembly;
string indication = "#test";
foreach (string potentialScript in assembly.GetManifestResourceNames())
{
using (StreamReader reader =
new StreamReader(assembly.GetManifestResourceStream(potentialScript)))
{
if (reader.ReadLine().Trim().StartsWith(indication,
true, CultureInfo.InvariantCulture))
{
scripts.Add(potentialScript);
}
}
}
return scripts.ToArray();
}
}
Which will load all scripts with " #test" in the first line, which
seem'is a little easier to me (not having to spell out the path to the
script, and only having to create the test file to make things happen) - YMMV of course.
Breaking scripts
I’ve been thinking about the issues involved with using IronPython
in a product as a scripting language, internally we’ve already
struggled at Syzmk with my love of refactoring breaking scripts in our
deployed products left right and centre.
I think the problems I need to solve first are:
- Ensuring an understanding of the user experience when scripting with the application (read: dogfooding the scripting experience)
- Making sure I identify where helper classes / wrappers etc. may be
necessary to make the scripting experience more palatable to end users.
- Ensuring any changes don’t clobber the contracts between the
scripting language and the client when refactoring the underlying .Net
classes they rely on.
Regression tests for expected script usage is what it comes down…
but if you sit down and start trying to write these tests in NUnit it
all gets a bit unwieldy – there’s a lot of time wasted making sure your
tabs are right, escaping characters correctly in strings, and running
scripts, evaluating the results, and then asserting against them – it's
frustrating, and fails to emulate the experience of your users who are
writing scripts purely in Python.
PyUnit?
So my next port of call was to try the unit testing support that
comes with python (PyUnit) – it would definitely do the job – but then
I need to do a couple of things… first I need to start bundling quite a
few standard libraries with the scripting engine in our product, and
then I have to write additional functionality to support capturing the
results of running the PyUnit tests and presenting them to the
automated build process… It's all sounding like a pain in the ass,
and the automation build works nicely as it is, I’d rather not
have to mess with it...
So I decided to do a little integration work, to see if I could run a test suite written in python as part of a C# assembly.
NUnit meets (Iron)Python
First off, my solutions user experience is ok – not perfect – but
then I just wanted to make sure I could get it working, before I put
any effort into cleaning it up, and not being an expert in extending
NUnit, or coding python, it was probably doomed from the start to be a
mediocre solution ;o) The steps to get it working in a test assembly are:
- Writing a fixture in python, and including it in the assembly as an embedded resource.
- Creating a suite which references the embedded python files - this is a class derived from the "AbstractPythonSuite".
- Creating a Suite builder (I had to do this in the test assembly
itself, because it wasn’t working when I bundled this in a support
library).
- Run the suite ;o)
First off, we write the fixture, the only requirements are:
- The fixture class is derived from “NUnitFixture”.
- The test methods are prefixed with “test” – as per PyUnit. And take only a “self” argument.
Here’s an example fixture:
import System
from System import Console
class MyPythonFixture(NUnitFixture):
def testPass(self):
Console.WriteLine("--testPass--")
Assert.IsTrue(True)
def testFail(self):
Console.WriteLine("--testFail--")
Assert.IsTrue(False, "this will fail")
As you can see we are just using the existing NUnit Assert methods…
There is one caveat to this that I will cover in another post (that of
testing for expected exceptions).
Next we create a suite – which can wrap one or more python script
files (included as assembly resources) – this is all we need to bridge
the gap.
public class MyPythonSuite : AbstractPythonSuite
{
public MyPythonSuite()
: base("MyPythonFixture.py")
{
}
}
And last of all, we need to drop in a suite builder – in theory I
should be able to include this in a support assembly – but it doesn’t
seem to work, here’s the code:
[SuiteBuilder]
public class PythonSuiteExtensionBuilder : ISuiteBuilder
{
public TestSuite BuildFrom(Type type, int assemblyKey)
{
if (CanBuildFrom(type)) return new PythonSuiteExtension(type, assemblyKey);
return null;
}
public bool CanBuildFrom(Type type)
{
return (typeof(AbstractPythonSuite).IsAssignableFrom(type) && (type != typeof(AbstractPythonSuite)));
}
}
At this point when can test the assembly with NUnitGui, and see our python fixtures appear under the “MyPythonSuite”… pretty slick huh?
The big downsides of this approach are that you can’t run the tests
individually using something like TestDriven.Net, and Resharper’s
unit test window doesn’t pick them up… However NUnitGui and the
corresponding NUnitConsole have no problems with them, and running all
the tests in your assembly with TestDriven.Net does pick them up
thankfully – so as long you don’t have many slow tests in your assembly
it should still be tolerable (you can run them while still coding...).
And of course, they will be picked up in your automated builds –
which is great news for me... I hate messing around with
CruiseControl.Net's configuration.
I’ll tidy up the implementation a little and post the code soon.
 Wednesday, October 04, 2006
Well, Seo Sanghyeon asked me a pretty reasonable question of the last entry
"Why don't you use help(), instead of printing Overloads?"
Good question, I didn't realise that actually worked in IronPython for
.Net types, I was very pleasantly surprised, I remember in the early
betas i tried using help() on various managed types and it would just raise a
NotImplementedException exception (or something like that, I forget
now) - so what does that mean for my examples... well... let's take a
system type like Guid and use help() on it.
IronPython 1.0.60816 on .NET 2.0.50727.42
Copyright (c) Microsoft Corporation. All rights reserved.
>>> import System
>>> from System import *
>>> help(Guid)
Help on Guid in module System in mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
| Guid(Array[Byte] b)
| Guid(UInt32 a, UInt16 b, UInt16
c, Byte d, Byte e, Byte f, Byte g, Byteh, Byte i, Byte j, Byte k)
| Guid(str g)
| Guid(int a, Int16 b, Int16 c, Array[Byte] d)
| Guid(int a, Int16 b, Int16 c,
Byte d, Byte e, Byte f, Byte g, Byte h, Byte i, Byte j, Byte k)
| Guid CreateInstance[Guid]()
|
| Data and other attributes defined here:
|
| CompareTo(...)
| int CompareTo(self, object value)
| int CompareTo(self, Guid value)
| Equals(...)
| bool Equals(self, object o)
| bool Equals(self, Guid g)
| Finalize(...)
| Finalize(self)
| GetHashCode(...)
| int GetHashCode(self)
| GetType(...)
| Type GetType(self)
| MemberwiseClone(...)
| object MemberwiseClone(self)
| NewGuid(...)
| Guid NewGuid()
| ToByteArray(...)
| Array[Byte] ToByteArray(self)
| ToString(...)
| str ToString(self)
| str ToString(self, str format)
|
str ToString(self, str format, IFormatProvider provider)
| __eq__(...)
| bool op_Equality(Guid a, Guid b)
| __init__(...)
|
x.__init__(...) initializes x; see x.__class__.__doc__ for signature
|
x.__init__(...) initializes x; see x.__class__.__doc__ for signature
| __ne__(...)
| bool op_Inequality(Guid a, Guid b)
| __new__(...)
| __new__(cls, Array[Byte] b)
|
__new__(cls, UInt32 a, UInt16 b, UInt16 c, Byte d, Byte e, Bytef, Byte
g, Byte h, Byte i, Byte j, Byte k)
| __new__(cls, str g)
|
__new__(cls, int a, Int16 b, Int16 c, Array[Byte] d)
|
__new__(cls, int a, Int16 b, Int16 c, Byte d, Byte e, Byte f, Byte g,
Byte h, Byte i, Byte j, Byte k)
| Guid CreateInstance[Guid]()
|
>>>
Sweet, it works a treat - and will save a lot of digging time - though you might want to capture the results for big classes ;o)
Thanks Seo!
(btw, Seo is the man behind the Iron Python Community Edition & FePy projects - which can be found on sourceforge) and I would have to agree with this article that he does seem almost OmniPresent in the IronPython community...
This entry was going to cover thinking about your .Net
classes from the perspective of Python consumption… but actually it's
not, we'll cover that another time. What this post is really
going to be about is how to spelunk a little into our managed classes
via the IronPython interactive console - and to see how some common
practices in .Net have interesting results in python. This
entry is going to be a little slow paced, I’m making the assumption
you’re a .Net developer, and haven’t cut Python code before, if you
know your way around Python probably best to pass this one over. Also worth noting at this point that I'm really just a Python hacker, and certainly no guru... So
first off, we have a class with a bunch of overloads for various
methods, here is the interface… I’ve left the code for the class itself
out, but the class is called MyFileStore.
public interface IMyFileStore { Guid AddFile(string fileName); Guid AddFile(string fileName, string mimeType); Guid AddFile(byte[] contents, string mimeType); Guid[] AddFiles(params string[] fileNames); Guid AddFile(byte[] contents); byte[] ReadFile(Guid id, out string mimeType); void ReadFile(Guid id, string fileName); void ExtractFiles(ref IList<byte[]> myList); } Pretty simple, now lets look at using this class with Iron python, I’ll be running with the ipy
(the interactive interpreter) so we can investigate our types as we go
along (this is an executable which comes with the Iron Python
binaries). The ipy console has a prompt consisting of three
greater then signs “>>>” so you’ll know what I’m typing into
the console by looking for those. First off, we need to load our type… this is pretty trivial… >>> import clr >>> clr.AddReferenceToFileAndPath("D:\\Demo\\SampleIronPythonLib.dll") >>> from IronPythonLib import * All
done, lets make sure our class is in there, using the inbuilt function
dir() – a list of built-in functions can be found here: http://www.python.net/quick-ref1_52.html#BuiltIn >>> dir() ['IMyFileStore', 'MyFileStore', '_', '__builtins__', '__doc__', '__name__', 'clr', 'results', 'site', 'store', 'sys'] MyFileStore, sweet. First off, lets create an instance of my type (not yours ;o) >>> store = new MyFileStore() Traceback (most recent call last): SyntaxError: unexpected token MyFileStore (<stdin>, line 1) >>> Arse, python doesn’t have a new operator… forgot that, lets try it again. >>> store = MyFileStore Right,
looks good, interpreter didn’t return any errors… but wait, lets make
sure we got what we want, which is an instance of MyFileStore…. >>> store <type ‘MyFileStore’> >>> Woops, that’s not what we wanted, we want an instance, not the type… lets try again: >>> store = MyFileStore() >>> store <MyFileStore object at 0x0000000000000035> >>> That’s better, lets find out what our store is capable off, using the dir command. >>> dir(store) ['AddFile',
'AddFiles', 'Equals', 'ExtractFiles', 'Finalize', 'GetHashCode',
'GetType', 'MakeDynamicType', 'MemberwiseClone', 'ReadFile', 'Reduce',
'ReferenceEquals', 'ToString', '__class__', '__doc__', '__init__',
'__module__', '__new__','__reduce__', '__reduce_ex__', '__repr__'] >>> We
see our AddFile, AddFiles,ReadFile & ExtractFiles methods –
everything looks to be in order… but what are the parameters for those
methods? >>> dir(store.AddFile) ['Call', 'CallInstance', 'Equals', 'Finalize', 'GetHashCode', 'GetTargetType', ' GetType',
'Make', 'MakeDelegate', 'MakeDynamicType', 'MemberwiseClone',
'Overloads', 'PrependInstance', 'Reduce', 'ReferenceEquals',
'ToString', '__call__', '__class__', '__doc__', '__eq__',
'__getitem__', '__hash__', '__init__', '__module__', '__name__',
'__new__', '__reduce__', '__reduce_ex__', '__repr__', '__self__',
'__str__'] >>> Hmm… what’s the “Overloads” thing, lets find out… >>> dir(store.AddFile.Overloads) ['Equals',
'Finalize', 'Function', 'GetHashCode', 'GetOverload',
'GetTargetFunction', 'GetType', 'MakeDynamicType', 'MemberwiseClone',
'Reduce', 'ReferenceEquals', 'Targets', 'ToString', '__class__',
'__doc__', '__getitem__', '__init__', '__module__', '__new__',
'__reduce__', '__reduce_ex__', '__repr__', '__str__'] >>> That doesn’t seem very useful, though what if we check it’s string representation? >>> store.AddFile.Overloads <IronPython.Runtime.Calls.BuiltinFunctionOverloadMapper
object at 0x0000000000000036 [{'Guid AddFile(self, str fileName)':
<built-in function AddFile>, 'Guid AddFile(self, str fileName,
str mimeType)': <built-in function AddFile>, 'Guid AddFile(self,
Array[Byte] contents, str mimeType)': <built-in function
AddFile>, 'Guid AddFile(self, Array[Byte] contents)': <built-in
function AddFile>}]> >>> Look at that, there are our 4 overloads – but it’s a bit jumbled, what am I looking at it…. Well
at first glance it looks a bit like a python dictionary… notice the
colon separating the signature for the method and <built-in function
AddFile> … what if we try to grab one for use later, maybe for some
functional programming, why don’t we get the one which takes the
fileName and mimeType parameters eh? >>> myFunc = store.AddFile.Overloads[str,str] Traceback (most recent call last): File , line 0, in <stdin>##100 TypeError: __getitem__() takes exactly 1 argument (1 given) >>> Bugger,
that didn’t work… still, not a bad guess - one parameter eh? Lets have
another stab, maybe it just wants us to group the types together
somehow? >>> myFunc = store.AddFile.Overloads[(str,str)] >>> myFunc <built-in method AddFile of MyFileStore object at 0x0000000000000035> >>> Looks better, of course if we wanted to we could use our function right now… let’s give it a whirl: >>> myFunc("d:\\testinput.3gp", "video/3gpp") <System.Guid object at 0x0000000000000037 [7b6d6dc5-bbe6-4831-bff7-21984031684f]> >>> Looks
to have worked, the method has successfully returned us a Guid
identifying the new file… shame we didn’t save it into a variable for
future reference, cest la vie. Luckily I can just copy and paste it – lets see if I can get the file back using this overload… byte[] ReadFile(Guid id, out string mimeType); Simple as.. lets try… >>> id = Guid("7b6d6dc5-bbe6-4831-bff7-21984031684f") Traceback (most recent call last): File , line 0, in <stdin>##116 NameError: name 'Guid' not defined >>> Woops, we need to import the System namespace… >>> import System Now lets try again… >>> id = Guid("7b6d6dc5-bbe6-4831-bff7-21984031684f") Traceback (most recent call last): File , line 0, in <stdin>##116 NameError: name 'Guid' not defined >>> Huh… oh wait, let’s check our local symbol table: >>> dir() ['MyFileStore',
'System', '_', '__builtins__', '__doc__', '__name__', 'clr', 'myArray',
'myFunc', 'results', 'site', 'store', 'sys'] System eh? Lets dig into it… >>> dir(System) ['AccessViolationException', 'Action', 'ActivationContext', 'Activator', 'AppDom ain', 'AppDomainInitializer', 'AppDomainManager', 'AppDomainManagerInitializatio nOptions', 'AppDomainSetup', 'AppDomainUnloadedException', 'ApplicationException… and all the rest. >> Well,
what can we do about that… we could import them all using “from System
import *” – but we only need Guid, so why don’t we just grab that eh? >>> from System import Guid >>> dir() ['Guid',
'MyFileStore', 'System', '_', '__builtins__', '__doc__', '__name__',
'clr', 'myArray', 'myFunc', 'results', 'site', 'store', 'sys'] >>> Sweet, now lets try it again… finally! >>> id = Guid("7b6d6dc5-bbe6-4831-bff7-21984031684f") >>> mimeType = "" >>> contents = store.ReadFile(id, mimeType) Traceback (most recent call last): File , line 0, in <stdin>##136 File , line 0, in ReadFile##66 File D:\dev\Projects\IronPythonDemo\IronPythonLib\MyClass.cs, line 70, in Read File File mscorlib, line unknown, in WriteAllBytes File mscorlib, line unknown, in .ctor File mscorlib, line unknown, in Init ValueError: Empty path name is not legal. >>> Oh
no! what went wrong??… what’s this about an empty path name, I was
expecting to get some bytes back…. Hmmm, don’t panic, lets just do some
digging… >>> store.ReadFile.Overloads <IronPython.Runtime.Calls.BuiltinFunctionOverloadMapper
object at 0x0000000000000039 [{'(Array[Byte], str) ReadFile(self, Guid
id)': <built-in function ReadFile>, 'ReadFile(self, Guid id, str
fileName)': <built-in function ReadFile>}]> >>> Well
there’s the problem, I’ve called the wrong version, but… that’s odd,
there’s a ReadFile there I haven’t defined in my class… and one that’s
missing… hmmm, time to file a bug report. Or not, this is just where the Python and .Net world don’t see eye to eye – Python doesn’t know about out
parameters as such, so it simulates the effect by altering the method’s
signature – lets have a go at playing by IronPython's rules: >>> contentsAndMimeType = store.ReadFile(id) >>> len(contentsAndMimeType) 2 >>> type(contentsAndMimeType[0]) <type 'Array[Byte]'> >>> type(contentsAndMimeType[1]) <type 'str'> >>> contentsAndMimeType[1] 'video/3gpp' >>> So our “out string mimeType” parameter was returned by the method instead, as the second item in an array… hmm… interesting. Well
if python does this to out parameters, what does it do to ref
parameters – lets try the extract files method to see what happens…
this method is implemented to create a new list if we don’t supply a
valid instance, but lets pass in a valid one first – I wont be caught
out this time, so I’ll import the types I need first (List<T>): >>> from System.Collections.Generic import * >>> bytes = List<byte[]>() Traceback (most recent call last): SyntaxError: unexpected token ] (<stdin>, line 1) >>> Oh dear, looks like Python doesn’t use the <T> syntax… lets start digging again… >>> dir(List) ['Add', 'AddRange', 'AsReadOnly', 'BinarySearch', 'Capacity', 'Clear', 'Contains ', 'ConvertAll', 'CopyTo', 'Count', 'Enumerator', 'Equals', 'Exists', 'Finalize' , 'Find', 'FindAll', 'FindIndex', 'FindLast', 'FindLastIndex', 'ForEach', 'GetEn umerator',
'GetHashCode', 'GetRange', 'GetType', 'IndexOf', 'Insert',
'InsertRange', 'LastIndexOf', 'MakeDynamicType', 'MemberwiseClone',
'Reduce', 'ReferenceEquals', 'Remove', 'RemoveAll', 'RemoveAt',
'RemoveRange', 'Reverse', 'Sort', 'ToArray', 'ToString', 'TrimExcess',
'TrueForAll', '__class__', '__doc__', '__getitem__', '__init__',
'__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__setitem__'] >>> List <type 'List[T]'> >>> Hmm… nothing obvious there, but we could hazard a guess… >>> List[str] <type 'List[str]'> >>> Hmmm… so lets give our List of byte array another go… >>> bytes = List[byte[]]() Traceback (most recent call last): SyntaxError: unexpected token ] (<stdin>, line 1) Hmm… it doesn’t like byte, lets have a look… >>> byte Traceback (most recent call last): File , line 0, in <stdin>##56 NameError: name 'byte' not defined >>> Byte <type 'Byte'> >>> Ahhh…
I forgot that “byte” (lowercase) isn’t actually what the Clr calls it…
hmm… we could try it again, but it wont work – simply because Byte[]
doesn’t make sense (if we’re using square brackets to index into a
generic type… ) so having another dig in the IronPython docs we see
that we need to index to the type “Array” with the type of our array,
given this knowledge we can get what we want… >>> bytes = List[Array[Byte]]() >>> bytes <List[Array[Byte]] object at 0x000000000000002C> >>> Sweet, now to call that ExtractFiles method… >>> store.ExtractFiles(bytes) <List[Array[Byte]] object at 0x000000000000002C> >>> bytes <List[Array[Byte]] object at 0x000000000000002C> >>> len(bytes) 8 Well
it returned a list… but it did accept our reference parameter, and it
has updated the collection we passed to it… but what if we had passed
it a null instead? >>> bytes = None >>> store.ExtractFiles(bytes) <List[Array[Byte]] object at 0x0000000000000030> >>> len(bytes) Traceback (most recent call last): File , line 0, in <stdin>##83 File , line 0, in Length##75 TypeError: len() of unsized object of type <type 'NoneType'> >>> Hmmm… it returned a list, but it didn’t set the value in our variable "bytes" – this isn’t really ref like behavior… yet, if you think about it we can get almost the same thing by doing this: >>> bytes = store.ExtractFiles(bytes) >>> len(bytes) 8 >>> Right, our journey has almost come to an end… looking at the interface we have a method declared like so: Guid[] AddFiles(params string[] fileNames); params
is handy in .Net code – anything to save tedious typing of array
declarations, lets see if we can do the same thing in python? >>> store.AddFiles("d:\\testinput1.3gp", "d:\\testinput2.3gp") System.Guid[](<System.Guid
object at 0x0000000000000031
[d3e8f099-3005-461c-a63a-fdc69b2091ee]>, <System.Guid object at
0x0000000000000032 [d9d1fb25-fc7a-47a2-89ac-c11264bfa47f]>) >>> Sweet, that’s a pleasant surprise after that whole out and ref debacle, I was beginning to loosing hope! But wait, why not revive our faith in Python a little more by looking at a few tricks… >>> paramsDict = { "fileName" : "d:\\testinput.3gp", "mimeType" : "video/3gpp"} >>> store.AddFile(**paramsDict) <System.Guid object at 0x0000000000000033 [e07680d9-f544-4847-9a1e-d04a0ef137f7]> >>> What
did we just do? Well… given a dictionary, where the keys are the
parameter names, we used them as the parameters for one of our .Net
methods using the double asterisk syntax – think of what you’d
have to do to code this in .Net… reflection city ;o) It doesn’t just have to be dictionaries… we can use arrays too… >>> paramsArray = [ "d:\\testinput.3gp", "video/3gpp" ] >>> store.AddFile(*paramsArray) <System.Guid object at 0x0000000000000034 [85006c43-e6f5-4e90-b837-0868788cf453]> >>> Neat,
of course, being a dynamic language and all, the selection of the
appropriate overload is based on the types in the array or dictionary…
it would be a nightmare to do both of these in .Net with reflection
yourself. Faith still not restored eh, what about creating a
python class that wraps an existing (possibly sealed) .Net class, that
forwards calls on unless we want to override the behavior… >>> class MyFileStoreWrapper: ... def __init__(self, realStore): ... self.realStore = realStore ... def ExtractFiles(self, myList): ... raise Exception, "this method isn't allowed" ... def __getattr__(self, name): ... if name == "ExtractFiles": return ExtractFiles ... return eval("self.realStore."+name) ... >>> store = MyFileStoreWrapper(MyFileStore()) >>> store.AddFile("d:\\testinput.3gp") <System.Guid object at 0x000000000000002C [a2bf8747-cc71-4b79-8c02-0b61a87ff67f] > >>> store.ExtractFiles(None) Traceback (most recent call last): File , line 0, in <stdin>##52 File , line 0, in ExtractFiles Exception: this method isn't allowed >>> ExtractFiles
can’t be invoked on our wrapper, however our wrapper automagically
responds for other methods like AddFile – sadly I’ve used an eval (which,
rhymes with Evil - coincidence?) here because MyFileStore doesn’t
expose a __getattr__ method (because it's a .Net classes, not a
native Python one). The nice thing now, is that given a native
python class, we can do some things we aren’t allowed to do to the
underlying .Net class – like adding new methods at run time. >>> def ExtractMoreFiles(self, myList): ... raise Exception, 'not implemented yet' ... >>> MyFileStoreWrapper.ExtractMoreFiles = ExtractMoreFiles >>> store.ExtractMoreFiles(None) Traceback (most recent call last): File , line 0, in <stdin>##60 File , line 0, in ExtractMoreFiles Exception: not implemented yet >>> I
think it’s pretty cool, and though python's metaprogramming model isn’t
quite up to ruby standards, it’s still pretty easy to dig down and
create some pleasantly surprising results :) and if you have your
thinking caps on you can probably see how this stuff would really help
to bring a DSL to life for your applications special needs. And
that’s where I’m going to conclude this rambling post…
;o) Hopefully it'll spark some thoughts about what's possible with
IronPython and allow you avoid some simple mistakes when using .Net
classes in iron python.
 Tuesday, October 03, 2006
Second Alpha
I released the second alpha of the Splicer library last night… you can grab it from the Splicer Codeplex site
Key changes are:
- It’s been FxCop’d – which in turn has seen some
naming issues, misspellings, security issues, incorrect disposal
implementations etc being fixed.
- Implemented some features:
- Support for adding System.Drawing.Image’s to a track (using overloads of the AddImage(…) methods.
- Support for shadow copying input files –
this will take a copy of a clip file, and use that in the ITimeline,
disposing of the copy when the clip is disposed – this is especially
useful when dealing with source filters which don’t like reading the
same file simultaneously.
- AbstractProgressParticipant class which
can be used to generate your own progress participants, 0 or more
participants can be registered for each group. This allows you to
do things like perform screen captures on the output of the timeline.
- Windows media renderer will now notify you when the selected rendering profile is unsuitable for your timeline.
- Fixed some bugs
- The clock is now correctly disabled for the
filter graph when rendering to a file. This will speed up
rendering in many scenarios.
- When supplying an audio encoder for either WAV
or AVI output, the format settings for the encoder were not being
applied properly (was using defaults, instead of setting the number of
channels, khz & kbps).
Though
there are also plenty more, I doubt anyone has actually been using this
library
so far (as it's really only useful in a rather select set of circumstances) -
however just to offer some vague hope of encouragement I’ll include
some code
samples to get you started – as there is no documentation short of the
150 odd
unit tests included with the project... and won't be for some time.
Example 1
First off, lets imagine you have some web 2.0 website, and you
encourage people to upload audio comments, instead of text, yet you don’t know what
format they’re going to provide, and you want to keep things standard
(say by outputting a certain windows media audio format) that eases pressure on your web server.
using (ITimeline timeline = new DefaultTimeline())
{
IGroup audioGroup = timeline.AddAudioGroup();
ITrack rootTrack = audioGroup.AddTrack();
rootTrack.AddAudio("testinput.mp3");
using (
WindowsMediaRenderer renderer =
new WindowsMediaRenderer(timeline, "output.wma", WindowsMediaProfiles.LowQualityAudio))
{
renderer.Render();
}
}
Here we are creating a Timeline, this is a container for all the audio
and video compositing we are doing, next we create a group for our
audio, a group is a top level composition… we then add a track to our
audio group, which is a container for our audio clip we wish to add (in
this case it’s an mp3, but it could be any audio format that you have a
codec installed for).
Once your Timeline is ready to be rendered, we create a renderer, in
this case it’s a windows media renderer, which takes your timeline, the
name of the output file and the windows media profile we wish to use –
profiles are xml strings containing the settings for the windows media
encoder – A couple of bundled ones are included, though you would
probably want to create your own using the “Windows Media Profile
Editor” which is included with Windows Media Encoder 9.
Last of all we invoke the Renderer, generally rendering can take a
little while, depening on the size of the content, and the complexity
of the encoding process - so I would suggest invoking the Renderer
asynchronously
(all Renderers support the BeginRender(…) … EndRender(…) methods which
should make this trivial). Our examples will use the blocking
Render() method, just because it's more concise.
Example 2
So audio cool, what about video?
Much the same:
using (ITimeline timeline = new DefaultTimeline())
{
timeline.AddAudioGroup().AddTrack();
timeline.AddVideoGroup(24, 320, 240).AddTrack();
timeline.AddVideoWithAudio("input.avi");
using (
WindowsMediaRenderer renderer =
new WindowsMediaRenderer(timeline, "output.wmv", WindowsMediaProfiles.LowQualityVideo))
{
renderer.Render();
}
}
This time we create two groups, and two tracks, one of each for the
audio from our source file, and another of each for the video.
Note we’ve specified the format of the video a little – so it will be 24 bits per pixel, and 320 high by 240 wide.
In this case we use a helpful method on the timeline itself which will
Add a video clip to the video group and an audio clip to the audio
group at the same time, called AddVideoWithAudio… Again we render it to
windows media format, but this time we use a profile which has settings
for both audio and video.
The bits per pixel becomes important when using something transitions
or effects, as you need an alpha channel for them to work - this is
generally achieved but just bumping the BPP from 24 to 32.
Example 3
Right, moving on from here, lets cover some more interesting ideas – first off, lets build a slide show video…
using (ITimeline timeline = new DefaultTimeline(25))
{
IGroup group = timeline.AddVideoGroup(32, 160, 100);
ITrack videoTrack = group.AddTrack();
IClip clip1 = videoTrack.AddImage("image1.jpg", 0, 2);
IClip clip2 = videoTrack.AddImage("image2.jpg", 0, 2);
IClip clip3 = videoTrack.AddImage("image3.jpg", 0, 2);
IClip clip4 = videoTrack.AddImage("image4.jpg", 0, 2);
double halfDuration = 0.5;
group.AddTransition(clip2.Offset - halfDuration, halfDuration, StandardTransitions.CreateFade(), true);
group.AddTransition(clip2.Offset, halfDuration, StandardTransitions.CreateFade(), false);
group.AddTransition(clip3.Offset - halfDuration, halfDuration, StandardTransitions.CreateFade(), true);
group.AddTransition(clip3.Offset, halfDuration, StandardTransitions.CreateFade(), false);
group.AddTransition(clip4.Offset - halfDuration, halfDuration, StandardTransitions.CreateFade(), true);
group.AddTransition(clip4.Offset, halfDuration, StandardTransitions.CreateFade(), false);
ITrack audioTrack = timeline.AddAudioGroup().AddTrack();
IClip audio =
audioTrack.AddAudio("soundtrack.wav", 0, videoTrack.Duration);
audioTrack.AddEffect(0, audio.Duration,
StandardEffects.CreateAudioEnvelope(1.0, 1.0, 1.0, audio.Duration));
using (
WindowsMediaRenderer renderer =
new WindowsMediaRenderer(timeline, "output.wmv", WindowsMediaProfiles.HighQualityVideo))
{
renderer.Render();
}
}
In this case we are producing a slideshow – Note that
25 in the constructor of the timline, this is setting the frames per
second, in this case we are saying 25fps… The renderer has the
final say in the “rendered” frames per second, based on it’s settings…
but some renderers will use the frame rate they receive, such as the
windows renderer (which renders the video into a window on-screen)
The slideshow fades between 4 still images, each lasts for a duration
of 2 seconds, we add in a couple of transitions, fading out of one clip
and into another – last of all we are using an AudioMixer effect on the
audio track to create an Audio envelope, which means it will take one
second for the audio to fade in from 0.0 volume to 1.0 volume (0 to
100%) and then play until one second before the end of the clip, at
which point it will fade out from 100% volume to 0% volume.
Example 4
Last of all, you can capture rendered frames from a video, this might
be handy to display on a web page, with links to the various quality
formats beneath – you can grab these frames during encoding, or you can
render to “null”, which allows you the grab frames, but wont produce
any output video or audio file… Lets take a look at the second idea:
using (DefaultTimeline timeline = new DefaultTimeline())
{
timeline.AddVideoGroup(24, 320, 240).AddTrack(); // we want 320x240 24bpp sized images
timeline.AddVideo("transitions.wmv"); // 8 second video clip
ImagesToDiskParticipant participant = new ImagesToDiskParticipant(24, 320, 240, Environment.CurrentDirectory, 1, 2, 3, 4, 5, 6, 7);
using (NullRenderer render = new NullRenderer(timeline, null, new ICallbackParticipant[] { participant }))
{
render.Render();
}
}
Here we add a video group (we don’t need any audio, we’re not capturing
sound samples) and then create an ImageToDiskParticipant (this is just
an example class, you would probably want to implement your own derived
from the AbstractProgressParticipant class). In this case the
format for the video group will be what you end up with, our
ImagesToDiskParticipant implements a simple queue where it picks out
the nearest frame to the specified times.. so in this case
transitions.wmv is an 8 second video clip, and we will take frame "snapshots" at 1,2,3,4,5,6 and 7 seconds aproximately.
To make use of our participant we use one of the overloads of
NullRenderer, and supply it a single-element array containing the image
to disk participant.
The end result is 7 jpeg images on disk (frame0.jpg -> frame6.jpg)
Hopefully this has given you some ideas about how Splicer can be used
for dealing width audio and video, next time I do an alpha drop I might include some samples
which use different container formats (WAV & AVI) - or maybe I'll
use splicer as an example of just how not to write an API you wish to
expose to IronPython due to it's bloated number of overloads.
 Sunday, October 01, 2006
[Test]
public void ExtendingClass()
{
PythonEngine engine = new PythonEngine();
engine.Import("clr");
engine.LoadAssembly(GetType().Assembly);
engine.ExecuteToConsole("from IronPythonLib.Tests import *");
engine.Execute(@"class MyTransformer(IStringTransformer):
def Transform(self, input):
return input + "" is now transformed""");
IStringTransformer transformer = engine.EvaluateAs<IStringTransformer>("MyTransformer()");
Assert.AreEqual("input is now transformed", transformer.Transform("input"));
}
hmmm?
edit: seems better now, swapped to a simpler layout though I
doub't it will work well for people with lower resolutions... most
people will (hopefully) read this via RSS at any rate.
Back once more...
I decided to spend ten minutes implementing a
version of the last post which didn't require us to generate a function
definition for a python method with a generic number of parameters... it's actually not too hard.
First off, the key to solving this problem is to generate a Type for
our custom delegate - for performance reasons you would want
to cache the results, but we won't do that here... Also I think it would
be more apropriate for the first 0->9 parameters that we just return
a generic Func delegate (generic Func & Proc delegates are going to
be part of .Net 3.0, as mentioned here - At the moment I use something similar from the RhinoCommons library, have a look here)
So, first off we build a helper method for generating our delegate - just heavy use of emit here, based off an example on MSDN:
public Type CreateCustomDelegate(Type returnType, params Type[] parameterTypes)
{
AssemblyName assembly = new AssemblyName();
assembly.Name = "CreateCustomDelegateAssembly";
AssemblyBuilder assemblyBuilder =
AppDomain.CurrentDomain.DefineDynamicAssembly(assembly, AssemblyBuilderAccess.RunAndSave);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("TempModule");
TypeBuilder typeBuilder =
moduleBuilder.DefineType("TempDelegateType",
TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class |
TypeAttributes.AnsiClass | TypeAttributes.AutoClass, typeof (MulticastDelegate));
ConstructorBuilder constructorBuilder =
typeBuilder.DefineConstructor(
MethodAttributes.RTSpecialName | MethodAttributes.HideBySig | MethodAttributes.Public,
CallingConventions.Standard, new Type[] {typeof (object), typeof (int)});
constructorBuilder.SetImplementationFlags(MethodImplAttributes.Runtime | MethodImplAttributes.Managed);
MethodBuilder methodBuilder =
typeBuilder.DefineMethod("Invoke",
MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot |
MethodAttributes.Virtual, returnType, parameterTypes);
methodBuilder.SetImplementationFlags(MethodImplAttributes.Managed | MethodImplAttributes.Runtime);
return typeBuilder.CreateType();
}
Basically it goes through the motions of building a delegate type, derived from MulticastDelegate with our selected output type and parameter types...
Now to put that type to use:
[Test]
public void WithCustomDelegate()
{
Dictionary<string, object> context = new Dictionary<string, object>();
context.Add("user", "alex");
context.Add("age", 26);
Proc testCore = delegate
{
List<string> parameters = new List<string>(context.Keys);
List<object> values = new List<object>(context.Values);
PythonEngine engine = new PythonEngine();
Type[] parameterTypes = new Type[context.Count];
for (int i = 0; i < parameterTypes.Length; i++) parameterTypes[i] = typeof (object);
Type delegateType = CreateCustomDelegate(typeof (object), parameterTypes);
Type pythonEngine = typeof (PythonEngine);
MethodInfo genericMethod =
pythonEngine.GetMethod("CreateMethod", new Type[] {typeof (string), typeof (IList<string>)});
MethodInfo method = genericMethod.MakeGenericMethod(delegateType);
object result =
method.Invoke(engine,
new object[] {"return user + ' is ' + str(age) + ' years old'", parameters});
Delegate myDelegate = (Delegate) result;
Assert.AreEqual("alex is 26 years old", myDelegate.DynamicInvoke(values.ToArray()));
};
// try it with our two keys in the dictionary
testCore();
// try it with another key in the dictionary
context.Add("monkey_colour", "brown");
testCore();
}
Our test creates our custom delegate type for the number of parameters we have, and then we use that when invoking the PythonEngine.CreateMethod<TDelegate>(string statements, IList<string> parameters) method.
Here we have to do a little reflection, simply because we cant directly
use a local variable of type "Type" as a generic type parameter, so we
grab the generic method, set the parameter and then invoke it.
However in the end, as you can see we get a real Delegate, which we can use DynamicInvoke upon.
This is actually pretty handy, and it's certainly a lot more
script-friendly, compared to the alternative of forcing users to look
up values from context by index ie.
context['user'] + ' is ' + str(context['age']) + ' years old'
vs.
user + ' is ' + str(age) + ' years old'
I know what I prefer ;o)
 Saturday, September 30, 2006
Part 2 - More IronPython and delegates…
So we have briefly covererd strongly typed delegates and event
handlers... But they make the assumption that we know what our
arguments are at compile time, what if we dont?
Say for instance you're expression engine has a bag of contextual values being passed around in a dictionary, like this:
Dictionary<string, object> context = new Dictionary<string, object>();
context.Add("user", "alex");
context.Add("age", 26);
What if we want to use python to evaluate expressions against that
context?... Say something like writing out “my name is alex and my age
is 25" - the expression in python is easy enough, we can go:
‘my name is’ + name + ‘ and my age is ‘ + str(age)
But how do we marry all these together... lets start exploring... ever noticed that delegates have a DynamicInvoke method with a signature like this?
object
DynamicInvoke(params object[] parameters)
Perhaps we can try and use this to our advantage... lets see shall we?
First Attempt
[Test]
[ExpectedException(typeof (ArgumentException), "T must be a concrete delegate, not MulticastDelegate or Delegate")]
public void LooseDelegateAndDynamicInvoke()
{
PythonEngine engine = new PythonEngine();
List<string> parameters = new List<string>(new string[] {"name", "age"});
Delegate func =
engine.CreateMethod<Delegate>("return ‘my name is’ + name + ‘ and my age is ‘ + str(age)", parameters);
string result = (string) func.DynamicInvoke("alex", 26);
Assert.AreEqual("my name is alex and my age is 25", result);
}
Sadly this doesn’t work so well, seems the PythonEngine is looking for
a concrete delegate... we could try giving it void delegate,
but it does type checking on the number of parameters and their type,
so we're a little stuck.
At this point I think the best answer is to actually build some code
which generates the apropriate delegate type, based on the supplied
dictionary, and then passing that onto the python engine, but there is
another way… it’s a little less elegant, but it’s at least amusing:
Round 2
First of let’s build a little helper method...
private static string GenerateFunction(string functionName, string[] parameters, string statements)
{
StringBuilder builder = new StringBuilder();
builder.AppendFormat("def {0}(", functionName);
for (int i = 0; i < parameters.Length; i++)
{
if (i > 0) builder.AppendFormat(", ");
builder.AppendFormat(parameters[i]);
}
builder.AppendFormat("):\r\n ");
builder.AppendFormat(statements.Replace("\r\n", "\n").Replace("\r", "\n").Replace("\n", "\r\n "));
return builder.ToString();
}
You can no doubt see where this is going – so given a statement like this:
GenerateFunction("func", new string[] { "user", "age" }, "return user + ' is ' + str(age) + ' years old'")
We’d end up with a string like this:
def func(user, age):
return user + ' is ' + str(age) + ' years old'
And, moving on from there we build a test…
[Test]
public void GeneratingPythonFunction()
{
Dictionary<string, object> context = new Dictionary<string, object>();
context.Add("user", "alex");
|