May 20, 2015

P4Python and the “with” Statement

Integration

Python 2.5 gained the with statement in 2005, implementing PEP 343. The statement was designed to factor out the use of try-finally blocks and is often used to control the use of resources. A standard idiom, for example, is to use a with block when opening a file:

  with open(“somefile.txt”,”r”) as f:
	  content = f.read()

As soon as you leave the block opened by the with statement, the resource (in this case the file handle) gets closed, even in the case of exceptions.

To facilitate this, the resource needs to implement two methods: __enter__() and __exit__(), either explicitly or through the help of a context manager, usually with the annotation @contextmanager.

If you look through P4.py, the Python part wrapper of P4Python, you’ll find both the __enter__() and __exit__() methods as well as several context managers implemented. And you might ask yourself: what are they good for? The answer is that they allow you to write this:

    with p4.connect():
	    p4.run_info()

This snippet will close the connection to the server as soon as the block is finished. Methods annotated with @contextmanager are listed here:

Methods tagged with @contextmanager
MethodUsage
while_tagged(t)Run code in block either tagged or untagged
at_exception_level(e)Run code at the specified exception handling
using_handler(h)Run code using the specified OutputHandler
saved_context(**kargs)Run code with modified context, for example different user or workspace
temp_client(prefix, template)Create a temporary client, run code with this client

In all cases, the original values are restored when the block is left.

For individual statements you don’t actually need any of these as all run() commands support the use of keyword arguments to override set values. For example, the following three statements are equivalent:

# Explicit
  saved = p4.exception_level
  p4.exception_level = 0
  try:
	p4.run_sync(“...”)
  finally:
	p4.exception_level = saved

# Using with statement
  with p4.at_exception_level(0):
	p4.run_sync(“...”)

# Using keyword argument
  p4.run_sync(“...”, exception_level=1)

I’ll let you be the judge of which statement you prefer☺. 

Oh, in case you wondered: p4.temp_client() returns (technically yields) the workspace, so you can write this:

  with p4.temp_client(“temp”, template_ws) as ws:
	p4.run_sync() # uses the temporary workspace

The new workspace is automatically assigned to p4.client, so you can immediately work in the new workspace. The view and options of the new workspace are copied from the supplied template workspace. Note well that p4.cwd is still pointing to your current working directory instead of the temporary workspace root, but I will fix this in the next release.

After completion, the workspace is reset to the original workspace, and the temporary workspace is deleted on the server and wiped from your local disk. The prefix (here “temp”) is handy if you run a spec depot and use a SpecMap to exclude certain workspaces from being preserve (look at this great post by Bryan Pendleton for more details).

Happy hacking!