Python code using XML-RPC to create a node and upload/attach a file.

Events happening in the community are now at Drupal community events on www.drupal.org.
pdumais42's picture

I've searched the web looking for something more than a "create a node" example for Drupal Services. There are some good examples for Flash/Flex and some bits and pieces in Python or PHP but nothing that helps me to figure out what I am doing wrong.

Since my client is planning to develop a cross platform application that interacts with their Drupal site, I thought I would create a sample app in Python - just to show the Flash, iPhone, Droid, Blackberry developers how the Drupal Services API works.

I may be doing something dumb. I've been programming for over 20 years but I have never really done anything serious with Python. I have a really hard time mapping what I know in PHP onto Python.

But, since the code will be of use to others and I really want to figure this out, I am posting it here and asking for help with one area of the sample app. I can't figure out how to attach the file upload to the node. I think it's because I am not passing the data correctly. Has anyone made this work when calling the XML-RPC server?

#! /usr/bin/python

import os.path, sys, time, mimetypes, xmlrpclib, pprint, base64
pp = pprint.PrettyPrinter()

config = {
  'url': 'http://example.com/services/xmlrpc',
  'username': 'developer',
  'password': 'bZdKNTv7XB8C',
}

# Make initial connection to service, then login as developer
server = xmlrpclib.Server(config['url'], allow_none=True);
connection = server.system.connect();
session = server.user.login(connection['sessid'], config['username'], config['password']);
sessid = session['sessid'];
user = session['user'];

timestamp = str(int(time.time()))

## Load a movie file - get size, mimetype
filename = 'testfile.MOV'
filesize = os.stat(filename).st_size
filemime = mimetypes.guess_type(filename)
fd = open(filename, 'rb')
video_file = fd.read()
fd.close()

# Create file_obj with encoded file data
file_obj = {
   'file': base64.b64encode(video_file),
    'filename': filename,
    'filepath': 'sites/default/files/' + filename,
'filesize': filesize,
    'timestamp': timestamp,
  'uid': user['uid'],
    'filemime': filemime,
}

# Save the file to the server
try:
  f = server.file.save(sessid, file_obj)

except xmlrpclib.Fault, err:
   print "A fault occurred"
print "Fault code: %d" % err.faultCode
   print "Fault string: %s" % err.faultString

else:
    pp.pprint(f) # DEBUG


# Get the new file from the server (verify)  DEBUG
try:
ff = server.file.get(sessid, f)

except xmlrpclib.Fault, err:
  print "A fault occurred"
print "Fault code: %d" % err.faultCode
   print "Fault string: %s" % err.faultString

else:
    # We have the new file info - we need this in case the name of the file was changed when sotred on the server
  # pp.pprint(ff) # DEBUG
   
   del ff['file'] # We don't need this - free it
  
   node_files = {
     'fid': f,
        'filemime': filemime,
        'filename': ff['filename'],
        'filepath': ff['filepath'],
        'filesize': filesize,
        'status': '1',
     'timestamp': timestamp,
      'uid': user['uid'],
    }
 
   # Create the node object and refernce the new fid just created
node = {
     'type': 'video_local',
     'status': 1,
     'type': 'video_local',
     'title': 'Remote Test ' + timestamp,
   'body': 'This is a test created from a remote app',
    'uid': user['uid'],
    'name': user['name'],
      'changed': timestamp,
    'files': {},
     'field_video_overlay' : [
      {'value': 'Here is some CCK field text'},
    ],
   }
 
   # my latest attempt to figure out how to get node->files in the right format for the node.save method
   # please don't laugh - I am just grasping at straws here - ive tried all the lists and dictionary formats.
    node['files'][f] = node_files
   
   pp.pprint(node) # DEBUG
   
   try:
       n = server.node.save(sessid, node)
     nn = server.node.get(sessid,int(n),[])

   except xmlrpclib.Fault, err:
       print "A fault occurred"
     print "Fault code: %d" % err.faultCode
       print "Fault string: %s" % err.faultString
  
   else:
      pp.pprint(n) # DEBUG
       pp.pprint(nn) # DEBUG

The code formatter makes it appear that some of the code is not indented correctly. See the attached file for working code

This code runs without errors - connects, logs in, uploads a file, gets the fid, creates a node - but in the end, the new node does not include the file attachment.

Does anyone know what I am doing wrong?

AttachmentSize
drupal_services_test.py_.txt2.82 KB

Comments

output from the pprint debug code

pdumais42's picture
'534'
{'body': 'This is a test created from a remote app',
 'changed': '1263608390',
 'field_video_overlay': [{'value': 'Here is some CCK field text'}],
 'files': {'534': {'fid': '534',
                   'filemime': ('video/quicktime', None),
                   'filename': 'testfile.MOV',
                   'filepath': 'sites/default/files/testfile.MOV',
                   'filesize': 893292L,
                   'status': '1',
                   'timestamp': '1263608390',
                   'uid': '9'}},
 'name': 'developer',
 'status': 1,
 'title': 'Remote Test 1263608390',
 'type': 'video_local',
 'uid': '9'}
'99'
{'body': 'This is a test created from a remote app',
 'changed': '1263608353',
 'comment': '0',
 'comment_count': '0',
 'created': '1263608353',
 'data': 'a:1:{s:13:"form_build_id";s:37:"form-b25e97816649fb6a7b49a7f115227c65";}',
 'field_video_attach': [''],
 'field_video_overlay': [{'value': 'Here is some CCK field text'}],
 'files': [],
 'format': '1',
 'language': '',
 'last_comment_name': '',
 'last_comment_timestamp': '1263608353',
 'log': '',
 'moderate': '0',
 'name': 'developer',
 'nid': '99',
 'picture': '',
 'promote': '1',
 'revision_timestamp': '1263608353',
 'revision_uid': '9',
 'status': '1',
 'sticky': '0',
 'taxonomy': [],
 'teaser': 'This is a test created from a remote app',
 'title': 'Remote Test 1263608390',
 'tnid': '0',
 'translate': '0',
 'type': 'video_local',
 'uid': '9',
 'vid': '99'}

I am running the script from my laptop and connecting to a Drupal site on a different machine.

You are mixing up attachments

smk-ka's picture

You are mixing up attachments and file objects. Attachments have different properties, see upload.module, line 410 ff. Since you are creating the file beforehand, you only want to save a new attachment with the node. Without any prior Python knowledge, node_files should IMO look more like this:

node_files = {
    'new': 1, # Required to insert the referenced file->fid as new attachment!
    'fid': f,
    'list': 0, # Or 1, depending on whether you want the attachment listed on node view.
    'description': 'whatever',
    'weight': 0,
}

-Stefan

That's it!

pdumais42's picture

That's it! I plugged in your code and ran my script. The uploaded file appears in the File attachments section as I was hoping.

{'body': 'This is a test created from a remote app',
 'changed': '1263691037',
 'comment': '0',
 'comment_count': '0',
 'created': '1263691037',
 'data': 'a:1:{s:13:"form_build_id";s:37:"form-b25e97816649fb6a7b49a7f115227c65";}',
 'field_video_attach': [''],
 'field_video_overlay': [{'value': 'CCK field text goes here'}],
 'files': {'535': {'description': 'Video Attachment',
                   'fid': '535',
                   'filemime': 'video/quicktime',
                   'filename': 'testfile_0.MOV',
                   'filepath': 'sites/default/files/testfile_0.MOV',
                   'filesize': '893292',
                   'list': '1',
                   'nid': '100',
                   'status': '1',
                   'timestamp': '1263691075',
                   'uid': '9',
                   'vid': '100',
                   'weight': '0'}},
 'format': '1',
 'language': '',
 'last_comment_name': '',
 'last_comment_timestamp': '1263691037',
 'log': '',
 'moderate': '0',
 'name': 'developer',
 'nid': '100',
 'picture': '',
 'promote': '1',
 'revision_timestamp': '1263691037',
 'revision_uid': '9',
 'status': '1',
 'sticky': '0',
 'taxonomy': [],
 'teaser': 'This is a test created from a remote app',
 'title': 'Remote Test 1263691075',
 'tnid': '0',
 'translate': '0',
 'type': 'video_local',
 'uid': '9',
 'vid': '100'}

Thank you smk-ka. You rock!

Media Mover takes over from here

pdumais42's picture

In my current situation, I use Media Mover to detect the file as a file attachment, transcode it and attach the result to the field_video_attach CCK field. So this solution works great for me.

If, in the future, I want to attach the file directly to a CCK field, do I use the method that I was attempting in my original solution? And actually pass the file as data with the node? You imply something to that effect in your comment about my confusing attachments and file objects.

Cheers,

-ped-

No, this doesn't work with

smk-ka's picture

No, this doesn't work with XML-RPC requests, since upload.module expects the file data in a separate variable (the $_FILES array in PHP, which isn't available in XML-RPC calls). Therefore the chosen way is the correct solution.

-Stefan

Can this be done via Services

jjjames's picture

Can this be done via Services instead of XML-RPC to bypass all of the media mover work?
thx

Cool stuff! I'm looking to do

jjjames's picture

Cool stuff! I'm looking to do this too. Can you please explain how you configured media mover to handle this?
Thanks!

Media Mover

pdumais42's picture

The python code is just one example of how to create a node from remote and attach a file upload. Anything that you do in MediaMover will work the same whether you create the node from the web interface or from a remote app.

Your question is about Media Mover and so we should really be posting that in a Media Mover thread.

On the face of it, the process is quite simple. In reality, there are issues beyond just Media Mover (see below). Media Mover has built in support for harvesting attachments from nodes, processing them, and attaching them either to a new node or to the same node. In my case, I harvest the attachment, process it with Ffmpeg, and then re-attach it to the same node in a CCK filefield. Nothing special there. The only tech note I can offer is that there is a bug in the version of MM that I am using (i'll get around to fixing it soon) that causes MM to re-process all attachments every time it runs. It should only process new attachments, but I get around this by simply telling MM to "Delete original source" file but that is not ideal. See this bug report: http://drupal.org/node/645060

While this is a very natural use of Media Mover, it does presume that you have Ffmpeg set up correctly on your server and you know how to display the resulting FLV file in the node (from CCK filefield). Try http://drupal.org/project/swftools

In my current project, we are doing some unique things that are specific to our needs, but I originally got started with some very useful insights from this post: http://drupal.org/node/465230 and the Media Mover project page: http://drupal.org/project/media_mover

I spent hours configuring and compiling Ffmpeg for my platform but never got exactly what I needed (codecs, etc) http://ubuntuforums.org/showpost.php?p=8345112&postcount=636
so it was a huge relief when I found this static binary http://24b6.net/content/ffmpeg-binary provided by arthurf

Media Mover is still in development. If you stick to what works, you should be fine. When you get very creative with it, as I have had to, it is very fragile. Check the issue queue to see what I mean. http://drupal.org/project/issues/media_mover

Cheers,

-ped-

Thanks for the info! It's a

jjjames's picture

Thanks for the info! It's a bit scary that Media Mover is still in dev, I've already experienced some issues...but will see if I can get it to work.

Please help.

kaw3939's picture

Hi,

I'm basically doing something very similar to what you are doing, and based on your website, you may be specifically interested in my project, it is a unique opportunity IMHOP.

At the very least, could you please contact me, so we can discuss what i'm doing and maybe answer some of my questions either here or through email.

I am working with a commercial vendor to create a drupal plugin to integrate a media encoding server that can extract text from videos using untrained speech to text processing. I.e. upload a video and outcomes a transcript that is probably about 50% accurate, but sufficient for searching. There are some other issues with this, but that we can talk about that offline.

I've developed with the services module a while back but I forget some things and need to do some new things.

  1. THe media encoding server will send a message to Drupal that pushes the file to Drupal. Based on this, it may need to be in the $_files array of an HTTP POST request.

  2. At that point the file should be sent to Drupal

  3. Upon completion of a successful transfer the system needs to notify the encoding server that the file is transfered correctly and that the node is created.

  4. Is it going to work something like this. The request data will be a json text in the request and then the $_files will contain the file. The $_files will be accepted and the data of the json text will be used to communicate information to the server about the video file?

We are creating a CCK customized field that can be added to nodes like file field; however, there is a lot of information related to the xml text transcript and xml video meta data file, which just is outside the scope of file field and needs to be handled differently IMHOP.

We are basing our module on file field though.

Please let me know if there is anything you think I should know about doing this, or anything you learned, since you solved this problem.

Services, Drupal 7

likin's picture

Hello.
Can anybody say about Services and Drupal7?
I have been working with 6 for a long time.

But I can not font any useful information for 6.

Thank you.

Dear Viktor, As I understand

ygerasimov's picture

Dear Viktor, As I understand you cannot find any information about services 7.x. You are very welcome to use them as they are more or less ready for usage. Please let me know whether you might have any specific questions about them.

Thank you for your reply. I

likin's picture

Thank you for your reply.
I have seen your video from DrupalCafe Ukraine once more.
My problem was That I had not understood new features of the Services in Drupal 7.
I did not know nothing about "end points". It's cool feature. It adds a new server layer. So I can able to communicate with Services using different client protocols.
I've added a new end point, determined the type of the server, enabled resources and actions.
I do not creating new service modules, I use the existed...

I see, there is not the stable version. I had got the problem: "undefined function drupal_set_header", I see that this functios is only used in Drupal 6.
So I use the last dev version.

Yes. The best is to use

ygerasimov's picture

Yes. The best is to use latest dev version for 7.x. I don't quite understand how I can help. Please contact me on the IRC (#drupal-services) or skype if you need any further assistance. And of course you are welcome to come to next Drupal Cafe to talk in person :)

Hi guys, hope this helps!

I want to admit that the

likin's picture

I want to admit that the "file.save" does not exist any more.
Insted of it the file.create is? But I can not get function signatures.
I see empty help.
When I try add a file I get the error.

"Ошибка сервера. Подпись запрошенного метода file.create не определена."
"Server error. The signature of file.create method is not underfined"

Is it right ?
return self.server.file.create(self.session["sessid"], file_obj)
I try so:

def send_file(self, path):
file = self.load_file(path)
timestamp = str(int(time.time()))
file_obj = {
'file': base64.b64encode(file["data"]),
'filename': file["filename"],
'filepath': 'sites/default/files/tmp/' + file["filename"],
'filesize': file["filesize"],
'timestamp': timestamp,
'uid': self.session['user']['uid'],
'filemime': file['filemime'],
}
print self.session['user']['uid']
print self.session["sessid"]
print self.server.file.create(file_obj)
return self.server.file.create(self.session["sessid"], file_obj)

Services

Group organizers

Group categories

Group notifications

This group offers an RSS feed. Or subscribe to these personalized, sitewide feeds: