translate.services.tmserver

Package translate :: Package services :: Module tmserver |
[hide private]
[frames] | no frames]

# Source Code for Module translate.services.tmserver

      1  #!/usr/bin/env python
      2  # -*- coding: utf-8 -*-
      3  #
      4  # Copyright 2008-2009 Zuza Software Foundation
      5  #
      6  # This file is part of translate.
      7  #
      8  # This program is free software; you can redistribute it and/or modify
      9  # it under the terms of the GNU General Public License as published by
     10  # the Free Software Foundation; either version 2 of the License, or
     11  # (at your option) any later version.
     12  #
     13  # This program is distributed in the hope that it will be useful,
     14  # but WITHOUT ANY WARRANTY; without even the implied warranty of
     15  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     16  # GNU General Public License for more details.
     17  #
     18  # You should have received a copy of the GNU General Public License
     19  # along with this program; if not, see <http://www.gnu.org/licenses/>.
     20
     21  """A translation memory server using tmdb for storage, communicates
     22  with clients using JSON over HTTP."""
     23
     24  import urllib
     25  import StringIO
     26  import logging
     27  import sys
     28  from cgi import parse_qs
     29  from optparse import OptionParser
     30  try:
     31      import json #available since Python 2.6
     32  except ImportError:
     33      import simplejson as json #API compatible with the json module
     34  from wsgiref import simple_server
     35
     36  from translate.misc import selector
     37  from translate.misc import wsgi
     38  from translate.storage import factory
     39  from translate.storage import base
     40  from translate.storage import tmdb
     41
     42 -class TMServer(object):
     43      """A RESTful JSON TM server."""
     44
     45 -    def __init__(self, tmdbfile, tmfiles, max_candidates=3, min_similarity=75,
     46              max_length=1000, prefix="", source_lang=None, target_lang=None):
     47
     48          self.tmdb = tmdb.TMDB(tmdbfile, max_candidates, min_similarity, max_length)
     49
     50          #load files into db
     51          if isinstance(tmfiles, list):
     52              [self.tmdb.add_store(factory.getobject(tmfile), source_lang, target_lang) \
     53                      for tmfile in tmfiles]
     54          elif tmfiles:
     55              self.tmdb.add_store(factory.getobject(tmfiles), source_lang, target_lang)
     56
     57          #initialize url dispatcher
     58          self.rest = selector.Selector(prefix=prefix)
     59          self.rest.add("/{slang}/{tlang}/unit/{uid:any}",
     60                        GET=self.translate_unit,
     61                        POST=self.update_unit,
     62                        PUT=self.add_unit,
     63                        DELETE=self.forget_unit
     64                        )
     65
     66          self.rest.add("/{slang}/{tlang}/store/{sid:any}",
     67                        GET=self.get_store_stats,
     68                        PUT=self.upload_store,
     69                        POST=self.add_store,
     70                        DELETE=self.forget_store)
     71
     72      @selector.opliant
     73 -    def translate_unit(self, environ, start_response, uid, slang, tlang):
     74          start_response("200 OK", [('Content-type', 'text/plain')])
     75          candidates = self.tmdb.translate_unit(uid, slang, tlang)
     76          logging.debug("candidates: %s", unicode(candidates))
     77          response = json.dumps(candidates, indent=4)
     78          params = parse_qs(environ.get('QUERY_STRING', ''))
     79          try:
     80              callback = params.get('callback', [])[0]
     81              response = "%s(%s)" % (callback, response)
     82          except IndexError:
     83              pass
     84          return [response]
     85
     86      @selector.opliant
     87 -    def add_unit(self, environ, start_response, uid, slang, tlang):
     88          start_response("200 OK", [('Content-type', 'text/plain')])
     89          #uid = unicode(urllib.unquote_plus(uid), "utf-8")
     90          data = json.loads(environ['wsgi.input'].read(int(environ['CONTENT_LENGTH'])))
     91          unit = base.TranslationUnit(data['source'])
     92          unit.target = data['target']
     93          self.tmdb.add_unit(unit, slang, tlang)
     94          return [""]
     95
     96      @selector.opliant
     97 -    def update_unit(self, environ, start_response, uid, slang, tlang):
     98          start_response("200 OK", [('Content-type', 'text/plain')])
     99          #uid = unicode(urllib.unquote_plus(uid), "utf-8")
    100          data = json.loads(environ['wsgi.input'].read(int(environ['CONTENT_LENGTH'])))
    101          unit = base.TranslationUnit(data['source'])
    102          unit.target = data['target']
    103          self.tmdb.add_unit(unit, slang, tlang)
    104          return [""]
    105
    106      @selector.opliant
    107 -    def forget_unit(self, environ, start_response, uid):
    108          #FIXME: implement me
    109          start_response("200 OK", [('Content-type', 'text/plain')])
    110          #uid = unicode(urllib.unquote_plus(uid), "utf-8")
    111
    112          return [response]
    113
    114      @selector.opliant
    115 -    def get_store_stats(self, environ, start_response, sid):
    116          #FIXME: implement me
    117          start_response("200 OK", [('Content-type', 'text/plain')])
    118          #sid = unicode(urllib.unquote_plus(sid), "utf-8")
    119
    120          return [response]
    121
    122      @selector.opliant
    123 -    def upload_store(self, environ, start_response, sid, slang, tlang):
    124          """add units from uploaded file to tmdb"""
    125          start_response("200 OK", [('Content-type', 'text/plain')])
    126          data = StringIO.StringIO(environ['wsgi.input'].read(int(environ['CONTENT_LENGTH'])))
    127          data.name = sid
    128          store = factory.getobject(data)
    129          count = self.tmdb.add_store(store, slang, tlang)
    130          response = "added %d units from %s" % (count, sid)
    131          return [response]
    132
    133      @selector.opliant
    134 -    def add_store(self, environ, start_response, sid, slang, tlang):
    135          """Add unit from POST data to tmdb."""
    136          start_response("200 OK", [('Content-type', 'text/plain')])
    137          units = json.loads(environ['wsgi.input'].read(int(environ['CONTENT_LENGTH'])))
    138          count = self.tmdb.add_list(units, slang, tlang)
    139          response = "added %d units from %s" % (count, sid)
    140          return [response]
    141
    142      @selector.opliant
    143 -    def forget_store(self, environ, start_response, sid):
    144          #FIXME: implement me
    145          start_response("200 OK", [('Content-type', 'text/plain')])
    146          #sid = unicode(urllib.unquote_plus(sid), "utf-8")
    147
    148          return [response]
    149
    150
    151 -def main():
    152      parser = OptionParser()
    153      parser.add_option("-d", "--tmdb", dest="tmdbfile", default=":memory:",
    154                        help="translation memory database file")
    155      parser.add_option("-f", "--import-translation-file", dest="tmfiles", action="append",
    156                        help="translation file to import into the database")
    157      parser.add_option("-t", "--import-target-lang", dest="target_lang",
    158                        help="target language of translation files")
    159      parser.add_option("-s", "--import-source-lang", dest="source_lang",
    160                        help="source language of translation files")
    161      parser.add_option("-b", "--bind", dest="bind", default="localhost",
    162                        help="adress to bind server to (default: localhost)")
    163      parser.add_option("-p", "--port", dest="port", type="int", default=8888,
    164                        help="port to listen on (default: 8888)")
    165      parser.add_option("--max-candidates", dest="max_candidates", type="int", default=3,
    166                        help="Maximum number of candidates")
    167      parser.add_option("--min-similarity", dest="min_similarity", type="int", default=75,
    168                        help="minimum similarity")
    169      parser.add_option("--max-length", dest="max_length", type="int", default=1000,
    170                        help="Maxmimum string length")
    171      parser.add_option("--debug", action="store_true", dest="debug", default=False,
    172                        help="enable debugging features")
    173
    174      (options, args) = parser.parse_args()
    175
    176      #setup debugging
    177      format = '%(asctime)s %(levelname)s %(message)s'
    178      level = options.debug and logging.DEBUG or logging.WARNING
    179      if options.debug:
    180          format = '%(levelname)7s %(module)s.%(funcName)s:%(lineno)d: %(message)s'
    181          if sys.version_info[:2] < (2, 5):
    182              format = '%(levelname)7s %(module)s [%(filename)s:%(lineno)d]: %(message)s'
    183      else:
    184          try:
    185              import psyco
    186              psyco.full()
    187          except Exception:
    188              pass
    189
    190      logging.basicConfig(level=level, format=format)
    191
    192      application = TMServer(options.tmdbfile, options.tmfiles, max_candidates=options.max_candidates,
    193                             min_similarity=options.min_similarity, max_length=options.max_length,
    194                             prefix="/tmserver", source_lang=options.source_lang, target_lang=options.target_lang)
    195      wsgi.launch_server(options.bind, options.port, application.rest)
    196
    197
    198  if __name__ == '__main__':
    199      main()
    200