Commits

Erik Svensson  committed e803459 Merge

Merge down to default.

  • Participants
  • Parent commits 1138409, c646edd
  • Tags release-0.3

Comments (0)

Files changed (10)

 43fc5953284c0103846c31685f2d319de260c565 release-0.1
 f13f60a1b1b19ce3a1a03c022ebdb23fc2dde0bf release-0.2
 327d3a89c94b3d0c8ed48d1f6da875c0325c5b15 release-0.2
+113840953040db2732edecefb0ca89e7b57eb4d4 release-0.3

File contrib/helical.py

 # -*- coding: utf-8 -*-
 # 2008-07, Erik Svensson <erik.public@gmail.com>
 
-import sys, os, os.path, re
+import sys, os, os.path, re, itertools
 import urllib2, base64, shlex
+from optparse import OptionParser
 try:
     import readline
 except:
 import cmd
 import transmissionrpc
 from transmissionrpc.utils import *
+from transmissionrpc.constants import DEFAULT_PORT
 
 __author__    = u'Erik Svensson <erik.public@gmail.com>'
-__version__   = u'0.1'
+__version__   = u'0.2'
 __copyright__ = u'Copyright (c) 2008 Erik Svensson'
 __license__   = u'MIT'
 
     def __init__(self):
         cmd.Cmd.__init__(self)
         self.intro = u'Helical %s' % (__version__)
-        self.verbose = False
-        self.set_daemon()
         self.doc_leader = u'''
 Helical is a command line interface that communicates with Transmission
 bittorent client through json-rpc. To run helical in interactive mode
 start without a command.
 '''
-    
-    def set_daemon(self, address=None):
-        if address:
-            (addr, port) = inet_address(address, transmissionrpc.DEFAULT_PORT)
-        else:
-            addr = u'localhost'
-            port = transmissionrpc.DEFAULT_PORT
-        self.address = (addr, port)
-        self.tc = transmissionrpc.Client(addr, port, verbose=self.verbose)
-        self.prompt = u'Helical %s:%d> ' % (self.address[0], self.address[1])
-    
+
+    def connect(self, address=None, port=None, username=None, password=None):
+        self.tc = transmissionrpc.Client(address, port, username, password)
+        self.prompt = u'Helical %s:%d> ' % (address, port)
+
     def arg_tokenize(self, argstr):
         return [unicode(token, 'utf-8') for token in shlex.split(argstr.encode('utf-8'))] or ['']
 
             if word.startswith(text):
                 suggestions.append(word)
         return suggestions
-    
+
     def _complete_torrent(self, name, offset):
         words = [torrent.name for id, torrent in self.tc.torrents.iteritems()]
         suggestions = []
             if word.startswith(name):
                 suggestions.append(word[cut_index:])
         return suggestions
-    
+
     def _complete_torrent_command(self, text, line, begidx, endidx):
         args = self.arg_tokenize(line)
         item = args[-1] if len(args) > 1 else ''
         return self._complete_torrent(item, endidx - begidx)
-    
+
     def help_quit(self):
         print(u'quit|exit\n')
         print(u'Exit to shell.\n')
-    
+
     def do_quit(self, line):
         sys.exit('')
     #Alias
     do_exit = do_quit
     help_exit = help_quit
     do_EOF = do_quit
-    
+
     def help_add(self):
         print(u'add <torrent file or url> [<target dir> paused=(yes|no) peer-limit=#]\n')
         print(u'Add a torrent to the transfer list.\n')
 
     def do_add(self, line):
         args = self.arg_tokenize(line)
-        
+
         if len(args) == 0:
             print(u'Specify a torrent file or url')
             return
-        
+
         torrent_url = args[0]
         args = args[1:]
         torrent_file = None
         if not torrent_file:
             print(u'Couldn\'t find torrent "%s"' % torrent_url)
             return
-        
+
         add_args = {}
         if len(args) > 0:
             for arg in args:
                         except:
                             pass
                     print(u'Unknown argument: "%s"' % arg)
-        
+
         torrent_data = base64.b64encode(torrent_file.read())
         try:
             self.tc.add(torrent_data, **add_args)
         except transmissionrpc.TransmissionError, e:
             print(u'Failed to add torrent "%s"' % e)
-    
+
     def complete_remove(self, text, line, begidx, endidx):
         return self._complete_torrent_command(text, line, begidx, endidx)
-    
+
     def help_remove(self):
         print(u'remove <torrent id> [,<torrent id>, ...]\n')
         print(u'Remove one or more torrents from the transfer list.\n')
-    
+
     def do_remove(self, line):
         args = self.arg_tokenize(line)
         if len(args) == 0:
             raise ValueError(u'No torrent id')
         self.tc.remove(args)
-    
+
     def complete_start(self, text, line, begidx, endidx):
         return self._complete_torrent_command(text, line, begidx, endidx)
-    
+
     def help_start(self):
         print(u'start <torrent id> [,<torrent id>, ...]\n')
         print(u'Start one or more queued torrent transfers.\n')
-    
+
     def do_start(self, line):
         args = self.arg_tokenize(line)
         if len(args) == 0:
             raise ValueError(u'No torrent id')
         self.tc.start(args)
-    
+
     def complete_stop(self, text, line, begidx, endidx):
         return self._complete_torrent_command(text, line, begidx, endidx)
-    
+
     def help_stop(self):
         print(u'stop <torrent id> [,<torrent id>, ...]\n')
         print(u'Stop one or more active torrent transfers.\n')
-    
+
     def do_stop(self, line):
         args = self.arg_tokenize(line)
         if len(args) == 0:
             raise ValueError(u'No torrent id')
         self.tc.stop(args)
-    
+
     def complete_verify(self, text, line, begidx, endidx):
         return self._complete_torrent_command(text, line, begidx, endidx)
-    
+
     def help_verify(self):
         print(u'verify <torrent id> [,<torrent id>, ...]\n')
         print(u'Verify one or more torrent transfers.\n')
-    
+
     def do_verify(self, line):
         args = self.arg_tokenize(line)
         if len(args) == 0:
             raise ValueError(u'No torrent id')
         self.tc.verify(args)
-    
+
     def complete_info(self, text, line, begidx, endidx):
         return self._complete_torrent_command(text, line, begidx, endidx)
-    
+
     def help_info(self):
         print(u'info [<torrent id>, ...]\n')
         print(u'Get details for a torrent. If no torrent id is provided, all torrents are displayed.\n')
-    
+
     def do_info(self, line):
         args = self.arg_tokenize(line)
         if len(args) == 0:
         result = self.tc.info(args)
         for id, torrent in result.iteritems():
             print(self._torrent_detail(torrent))
-    
+
     def help_list(self):
         print(u'list\n')
         print(u'List all torrent transfers.\n')
-    
+
     def do_list(self, line):
         args = self.arg_tokenize(line)
         result = self.tc.list()
         self._list_torrents(result)
-    
+
     def help_files(self):
         print(u'files [<torrent id>, ...]\n')
         print(u'Get the file list for one or more torrents\n')
-    
+
     def do_files(self, line):
         args = self.arg_tokenize(line)
         result = self.tc.get_files(args)
             print('torrent id: %d' % tid)
             for fid, file in files.iteritems():
                 print('  %d: %s' % (fid, file['name']))
-    
+
     def do_set(self, line):
         args = self.arg_tokenize(line)
         set_args = {}
         ids = []
         add_ids = True
-        
+
         if len(args) > 0:
             for arg in args:
                 try:
                     if add_ids:
                         ids.append(arg)
                     else:
-                        print(u'Unknown argument: "%s"' % arg)        
+                        print(u'Unknown argument: "%s"' % arg)
         if len(ids) > 0:
             result = self.tc.change(ids, **set_args)
-    
+
     def complete_session(self, text, line, begidx, endidx):
         return self.word_complete(text, [u'get', u'set', u'stats'])
-    
+
     def help_session(self):
         print(u'session (get|stats)\n')
         print(u'Get session parameters or session statistics.\n')
-    
+
     def do_session(self, line):
         args = self.arg_tokenize(line)
         if len(args[0]) == 0 or args[0] == u'get':
             print(self.tc.session)
         elif args[0] == u'stats':
             print(self.tc.session_stats())
-    
+
     def do_request(self, line):
         (method, sep, args) = line.partition(' ')
         try:
         self.tc.verbose = True
         self.tc._request(method, args)
         self.tc.verbose = verbose
-    
+
     def _list_torrents(self, torrents):
         if len(torrents) > 0:
             print(self._torrent_brief_header())
             for tid, torrent in torrents.iteritems():
                 print(self._torrent_brief(torrent))
-    
+
     def _torrent_brief_header(self):
         return u' Id  Done   ETA           Status       Download    Upload      Ratio  Name'
-            
+
     def _torrent_brief(self, torrent):
         s = u'% 3d: ' % (torrent.id)
         try:
             pass
         try:
             s += u' %6.2f' % torrent.ratio
-        except:    
+        except:
             s += u' -ratio'
             pass
         s += u' ' + torrent.name
         return s
-    
+
     def _torrent_detail(self, torrent):
         s = ''
         s +=   '            id: ' + str(torrent.fields['id'])
     """Main entry point"""
     if args is None:
         args = sys.argv[1:]
-    
+    parser = OptionParser(usage='Usage: %prog [options] [[address]:[port]] [command]')
+    parser.add_option('-u', '--username', dest='username',
+                    help='Athentication username.')
+    parser.add_option('-p', '--password', dest='password',
+                    help='Athentication password.')
+    (values, args) = parser.parse_args(args)
+    commands = [cmd[3:] for cmd in itertools.ifilter(lambda c: c[:3] == 'do_', dir(Helical))]
+    address = 'localhost'
+    port = DEFAULT_PORT
+    command = None
+    for arg in args:
+        if arg in commands:
+            command = arg
+            break
+        try:
+            (address, port) = inet_address(arg, DEFAULT_PORT)
+        except INetAddressError:
+            pass
     helical = Helical()
-    
-    # parse flags
-    if len(args) > 0:
-        if args[0] == u'-d':
-            helical.verbose = True
-            args = args[1:]
-        elif args[0] in [u'-h', u'--help', u'help']:
-            arg = ''
-            try:
-                arg = args[1]
-            except:
-                pass
-            sys.exit(helical.do_help(arg))
-    
-    # parse daemon address
-    if len(args) > 0:
-        try:
-            helical.set_daemon(args[0])
-            args = args[1:]
-        except:
-            pass
-    
-    # parse command and arguments
-    if len(args) > 0:
-        # we must put arguments in quotes to help the world peace
-        command_args = u''
-        command = args[0]
-        if len(args) > 0:
-            command_args += u' '.join([u'"%s"' % arg for arg in args[1:]])
+    try:
+        helical.connect(address, port, values.username, values.password)
+    except transmissionrpc.TransmissionError, error:
+        print(error)
+        parser.print_help()
+        return
+
+    if command:
+        command_args = u' '.join([u'"%s"' % arg for arg in args[args.index(command)+1:]])
         helical.onecmd(command + command_args)
     else:
         try:
-            helical.tc.list()
-        except transmissionrpc.TransmissionError, e:
-            print(e)
-            helical.exit(helical.do_help(u''))
-        
-        try:
             helical.cmdloop()
         except KeyboardInterrupt:
             helical.do_quit('')

File doc/_static/style.css

 }
 
 h1, h2, h3, h4, h5, h6 {
-    font-weight: 200;
+    font-weight: 400;
 }
 
 a {
     list-style: none;
     padding-left: 2em;
 }
+
+div.admonition {
+    margin: 0.5em;
+    border: 1px solid #999;
+}
+
+div.admonition p {
+    margin: 0.5em;
+}
+
+p.admonition-title {
+    font-weight: bold;
+}
+
+div.note {
+    background: #ff9;
+    border: 1px solid #990;
+}
+

File doc/index.rst

 Introduction
 ============
 
-This is the transmissionrpc. This module helps using Python to connect
+This is **transmissionrpc**. This module helps using Python to connect
 to a Transmission_ JSON-RPC service. transmissionrpc is compatible with
 Transmission 1.3 and later.
 
 -----------
 
  * simplejson >= 1.7.1 or Python >= 2.6.
- 
+
 Report a problem
 ----------------
 
 
     $ python setup.py install
 
-Or if you wish to poke around in transmissionrpc itself use
+Or if you wish to further develop transmissionrpc itself use
 ::
 
 	$ python setup.py develop
 ::
 
     >>> import transmissionrpc
-    >>> tc = transmissionrpc.Client('localhost', port=9091, user=None, password=None)
+    >>> tc = transmissionrpc.Client('localhost', port=9091)
     >>> tc.list()
 
 List will return a dictionary of Torrent object indexed by their id. You might
     >>> tc.info('1:3')
     {2: <Torrent 2 "ubuntu-8.10-server-amd64.iso">, 3: <Torrent 3 "ubuntu-8.10-alternate-amd64.iso">}
 
-Continue to explore and have fun! For more in depth information read the module reference.
+Continue to explore and have fun! For more in depth information read the module
+reference.
+
+A note about debugging information
+----------------------------------
+
+If you ever need to see what's going on inside transmissionrpc, you can change
+the logging level of transmissionrpc. This is done with these easy steps
+::
+
+	>>> import logging
+	>>> logging.getLogger('transmissionrpc').setLevel(logging.DEBUG)
+
+Note that this will produce a whole lot of output! Other levels are (listed by
+severity)
+
+ * ``logging.ERROR``
+ * ``logging.WARNING``
+ * ``logging.INFO``
+ * ``logging.DEBUG``
+
+The default logging level of transmissionrpc is ``logging.ERROR``.
 
 Module reference
 ================

File doc/reference/transmissionrpc.rst

 
     This exception is raised when there has occured an error related to
     communication with Transmission. It is a subclass of :exc:`Exception`.
-    
+
     .. attribute:: original
-    
+
         The original exception.
 
 Torrent object
 
 Torrent is a class holding the information received from Transmission regarding
 a bittorrent transfer. All fetched torrent fields are accessible through this
-class using attributes. This class has a few convenience properties using the
-torrent information.
+class using attributes. The attributes use underscore instead of hyphen in the
+names though. This class has a few convenience properties using the torrent
+information.
 
 Example:
 ::
     'My torrent'
     >>> t.date_added
     datetime.datetime(2009, 1, 18, 13, 16, 59)
-    >>> 
+    >>>
 
 .. class:: Torrent(fields)
-    
+
     *fields* should be an dictionary build from the torrent information from an
     Transmission JSON-RPC result.
-    
+
 .. attribute:: Torrent.date_active
 
     Get the attribute *activityDate* as datetime.datetime.
 .. attribute:: Torrent.date_done
 
     Get the attribute *doneDate* as datetime.datetime.
-    
+
 .. attribute:: Torrent.eta
 
     The attribute *eta* as datetime.timedelta.
 .. attribute:: Torrent.ratio
 
     The upload/download ratio.
-    
+
 .. attribute:: Torrent.status
 
     Returns the torrent status. Is either one of 'check pending', 'checking',
                 'priority': <priority ('high'|'normal'|'low')>,
                 'selected': <selected for download>
             }
-        
+
             ...
         }
-    
+
     Example:
     ::
 
     hashString. When suppling multiple id's it is possible to use a list mixed
     with both id and hashString.
 
-.. class:: Client(address='localhost', port=9091, user=None, password=None, verbose=False)
+.. class:: Client(address='localhost', port=9091, user=None, password=None)
 
     * *address* and *port* should be the address and port to the Transmission
       "server", this can be either a Transmission client with rpc access enabled
       or transmission-daemon.
     * *user* and *password* is the username and password for RPC access
-      if password protection is used.
+      if athentication is used.
+    
+    The argument *verbose* was removed in 0.3, use logging levels instead.
 
 .. _transmissionrpc-client-add:
 .. method:: Client.add(data, kwargs**)
     * `download_dir`, The directory where the downloaded contents will be
       saved in.
     * `peer_limit`, Limits the number of peers for this transfer.
+    * `files_unwanted`, A list of file index not to download.
+    * `files_wanted`, A list of file index to download.
+    * `priority_high`, A list of file index with high priority.
+    * `priority_low`, A list of file index with low priority.
+    * `priority_normal`, A list of file index with normal priority.
+    
+    `files_unwanted`, `files_wanted`, `priority_high`, `priority_low`
+    , `priority_normal` are new in RPC protocol version 5.
 
 .. method:: Client.add_url(torrent_url, kwargs**)
 
     Add torrent to transfers list. Takes a file path or url to a .torrent file
     in *torrent_url*.
-    
-    For information on addition argument see :ref:`Client.add <transmissionrpc-client-add>`.
+
+    For information on additional argument see
+    :ref:`Client.add <transmissionrpc-client-add>`.
 
 .. method:: Client.remove(ids, delete_data=False)
 
 .. method:: Client.info(ids=[])
 
     Get information for the torrent(s) with the supplied id(s). If *ids* is
-    empty, information for all torrents are fetched.
+    empty, information for all torrents are fetched. See the RPC specification
+    for a full list of information fields.
 
 .. _transmissionrpc-client-get_files:
 .. method:: Client.get_files(ids=[])
     Get list of files for provided torrent id(s). If *ids* is empty,
     information for all torrents are fetched. This function returns a dictonary
     for each requested torrent id holding the information about the files.
-    
+
     ::
-    
+
         {
             <torrent id>: {
                 <file id>: {
                     'priority': <priority ('high'|'normal'|'low')>,
                     'selected': <selected for download>
                 }
-                
+
                 ...
             }
-            
+
             ...
         }
-    
+
     Example:
     ::
-    
+
         {
             1: {
                 0: {
 
 .. method:: Client.list()
 
-    list all torrents, fetching ``id``, ``hashString``, ``name``, ``sizeWhenDone``,
-    ``leftUntilDone``, ``eta``, ``status``, ``rateUpload``, ``rateDownload``,
-    ``uploadedEver``, ``downloadedEver`` for each torrent.
+    list all torrents, fetching ``id``, ``hashString``, ``name``
+    , ``sizeWhenDone``, ``leftUntilDone``, ``eta``, ``status``, ``rateUpload``
+    , ``rateDownload``, ``uploadedEver``, ``downloadedEver`` for each torrent.
 
 .. method:: Client.change(ids, kwargs**)
 
     Change torrent parameters for the torrent(s) with the supplied id's. The
     parameters are:
-    
+
     * ``files_wanted``, A list of file id's that should be downloaded.
     * ``files_unwanted``, A list of file id's that shouldn't be downloaded.
     * ``peer_limit``, The peer limit for the torrents.
     * ``priority_high``, A list of file id's that should have high priority.
     * ``priority_normal``, A list of file id's that should have normal priority.
     * ``priority_low``, A list of file id's that should have low priority.
-    * ``speed_limit_up``, Set the speed limit for upload in Kib/s.
-    * ``speed_limit_up_enable``, Enable upload speed limiter.
-    * ``speed_limit_down``, Set the speed limit for download in Kib/s.
-    * ``speed_limit_down_enable``, Enable download speed limiter.
+    * ``uploadLimit``, Set the speed limit for upload in Kib/s.
+    * ``uploadLimited``, Enable upload speed limiter.
+    * ``downloadLimit``, Set the speed limit for download in Kib/s.
+    * ``downloadLimited``, Enable download speed limiter.
+    
+    Following arguments where renamed in RPC protocol version 5.
+    
+    * ``speed_limit_up`` is now called ``uploadLimit`` 
+    * ``speed_limit_up_enable`` is now called ``uploadLimited``
+    * ``speed_limit_down`` is now called ``downloadLimit``
+    * ``speed_limit_down_enable`` is now called ``downloadLimited``
+    
+    .. NOTE::
+       transmissionrpc will try to automatically fix argument errors.
 
 .. method:: Client.get_session()
 
 .. method:: Client.set_session()
 
     Set session parameters. The parameters are:
-    
+
+    * ``alt_speed_down``, max global download speed (in K/s).
+    * ``alt_speed_enabled``, True means use the alt speeds.
+    * ``alt_speed_time_begin``, when to turn on alt speeds (units: minutes after midnight).
+    * ``alt_speed_time_day``, what day(s) to turn on alt speeds (look at tr_sched_day).
+    * ``alt_speed_time_enabled``, True means the scheduled on/off times are used.
+    * ``alt_speed_time_end``, when to turn off alt speeds (units: same).
+    * ``alt_speed_up``, max global upload speed (in K/s).
+    * ``blocklist_enabled``, Enabled block list.
     * ``encryption``, Level of encryption. Should be one of ``required``, ``preferred`` or ``tolerated``.
     * ``download_dir``, Default download dir.
-    * ``peer_limit``, Default download dir.
-    * ``pex_allowed``, Allow pex in public torrents.
-    * ``port``, Set the port number.
-    * ``port_forwarding_enabled``, 
+    * ``peer_limit_global``, Maximum number of peers.
+    * ``peer_limit_per_torrent``, Maximum number of peers per torrent.
+    * ``pex_enabled``, Allow pex in public torrents.
+    * ``peer_port``, Set the port number.
+    * ``peer-port-random-on-start``, Ranomize port peer port om launch.
+    * ``port_forwarding_enabled``, Enabled port forwarding.
+    * ``seedRatioLimit``, Limits how much to seed, where 1.0 is as much as you downloaded.
+    * ``seedRatioLimited``, Enables seed limiting.
     * ``speed_limit_down``, Set the global download speed limit in Kib/s.
     * ``speed_limit_down_enabled``, Enables the global download speed limiter.
     * ``speed_limit_up``, Set the global upload speed limit in Kib/s.
     * ``speed_limit_up_enabled``, Enables the global upload speed limiter.
+    
+    Following arguments where renamed in RPC protocol version 5.
+    
+    * ``peer_limit`` is now called ``peer_limit_global``
+    * ``pex_allowed`` is now called ``pex_enabled`` 
+    * ``port`` is now called ``peer_port``
+    
+    .. NOTE::
+       transmissionrpc will try to automatically fix argument errors.
 
 .. method:: Client.session_stats()
 

File tests/client.py

         client._request = request
         call(*args, **kwargs)
         client._request = original_request
-    
+
     def assertTransmissionQuery(self, expected, result, client, call, *args, **kwargs):
         def query(q):
             data = json.loads(q)
         client._http_query = query
         call(*args, **kwargs)
         client._http_query = original_method
-    
+
     def testConstruction(self):
         tc = Client()
         self.assertEqual(tc.url, 'http://localhost:%d/transmission/rpc' % (transmissionrpc.constants.DEFAULT_PORT))
         tc = Client('127.0.0.1', 7000, password='secret')
         self.assertEqual(tc.url, 'http://127.0.0.1:7000/transmission/rpc')
         self.assertEqual(tc.verbose, False)
-    
+
     def testAdd(self):
         tc = Client()
         data = 'data'
         self.assertTransmissionRequest(expected, tc, tc.add, data, paused=True, download_dir='/tmp', peer_limit=10)
         # test exception for non integer peer_limit
         self.failUnlessRaises(ValueError, tc.add, data, peer_limit='apa')
-    
+
     def testAddUrl(self):
         dirpath = os.path.dirname(os.path.abspath(__file__))
         tc = Client()
         self.assertTransmissionRequest(expected, tc, tc.add_url, '%s/torrent.txt' % (dirpath), paused=True, download_dir='/tmp', peer_limit=10)
         self.failUnlessRaises(TransmissionError, tc.add_url, 'torrent.torrent')
         # TODO: Add test for real web url's?
-    
+
     def testRemove(self):
         tc = Client()
         ids = ['0123456789abcdef', 2, 3]
         # test at http interface
         expected = {'method': 'torrent-remove', 'arguments': {'ids': ids, 'delete-local-data': 0}}
         self.assertTransmissionQuery(expected, {}, tc, tc.remove, ids)
-    
+
     def testStart(self):
         tc = Client()
         ids = ['0123456789abcdef', 2, 3]
         # test at http interface
         expected = {'method': 'torrent-start', 'arguments': {'ids': ids}}
         self.assertTransmissionQuery(expected, {}, tc, tc.start, ids)
-    
+
     def testStop(self):
         tc = Client()
         ids = ['0123456789abcdef', 2, 3]
         # test at http interface
         expected = {'method': 'torrent-stop', 'arguments': {'ids': ids}}
         self.assertTransmissionQuery(expected, {}, tc, tc.stop, ids)
-    
+
     def testVerify(self):
         tc = Client()
         ids = ['0123456789abcdef', 2, 3]
         # test at http interface
         expected = {'method': 'torrent-verify', 'arguments': {'ids': ids}}
         self.assertTransmissionQuery(expected, {}, tc, tc.verify, ids)
-    
+
     def testInfo(self):
         tc = Client()
         fields = transmissionrpc.utils.get_arguments('torrent-get', 5)

File tests/live.py

         self.client.remove(self.torrent_id, delete_data=True)
         del self.client
 
-    def doSetSession(self, argument, value, rvalfunc=None):
+    def doSetSession(self, argument, value, rvalfunc=None, rarg=None):
         if not rvalfunc:
             rvalfunc = lambda v: v
+        if not rarg:
+            rarg = argument
         original = copy.deepcopy(self.client.get_session())
         args = {argument: value}
         self.client.set_session(**args)
         session = self.client.get_session()
-        rval = rvalfunc(session.fields[argument])
-        self.assertEqual(rval, rval
+        rval = rvalfunc(session.fields[rarg])
+        self.assertEqual(value, rval
             , msg='Argument "%s": in: "%r" does not equal out:"%r"'
             % (argument, value, rval))
-        sval = rvalfunc(original.fields[argument])
+        sval = rvalfunc(original.fields[rarg])
         args = {argument: sval}
         self.client.set_session(**args)
         session = self.client.get_session()
-        rval = rvalfunc(session.fields[argument])
+        rval = rvalfunc(session.fields[rarg])
         self.assertEqual(rval, sval
             , msg='Argument "%s": original in: "%r" does not equal out:"%r"'
             % (argument, sval, rval))
             self.doSetSession('pex_allowed', True)
             self.doSetSession('port', 33033)
             self.doSetSession('peer_limit', 1000)
+            # test automatic argument replacer
+            self.doSetSession('peer_limit_global', 1000, rarg='peer_limit')
+            self.doSetSession('pex_enabled', True, rarg='pex_allowed')
+            self.doSetSession('peer_port', 33033, rarg='port')
             # TODO: should test a lot of failures
         if self.client.rpc_version > 4:
             self.doSetSession('alt_speed_down', 10)
             self.doSetSession('seedRatioLimit', 100)
             self.doSetSession('seedRatioLimited', False)
             self.doSetSession('seedRatioLimited', True)
-            # fail!
-            self.doFailSetSession('peer_limit', 1000)
-            self.doFailSetSession('pex_allowed', False)
-            self.doFailSetSession('port', 1000)
+            # test automatic argument replacer
+            self.doSetSession('peer_limit', 1000, rarg='peer_limit_global')
+            self.doSetSession('pex_allowed', True, rarg='pex_enabled')
+            self.doSetSession('port', 33033, rarg='peer_port')
 
     def testGetSession(self):
         o = self.client.get_session()

File transmissionrpc/constants.py

 # -*- coding: utf-8 -*-
 # 2008-07, Erik Svensson <erik.public@gmail.com>
 
+import logging
+
+logger = logging.getLogger('transmissionrpc')
+logger.setLevel(logging.ERROR)
+
 def mirror_dict(d):
     d.update(dict((v, k) for k, v in d.iteritems()))
     return d
     'unlimeted' : TR_RATIOLIMIT_UNLIMITED
 })
 
+# A note on argument maps
+# These maps are used to verify *-set methods. The information is structured in
+# a tree.
+# set +- <argument1> - [<type>, <added version>, <removed version>, <previous argument name>, <next argument name>]
+#  |  +- <argument2> - [<type>, <added version>, <removed version>, <previous argument name>, <next argument name>]
+#  |
+# get +- <argument1> - [<type>, <added version>, <removed version>, <previous argument name>, <next argument name>]
+#     +- <argument2> - [<type>, <added version>, <removed version>, <previous argument name>, <next argument name>]
+
+# Arguments for torrent methods
 TORRENT_ARGS = {
     'get' : {
-        'activityDate':             ('number', 1, None),
-        'addedDate':                ('number', 1, None),
-        'announceResponse':         ('string', 1, None),
-        'announceURL':              ('string', 1, None),
-        'bandwidthPriority':        ('number', 5, None),
-        'comment':                  ('string', 1, None),
-        'corruptEver':              ('number', 1, None),
-        'creator':                  ('string', 1, None),
-        'dateCreated':              ('number', 1, None),
-        'desiredAvailable':         ('number', 1, None),
-        'doneDate':                 ('number', 1, None),
-        'downloadDir':              ('string', 4, None),
-        'downloadedEver':           ('number', 1, None),
-        'downloaders':              ('number', 4, None),
-        'downloadLimit':            ('number', 1, None),
-        'downloadLimited':          ('boolean', 5, None),
-        'downloadLimitMode':        ('number', 1, 5),
-        'error':                    ('number', 1, None),
-        'errorString':              ('number', 1, None),
-        'eta':                      ('number', 1, None),
-        'files':                    ('array', 1, None),
-        'fileStats':                ('array', 5, None),
-        'hashString':               ('string', 1, None),
-        'haveUnchecked':            ('number', 1, None),
-        'haveValid':                ('number', 1, None),
-        'honorsSessionLimits':      ('boolean', 5, None),
-        'id':                       ('number', 1, None),
-        'isPrivate':                ('boolean', 1, None),
-        'lastAnnounceTime':         ('number', 1, None),
-        'lastScrapeTime':           ('number', 1, None),
-        'leechers':                 ('number', 1, None),
-        'leftUntilDone':            ('number', 1, None),
-        'manualAnnounceTime':       ('number', 1, None),
-        'maxConnectedPeers':        ('number', 1, None),
-        'name':                     ('string', 1, None),
-        'nextAnnounceTime':         ('number', 1, None),
-        'nextScrapeTime':           ('number', 1, None),
-        'peer-limit':               ('number', 5, None),
-        'peers':                    ('array', 2, None),
-        'peersConnected':           ('number', 1, None),
-        'peersFrom':                ('object', 1, None),
-        'peersGettingFromUs':       ('number', 1, None),
-        'peersKnown':               ('number', 1, None),
-        'peersSendingToUs':         ('number', 1, None),
-        'percentDone':              ('double', 5, None),
-        'pieces':                   ('string', 5, None),
-        'pieceCount':               ('number', 1, None),
-        'pieceSize':                ('number', 1, None),
-        'priorities':               ('array', 1, None),
-        'rateDownload':             ('number', 1, None),
-        'rateUpload':               ('number', 1, None),
-        'recheckProgress':          ('double', 1, None),
-        'scrapeResponse':           ('string', 1, None),
-        'scrapeURL':                ('string', 1, None),
-        'seeders':                  ('number', 1, None),
-        'seedRatioLimit':           ('double', 5, None),
-        'seedRatioMode':            ('number', 5, None),
-        'sizeWhenDone':             ('number', 1, None),
-        'startDate':                ('number', 1, None),
-        'status':                   ('number', 1, None),
-        'swarmSpeed':               ('number', 1, None),
-        'timesCompleted':           ('number', 1, None),
-        'trackers':                 ('array', 1, None),
-        'totalSize':                ('number', 1, None),
-        'torrentFile':              ('string', 5, None),
-        'uploadedEver':             ('number', 1, None),
-        'uploadLimit':              ('number', 1, None),
-        'uploadLimitMode':          ('number', 1, 5),
-        'uploadLimited':            ('boolean', 5, None),
-        'uploadRatio':              ('double', 1, None),
-        'wanted':                   ('array', 1, None),
-        'webseeds':                 ('array', 1, None),
-        'webseedsSendingToUs':      ('number', 1, None),
+        'activityDate':             ('number', 1, None, None, None),
+        'addedDate':                ('number', 1, None, None, None),
+        'announceResponse':         ('string', 1, None, None, None),
+        'announceURL':              ('string', 1, None, None, None),
+        'bandwidthPriority':        ('number', 5, None, None, None),
+        'comment':                  ('string', 1, None, None, None),
+        'corruptEver':              ('number', 1, None, None, None),
+        'creator':                  ('string', 1, None, None, None),
+        'dateCreated':              ('number', 1, None, None, None),
+        'desiredAvailable':         ('number', 1, None, None, None),
+        'doneDate':                 ('number', 1, None, None, None),
+        'downloadDir':              ('string', 4, None, None, None),
+        'downloadedEver':           ('number', 1, None, None, None),
+        'downloaders':              ('number', 4, None, None, None),
+        'downloadLimit':            ('number', 1, None, None, None),
+        'downloadLimited':          ('boolean', 5, None, None, None),
+        'downloadLimitMode':        ('number', 1, 5, None, None),
+        'error':                    ('number', 1, None, None, None),
+        'errorString':              ('number', 1, None, None, None),
+        'eta':                      ('number', 1, None, None, None),
+        'files':                    ('array', 1, None, None, None),
+        'fileStats':                ('array', 5, None, None, None),
+        'hashString':               ('string', 1, None, None, None),
+        'haveUnchecked':            ('number', 1, None, None, None),
+        'haveValid':                ('number', 1, None, None, None),
+        'honorsSessionLimits':      ('boolean', 5, None, None, None),
+        'id':                       ('number', 1, None, None, None),
+        'isPrivate':                ('boolean', 1, None, None, None),
+        'lastAnnounceTime':         ('number', 1, None, None, None),
+        'lastScrapeTime':           ('number', 1, None, None, None),
+        'leechers':                 ('number', 1, None, None, None),
+        'leftUntilDone':            ('number', 1, None, None, None),
+        'manualAnnounceTime':       ('number', 1, None, None, None),
+        'maxConnectedPeers':        ('number', 1, None, None, None),
+        'name':                     ('string', 1, None, None, None),
+        'nextAnnounceTime':         ('number', 1, None, None, None),
+        'nextScrapeTime':           ('number', 1, None, None, None),
+        'peer-limit':               ('number', 5, None, None, None),
+        'peers':                    ('array', 2, None, None, None),
+        'peersConnected':           ('number', 1, None, None, None),
+        'peersFrom':                ('object', 1, None, None, None),
+        'peersGettingFromUs':       ('number', 1, None, None, None),
+        'peersKnown':               ('number', 1, None, None, None),
+        'peersSendingToUs':         ('number', 1, None, None, None),
+        'percentDone':              ('double', 5, None, None, None),
+        'pieces':                   ('string', 5, None, None, None),
+        'pieceCount':               ('number', 1, None, None, None),
+        'pieceSize':                ('number', 1, None, None, None),
+        'priorities':               ('array', 1, None, None, None),
+        'rateDownload':             ('number', 1, None, None, None),
+        'rateUpload':               ('number', 1, None, None, None),
+        'recheckProgress':          ('double', 1, None, None, None),
+        'scrapeResponse':           ('string', 1, None, None, None),
+        'scrapeURL':                ('string', 1, None, None, None),
+        'seeders':                  ('number', 1, None, None, None),
+        'seedRatioLimit':           ('double', 5, None, None, None),
+        'seedRatioMode':            ('number', 5, None, None, None),
+        'sizeWhenDone':             ('number', 1, None, None, None),
+        'startDate':                ('number', 1, None, None, None),
+        'status':                   ('number', 1, None, None, None),
+        'swarmSpeed':               ('number', 1, None, None, None),
+        'timesCompleted':           ('number', 1, None, None, None),
+        'trackers':                 ('array', 1, None, None, None),
+        'totalSize':                ('number', 1, None, None, None),
+        'torrentFile':              ('string', 5, None, None, None),
+        'uploadedEver':             ('number', 1, None, None, None),
+        'uploadLimit':              ('number', 1, None, None, None),
+        'uploadLimitMode':          ('number', 1, 5, None, None),
+        'uploadLimited':            ('boolean', 5, None, None, None),
+        'uploadRatio':              ('double', 1, None, None, None),
+        'wanted':                   ('array', 1, None, None, None),
+        'webseeds':                 ('array', 1, None, None, None),
+        'webseedsSendingToUs':      ('number', 1, None, None, None),
     },
     'set': {
-        'bandwidthPriority':        ('number', 5, None),
-        'downloadLimit':            ('number', 5, None),
-        'downloadLimited':          ('boolean', 5, None),
-        'files-wanted':             ('array', 1, None),
-        'files-unwanted':           ('array', 1, None),
-        'honorsSessionLimits':      ('boolean', 5, None),
-        'ids':                      ('array', 1, None),
-        'peer-limit':               ('number', 1, None),
-        'priority-high':            ('array', 1, None),
-        'priority-low':             ('array', 1, None),
-        'priority-normal':          ('array', 1, None),
-        'seedRatioLimit':           ('double', 5, None),
-        'seedRatioMode':            ('number', 5, None),
-        'speed-limit-down':         ('number', 1, 5,),
-        'speed-limit-down-enabled': ('boolean', 1, 5,),
-        'speed-limit-up':           ('number', 1, 5,),
-        'speed-limit-up-enabled':   ('boolean', 1, 5,),
-        'uploadLimit':              ('number', 5, None),
-        'uploadLimited':            ('boolean', 5, None),
+        'bandwidthPriority':        ('number', 5, None, None, None),
+        'downloadLimit':            ('number', 5, None, 'speed-limit-down', None),
+        'downloadLimited':          ('boolean', 5, None, 'speed-limit-down-enabled', None),
+        'files-wanted':             ('array', 1, None, None, None),
+        'files-unwanted':           ('array', 1, None, None, None),
+        'honorsSessionLimits':      ('boolean', 5, None, None, None),
+        'ids':                      ('array', 1, None, None, None),
+        'peer-limit':               ('number', 1, None, None, None),
+        'priority-high':            ('array', 1, None, None, None),
+        'priority-low':             ('array', 1, None, None, None),
+        'priority-normal':          ('array', 1, None, None, None),
+        'seedRatioLimit':           ('double', 5, None, None, None),
+        'seedRatioMode':            ('number', 5, None, None, None),
+        'speed-limit-down':         ('number', 1, 5, None, 'downloadLimit'),
+        'speed-limit-down-enabled': ('boolean', 1, 5, None, 'downloadLimited'),
+        'speed-limit-up':           ('number', 1, 5, None, 'uploadLimit'),
+        'speed-limit-up-enabled':   ('boolean', 1, 5, None, 'uploadLimited'),
+        'uploadLimit':              ('number', 5, None, 'speed-limit-up', None),
+        'uploadLimited':            ('boolean', 5, None, 'speed-limit-up-enabled', None),
     },
     'add': {
-        'download-dir':             ('string', 1, None),
-        'filename':                 ('string', 1, None),
-        'files-wanted':             ('array', 1, None),
-        'files-unwanted':           ('array', 1, None),
-        'metainfo':                 ('string', 1, None),
-        'paused':                   ('boolean', 1, None),
-        'peer-limit':               ('number', 1, None),
-        'priority-high':            ('array', 1, None),
-        'priority-low':             ('array', 1, None),
-        'priority-normal':          ('array', 1, None),
+        'download-dir':             ('string', 1, None, None, None),
+        'filename':                 ('string', 1, None, None, None),
+        'files-wanted':             ('array', 1, None, None, None),
+        'files-unwanted':           ('array', 1, None, None, None),
+        'metainfo':                 ('string', 1, None, None, None),
+        'paused':                   ('boolean', 1, None, None, None),
+        'peer-limit':               ('number', 1, None, None, None),
+        'priority-high':            ('array', 1, None, None, None),
+        'priority-low':             ('array', 1, None, None, None),
+        'priority-normal':          ('array', 1, None, None, None),
     }
 }
 
-# Arguments for session-set
-# The set describes:
-#   (<type>, <rpc version introduced>, <rpc version removed>, <read/write>)
+# Arguments for session methods
 SESSION_ARGS = {
     'get': {
-        "alt-speed-down":            ('number', 5, None),
-        "alt-speed-enabled":         ('boolean', 5, None),
-        "alt-speed-time-begin":      ('number', 5, None),
-        "alt-speed-time-enabled":    ('boolean', 5, None),
-        "alt-speed-time-end":        ('number', 5, None),
-        "alt-speed-time-day":        ('number', 5, None),
-        "alt-speed-up":              ('number', 5, None),
-        "blocklist-enabled":         ('boolean', 5, None),
-        "blocklist-size":            ('number', 5, None),
-        "encryption":                ('string', 1, None),
-        "download-dir":              ('string', 1, None),
-        "peer-limit":                ('number', 1, 5),
-        "peer-limit-global":         ('number', 5, None),
-        "peer-limit-per-torrent":    ('number', 5, None),
-        "pex-allowed":               ('boolean', 1, 5),
-        "pex-enabled":               ('boolean', 5, None),
-        "port":                      ('number', 1, 5),
-        "peer-port":                 ('number', 5, None),
-        "peer-port-random-on-start": ('boolean', 5, None),
-        "port-forwarding-enabled":   ('boolean', 1, None),
-        "rpc-version":               ('number', 4, None),
-        "rpc-version-minimum":       ('number', 4, None),
-        "seedRatioLimit":            ('double', 5, None),
-        "seedRatioLimited":          ('boolean', 5, None),
-        "speed-limit-down":          ('number', 1, None),
-        "speed-limit-down-enabled":  ('boolean', 1, None),
-        "speed-limit-up":            ('number', 1, None),
-        "speed-limit-up-enabled":    ('boolean', 1, None),
-        "version":                   ('string', 3, None),
+        "alt-speed-down":            ('number', 5, None, None, None),
+        "alt-speed-enabled":         ('boolean', 5, None, None, None),
+        "alt-speed-time-begin":      ('number', 5, None, None, None),
+        "alt-speed-time-enabled":    ('boolean', 5, None, None, None),
+        "alt-speed-time-end":        ('number', 5, None, None, None),
+        "alt-speed-time-day":        ('number', 5, None, None, None),
+        "alt-speed-up":              ('number', 5, None, None, None),
+        "blocklist-enabled":         ('boolean', 5, None, None, None),
+        "blocklist-size":            ('number', 5, None, None, None),
+        "encryption":                ('string', 1, None, None, None),
+        "download-dir":              ('string', 1, None, None, None),
+        "peer-limit":                ('number', 1, 5, None, None),
+        "peer-limit-global":         ('number', 5, None, None, None),
+        "peer-limit-per-torrent":    ('number', 5, None, None, None),
+        "pex-allowed":               ('boolean', 1, 5, None, None),
+        "pex-enabled":               ('boolean', 5, None, None, None),
+        "port":                      ('number', 1, 5, None, None),
+        "peer-port":                 ('number', 5, None, None, None),
+        "peer-port-random-on-start": ('boolean', 5, None, None, None),
+        "port-forwarding-enabled":   ('boolean', 1, None, None, None),
+        "rpc-version":               ('number', 4, None, None, None),
+        "rpc-version-minimum":       ('number', 4, None, None, None),
+        "seedRatioLimit":            ('double', 5, None, None, None),
+        "seedRatioLimited":          ('boolean', 5, None, None, None),
+        "speed-limit-down":          ('number', 1, None, None, None),
+        "speed-limit-down-enabled":  ('boolean', 1, None, None, None),
+        "speed-limit-up":            ('number', 1, None, None, None),
+        "speed-limit-up-enabled":    ('boolean', 1, None, None, None),
+        "version":                   ('string', 3, None, None, None),
     },
     'set': {
-        "alt-speed-down":            ('number', 5, None),
-        "alt-speed-enabled":         ('boolean', 5, None),
-        "alt-speed-time-begin":      ('number', 5, None),
-        "alt-speed-time-enabled":    ('boolean', 5, None),
-        "alt-speed-time-end":        ('number', 5, None),
-        "alt-speed-time-day":        ('number', 5, None),
-        "alt-speed-up":              ('number', 5, None),
-        "blocklist-enabled":         ('boolean', 5, None),
-        "blocklist-size":            ('number', 5, None),
-        "encryption":                ('string', 1, None),
-        "download-dir":              ('string', 1, None),
-        "peer-limit":                ('number', 1, 5),
-        "peer-limit-global":         ('number', 5, None),
-        "peer-limit-per-torrent":    ('number', 5, None),
-        "pex-allowed":               ('boolean', 1, 5),
-        "pex-enabled":               ('boolean', 5, None),
-        "port":                      ('number', 1, 5),
-        "peer-port":                 ('number', 5, None),
-        "peer-port-random-on-start": ('boolean', 5, None),
-        "port-forwarding-enabled":   ('boolean', 1, None),
-        "seedRatioLimit":            ('double', 5, None),
-        "seedRatioLimited":          ('boolean', 5, None),
-        "speed-limit-down":          ('number', 1, None),
-        "speed-limit-down-enabled":  ('boolean', 1, None),
-        "speed-limit-up":            ('number', 1, None),
-        "speed-limit-up-enabled":    ('boolean', 1, None),
+        "alt-speed-down":            ('number', 5, None, None, None),
+        "alt-speed-enabled":         ('boolean', 5, None, None, None),
+        "alt-speed-time-begin":      ('number', 5, None, None, None),
+        "alt-speed-time-enabled":    ('boolean', 5, None, None, None),
+        "alt-speed-time-end":        ('number', 5, None, None, None),
+        "alt-speed-time-day":        ('number', 5, None, None, None),
+        "alt-speed-up":              ('number', 5, None, None, None),
+        "blocklist-enabled":         ('boolean', 5, None, None, None),
+        "encryption":                ('string', 1, None, None, None),
+        "download-dir":              ('string', 1, None, None, None),
+        "peer-limit":                ('number', 1, 5, None, 'peer-limit-global'),
+        "peer-limit-global":         ('number', 5, None, 'peer-limit', None),
+        "peer-limit-per-torrent":    ('number', 5, None, None, None),
+        "pex-allowed":               ('boolean', 1, 5, None, 'pex-enabled'),
+        "pex-enabled":               ('boolean', 5, None, 'pex-allowed', None),
+        "port":                      ('number', 1, 5, None, 'peer-port'),
+        "peer-port":                 ('number', 5, None, 'port', None),
+        "peer-port-random-on-start": ('boolean', 5, None, None, None),
+        "port-forwarding-enabled":   ('boolean', 1, None, None, None),
+        "seedRatioLimit":            ('double', 5, None, None, None),
+        "seedRatioLimited":          ('boolean', 5, None, None, None),
+        "speed-limit-down":          ('number', 1, None, None, None),
+        "speed-limit-down-enabled":  ('boolean', 1, None, None, None),
+        "speed-limit-up":            ('number', 1, None, None, None),
+        "speed-limit-up-enabled":    ('boolean', 1, None, None, None),
     },
 }

File transmissionrpc/transmission.py

 # 2008-07, Erik Svensson <erik.public@gmail.com>
 
 import sys, os, time, datetime
-import re, logging
+import re
 import httplib, urllib2, base64, socket
 
 try:
 from constants import *
 from utils import *
 
-logger = logging.getLogger('transmissionrpc')
-logger.setLevel(logging.ERROR)
-
 class TransmissionError(Exception):
     def __init__(self, message='', original=None):
         Exception.__init__(self, message)
+        self.message = message
         self.original = original
 
+    def __str__(self):
+        if self.original:
+            original_name = type(self.original).__name__
+            return '%s Original exception: %s, "%s"' % (self.message, original_name, self.original.args)
+        else:
+            return self.args
+
 class Torrent(object):
     """
     Torrent is a class holding the data raceived from Transmission regarding a bittorrent transfer.
                 if error.code == 409:
                     logger.info('Server responded with 409, trying to set session-id.')
                     if request_count > 1:
-                        raise TransmissionError('Session ID negotiation failed with %s.' % (error), error)
+                        raise TransmissionError('Session ID negotiation failed.', error)
                     if 'X-Transmission-Session-Id' in error.headers:
                         self.sessionid = error.headers['X-Transmission-Session-Id']
                         request.add_header('X-Transmission-Session-Id', self.sessionid)
                     else:
-                        raise TransmissionError('Unknown conflict %s.' % (error), error)
+                        raise TransmissionError('Unknown conflict.', error)
             except urllib2.URLError, error:
-                raise TransmissionError('Failed to connect to daemon %s.' % (error), error)
+                raise TransmissionError('Failed to connect to daemon.', error)
             except httplib.BadStatusLine, error:
                 if (request_count > 1):
-                    raise TransmissionError('Server responded with: "%s" when requesting %s "%s".' % (error.args, self.url, query), error)
+                    raise TransmissionError('Failed to request %s "%s".' % (self.url, query), error)
             finally:
                 if error_data:
                     self._debug_response(error, error_data)

File transmissionrpc/utils.py

 
 import socket, datetime
 import constants
+from constants import logger
 
 UNITS = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB']
 
         invalid_version = True
         while invalid_version:
             invalid_version = False
+            replacement = None
             if rpc_version < info[1]:
                 invalid_version = True
+                replacement = info[3]
             if info[2] and info[2] <= rpc_version:
                 invalid_version = True
+                replacement = info[4]
             if invalid_version:
-                if len(info) < 4:
+                if replacement:
+                    logger.warning(
+                        'Replacing requested argument "%s" with "%s".'
+                        % (argument, replacement))
+                    argument = replacement
+                    info = args[argument]
+                else:
                     raise ValueError(
                         'Method "%s" Argument "%s" does not exist in version %d.'
                         % (method, argument, rpc_version))
-                else:
-                    argument = info[3]
-                    info = args[argument]
         return (argument, TR_TYPE_MAP[info[0]](value))
     else:
         raise ValueError('Argument "%s" does not exists for method "%s".',
         if valid_version:
             accessible.append(argument)
     return accessible
-
-