export_factory_operation question

Asked by Edward F. Long Jr. on 2011-02-23

Hi:

How do i properly annotate a factory operation that returns a new instance of the interface where the factory operation is defined?

Below is an example of what i'm trying to do in a general way.

class IThing(ILocation):
    """Interface for Things."""
    export_as_webservice_entry()

    name = exported(Text(title=u'my name'))

    @rename_parameters_as(my_color='new_color')
    @operation_parameters(my_color=Text())
    @export_factory_operation(IThing, [])
    def create_by_color(my_color):
        """Create a new Thing based on color (yeah, that makes no sense ;)."""

If i did: @export_factory_operation(Interface, []), the code works, but the wadl docs fail to generate since Interface is not an entry.

It looks like i need to do something after-the-fact, something like this:
params = IThing['move'].queryTaggedValue(LAZR_WEBSERVICE_EXPORTED)
params['creates'] = IThing
IThing['create_by_color'].setTaggedValue(LAZR_WEBSERVICE_EXPORTED, params)
annotate_exported_methods(IThing)

I'm doing something wrong, I'm not sure what

Any assistance would be most appreciated.

Question information

Language:
English Edit question
Status:
Solved
For:
lazr.restful Edit question
Assignee:
No assignee Edit question
Solved by:
Leonard Richardson
Solved:
2011-03-09
Last query:
2011-03-09
Last reply:
2011-02-28
Curtis Hovey (sinzui) said : #1

I do not think operation_parameters is needed because the params come from export_factory_operation, which declares the interface and the list of field/params required.
The interface created by the factory must also be defined and have export_as_webservice_entry for that wadl to generate.

Using Launchapd's API as an example. The
    @call_with(owner=REQUEST_USER)
    @rename_parameters_as(releasefileglob="release_url_pattern")
    @export_factory_operation(
        IProductSeries, ['name', 'summary', 'branch', 'releasefileglob'])
    @export_operation_as('newSeries')
    def newSeries(owner, name, summary, branch=None, releasefileglob=None):

and a simpler example:
    @rename_parameters_as(dateexpected='date_targeted')
    @export_factory_operation(
        IMilestone, ['name', 'dateexpected', 'summary', 'code_name'])
    def newMilestone(name, dateexpected=None, summary=None, code_name=None):
        """Create a new milestone for this DistroSeries."""

Edward F. Long Jr. (edl) said : #2

Thanks for your response Curtis, but that's not where I'm having an issue:

What i want to do is: export a factory operation of IThing from inside the IThing interface (see line 4 below in my simplified class).
1: class IThing(ILocation)
2: export_as_webservice_entry()
3:
4: @export_factory_operation(IThing, [])
5: def create_by_color():
6: """the doc string"""

Python won't let you do this since you're referencing the class that you're inside of

In launchpad (and lazr's examples) they sometimes define stuff in the interface after the interface has been defined to clear up import order issues, so im looking for a way to do that for export_factory_operation

So... if I change line 4 to read:
4: @export_factory_operation(Interface, [])

When I run the code now, my factory operation works, because the return value of the call to 'create_by_color' returns an instance that implements the IThing interface, and every interface (including IThing) subclasses Interface

but if i attempt to generate the wadl docs for my webservice, i get an error:
AssertionError: No IEntry adapter found for Interface (web service version: 1.0).

So i was thinking what needs to be done is something similar to the following:
1: class IThing(ILocation)
2: export_as_webservice_entry()
3:
4: @export_factory_operation(Interface, [])
5: def create_by_color():
6: """the doc string"""
7:
8: params = IThing['create_by_color'].queryTaggedValue(LAZR_WEBSERVICE_EXPORTED)
9: params['creates'] = IThing
10: IThing['create_by_color'].setTaggedValue(LAZR_WEBSERVICE_EXPORTED, params)

Lines 8-10 seem to be doing something, since now, when i run:
IThing['create_by_color'].queryTaggedValue('lazr.restful.exported').items()

I get:
[('call_with', {}),
 ('creates', <InterfaceClass myclass.IThing>),
 ('as', 'create_by_color'),
 ... etc
]

If i run my code, the create_by_color method works correctly, but again, when i attempt to generate the wadl docs, i still get the error
AssertionError: No IEntry adapter found for Interface (web service version: 1.0).

Edward F. Long Jr. (edl) said : #3

on a slightly different note.

I can use @export_write_operation instead of @export_factory_operation

but using export_write_operation returns 200 OK with the representation, instead of 201 CREATED with the Location header

Best Leonard Richardson (leonardr) said : #4

In Launchpad we have some helper functions for this. They should probably be moved into lazr.restful.

Here's an example of a similar case in Launchpad:

    @call_with(registrant=REQUEST_USER)
    @export_factory_operation(Interface, []) # Really ICodeImport.
    def newCodeImport(registrant=None, branch_name=None, rcs_type=None,
                      url=None, cvs_root=None, cvs_module=None, owner=None):

Then, in another file, we call the helper method, patch_entry_return_type():

patch_entry_return_type(IHasCodeImports, 'newCodeImport', ICodeImport)

Here's the implementation of patch_entry_return_type():

def patch_entry_return_type(exported_class, method_name, return_type):
    """Update return type for a webservice method that returns entries.

    :param exported_class: The class containing the method.
    :param method_name: The method name that you need to patch.
    :param return_type: The new return type for the method.
    """
    exported_class[method_name].queryTaggedValue(
        LAZR_WEBSERVICE_EXPORTED)['return_type'].schema = return_type

Try that and see if it works for you.

Edward F. Long Jr. (edl) said : #5

Leonard...

Thanks for your help, thats exactly what I needed!

FWIW:
In SQLAlchemy, I frequently have to define relationships to other classes before those classes are mapped/defined, This is the same type of thing that I ran into with lazr.restful. SQLAlchemy allows you to do this by using the class name as a string instead of the class itself. It would be very useful to be able to do this in lazr.restful. (See line #16 below)

1 class IRoom(ILocation, IPublishTraverse):
2 """Interface for room"""
3 export_as_webservice_entry()
4 name = exported(Text(title=u'Name'))
5
6 class IBox(ILocation, IPublishTraverse):
7 """Interface for Box."""
8 export_as_webservice_entry()
9 length = exported(Int(title=u'L'))
10 width = exported(Int(title=u'W'))
11 height = exported(Int(title=u'H'))
12 room = exported(Reference(schema=IRoom, title=u'room where box is located'))
13
14 @rename_parameters_as(room='room_link')
15 @operation_parameters(room=Reference(schema=IRoom))
16 @export_factory_operation('IBox', [])
17 def move(room):
18 """move a box to a different room""""
19 pass

After all the classes are defined, but before the EntryAdapters are created, we'd need to translate the strings to classes.

But all things being equal, the solution that you posted solves our issue. Thanks

Edward F. Long Jr. (edl) said : #6

Thanks Leonard Richardson, that solved my question.