
First, the test setup: we'll set up a repository and local checkout.
Sven won't do this for you:

   >>> repo_dir = '/tmp/svensvn/'; checkout_dir = '/tmp/svencheckout'; other_checkoutdir = '/tmp/svenothercheckout'
   >>> import subprocess
   >>> subprocess.call(['rm', '-rf', repo_dir, checkout_dir, other_checkoutdir])
   0
   >>> subprocess.call(['svnadmin', 'create', repo_dir])
   0
   >>> subprocess.call(['svn', 'co', 'file://%s'%repo_dir, checkout_dir])
   0

Instantiate an object that knows about your local checkout directory:

   >>> from sven.backend import SvnAccess
   >>> client = SvnAccess(checkout_dir)

Sven throws its own flavors of IOErrors to deal with various edge cases of
interacting with the repository. For instance, trying to get metadata on
a resource that doesn't exist:

   >>> client.last_changed_rev('/path/to/file')
   Traceback (most recent call last):
   ...
   NoSuchResource: [Errno 2] No such file or directory: 'path/to/file'

The same error is thrown if you're looking for a particular revision:

   >>> client.last_changed_rev('/path/to/file', rev=4)
   Traceback (most recent call last):
   ...
   NoSuchResource: [Errno 2] No such file or directory: 'path/to/file'

Or if you're trying to get the contents of a resource:

   >>> client.read('/path/to/file')
   Traceback (most recent call last):
   ...
   NoSuchResource: [Errno 2] No such file or directory: 'path/to/file'

   >>> client.read('/path/to/file', rev=4)
   Traceback (most recent call last):
   ...
   NoSuchResource: [Errno 2] No such file or directory: 'path/to/file'

Now let's actually save a file. Sven's workflow encourages you to think
of your repository as a filesystem; you write to a file and commit it
to the repository in a single step:
   
   >>> client.write('file', "first version")
   <Revision kind=number 1>

Sven returns a pysvn.Revision object with information about the changeset
you just committed.

Sven will create all necessary directories based on the resource path[1]:

   >>> client.write('/path/to/file', "a second versioned content")
   <Revision kind=number 3>

Wait! Why is this revision 3 instead of revision 2? The answer is that,
when sven discovers it needs to create intermediate directories, it adds
and checks in these directories in an internal commit. This may change
in a later version of sven, so don't depend on it too much (but why would
you?)

You commit changes to an existing file in the same way as saving a new file:

   >>> client.write('/file', "second version")
   <Revision kind=number 4>

Now that we have some resources, we can finally start reading them too:

   >>> client.read('file')
   {'body': 'second version\n', 'mimetype': None}

And we can read previous versions as well, by passing in the desired
global revision number:

   >>> client.read('file', rev=1)
   {'body': 'first version\n', 'mimetype': None}

Sven will throw a different flavor of IOError if you try to read content
from a path that exists, but is a directory instead of a file:

   >>> client.read('/path')
   Traceback (most recent call last):
   ...
   NotAFile: [Errno 21] Is a directory: 'path'

To read the most recent version of a resource, don't pass in any revision:

   >>> client.read('path/to/file')
   {'body': 'a second versioned content\n', 'mimetype': None}

But if you do pass in a revision, sven will tell you if the file was unchanged
in that revision, with a custom exception:

   >>> client.read('path/to/file', rev=4)
   Traceback (most recent call last):
   ...
   ResourceUnchanged: Resource 'path/to/file' unchanged since 3

This ResourceUnchaged exception contains the revision number of the last
change made to the file:

   >>> from sven.exc import ResourceUnchanged
   >>> try:
   ...     client.read('path/to/file', rev=4)
   ... except ResourceUnchanged, e:
   ...     print "last changed at r%d" % e.last_change
   last changed at r3
   
   >>> client.read('path/to/file', rev=3)
   {'body': 'a second versioned content\n', 'mimetype': None}

You cannot overwrite directories with files, of course:

   >>> client.write('/path/to', "i'm gonna clobber this directory with a file!")
   Traceback (most recent call last):
   ...
   NotAFile: [Errno 21] Is a directory: 'path/to'

When writing a file, you can pass in a commit message, and you can also
set the file's svn:mime-type property:

   >>> client.write('file', "now with metadata",
   ...              msg="Changed the content",
   ...              mimetype="text/plain")
   <Revision kind=number 5>

   >>> client.read('file')
   {'body': 'now with metadata\n', 'mimetype': 'text/plain'}

Great work!

[1] "Sven will create all necessary directories based on the resource path"
This is actually tricky -- so let's exercise it in a couple of cases:

   >>> client.write('a_file_at_the_root', "foo")
   <Revision kind=number 6>
   >>> client.write('one_level/deep', "foo")
   <Revision kind=number 8>
   >>> client.write('one_level/again', "foo")
   <Revision kind=number 9>
   >>> client.write('totally/nonexistent/path_to/a_file', "foo")
   <Revision kind=number 11>
   >>> client.write('totally/partly/extant_path/to_a_file', "foo")
   <Revision kind=number 13>
   >>> client.write('totally/partly/extant/path/to_another_file', "foo")
   <Revision kind=number 15>
   >>> client.write('a_file_at_the_root', "foobar")
   <Revision kind=number 16>
   >>> client.write('one_level/deep', "foobar")
   <Revision kind=number 17>
   >>> client.write('one_level/again', "foobar")
   <Revision kind=number 18>
   >>> client.write('totally/nonexistent/path_to/a_file', "foobar")
   <Revision kind=number 19>
   >>> client.write('totally/partly/extant_path/to_a_file', "foobar")
   <Revision kind=number 20>
   >>> client.write('totally/partly/extant/path/to_another_file', "foobar")
   <Revision kind=number 21>

Sven tries to think of your repository-backed filesystem as basically
stateless -- a sort of RESTful approach to versioning. Because of this,
sven tries to make version conflicts just sort of melt away by always
clobbering the repository's copy with the client's new copy:

   >>> subprocess.call(['svn', 'co', 'file://%s'%repo_dir, other_checkoutdir])
   0

   >>> client2 = SvnAccess(other_checkoutdir)
   >>> client.write('file', "version 1\n2\n3")
   <Revision kind=number 22>
   >>> client2.write('file', 'feeble feeblefeed')
   <Revision kind=number 23>
   >>> client.write('file', "version 1\n5\n3")
   <Revision kind=number 24>

   >>> client.read('file')['body']
   'version 1\n5\n3\n'

   client2.read('file')['body']
   'feeble feeblefeed\n'

Synchronization errors can still occur if you tell sven not to
keep files up-to-date before writing, though:

   >>> from sven.exc import ResourceChanged
   >>> client2.write('file', 'moobly moo', update_before_write=False)
   Traceback (most recent call last):
   ...
   ResourceChanged: Resource 'file' is out of date

   >>> client.write('file', "version 1\n2\n3", update_before_write=False)
   <Revision kind=number 25>

So there are a couple of approaches possible here if you prefer to keep
auto-updates off for performance reasons. One simple approach would be
to assume that writes will succeed until proven otherwise, while retaining
sven's natural inclination to clobber conflicting changesets, viz.:

   >>> try:
   ...     client2.write('file', 'booga booga boo', update_before_write=False)
   ... except ResourceChanged:
   ...     client2.write('file', 'booga booga boo', update_before_write=True)
   <Revision kind=number 26>

