I’m fairly new to perforce and this question may be a bit nit-picky, but I’m curious if there is a “right”/preferred way to do this.
I want a copy files, and submit them in a non-default changelist. Do you create the changelist first and parse it’s id from the “Changelist #### created” return value and pass that to the copy command? Or do you store all the files you want to copy and create/submit a new changelist at the end?
change = p4.fetch_change()
change['Files'] = []
change['Description'] = 'Copy test'
result = p4.save_change(change)
changeid = int(result[0].split()[1])
for src, dst in files:
p4.run_copy('-c', changeid, src, dst)
I generally try to avoid parsing information from strings when I can in case that return value changes somewhere down the line, but when using the second method I have to make sure I know which files will be added to a changelist, and the unicode/string fickleness of the changelist info is odd.
def create_changelist( message ):
'''
Creates a changelist with the supplied message
'''
p4 = connection( None )
tryConnect( p4 )
desc = {"Description": message, "Change": "new"}
p4.input = desc
res = p4.run( "change", "-i" )
if res:
num = res[0].split()[1]
return p4.fetch_changelist( int( num ) )
raise P4.P4Exception( "Could not create changelist" )
That gives me a handle to the new changelist as a dictionary. All the calls run through a wrapper that handles adding the -c flag and the changelistID id the changelist is passed as a an argument:
Couple more P4 questions.
How do you handle connecting to the perforce server? Do you have a global perforce variable that you access (from connection in your sample)? Do you stay connected to the perforce server for the duration of a script? I’ve started using P4.P4() with a with statement, which means I’m connecting/disconnecting at least once in each function that I make p4 calls with. Is that a bad idea (don’t know what overhead there is with connecting/disconnecting)? Do you do that as well or maintain a constant/retrievable connection/instance or something else?
Do you catch and parse P4Exceptions at all? It’s annoying having only one big error class which could be thrown for a multitude of reasons. Do you catch it assuming that it’s being thrown for reason X? Parse the errors/warnings that result from the error? Or just let all P4Exceptions kill the script? There seem to be a couple instances (file existence checks with fstat/files) where it’s necessary to catch the error, so you’ll have to catch them in some cases at least.
Have you written much to extend the p4api? Any suggestions on extensions/implementations that have been particularly useful, or areas/processes that are complex and common enough that it warrants wrapping their functionality?
The sparse amount of code written to extend the p4api here is kind of hacky and simplistic, so I’d like to get some p4 tools written The Right Way.
All my wrappers take an optional p4 instance. If the instance is not presence I spin one up and discard it the same way you do; if it is present I just use it. That lets me pass one connection around between functions if I want to keep a connection alive for a long time. In general p4 likes a small number of big queries rather than a lot of little ones.
I usually pass the excptions on from the wrapper calls - sometimes you care, sometimes you dont, and the job your doing , rather than the p4 library, should decide what’s worth making a stink about and what’s not I do sometimes use a contextManager to set the p4.exceptionLevel variable depending on what I’m doing - it can be useful to flag errors that way (and irritating to get errors on harmless informatinoal queries) but it’s also good practice not to change the global state of the perforce library willy-nilly.
In the past i’ve gone nuts trying to make a more pythonic, more logical api - but it’s not worth it most of the time. I’m stuck in the rut where I’m so used to p4’s bullshit that I work around it, try to stick to native types (dicts and lists), and in general try not to solve their lame 1990’s vintage api for them. Don’t even get me started on stuff like asking a question about 5 files and getting 21 different responses !
How do you handle the p4 instance in the calling script? If you have a script with multiple functions that all make p4 calls, do you have all of those functions accept a p4 instance so the main function could create the p4inst and then pass it around? And then each sub-function would pass it to the wrappers? Do you have a global p4 instance that gets connected once and each function then uses that for any calls? Or maybe a getP4inst function that creates the global instance if it doesn’t exist or returns the existing one?
–EDIT–
Like is something like this overkill to do for all methods that make p4 calls??
def main():
p4 = P4.P4(port='server:port')
p4.connect()
data = p4.command()
# Do stuff with data
sub_1(data, p4inst=p4)
def sub_1(data, p4inst=None):
p4 = getP4inst(p4inst, port='server:port')
with p4Connect(p4inst) as p4:
res = p4.command()
# Do stuff with res
return res
def getP4inst(p4inst, **kwargs):
if p4inst is None:
return P4.P4(**kwargs)
return p4inst
class p4Connect(object):
"""
Context manager that connects a p4 instance on entry if it is not connected
and disconnects on exit if it wasn't connected.
"""
def __init__(self, p4inst):
self.p4inst = p4inst
def __enter__(self):
self.isConnected = self.p4inst.connected()
if not self.isConnected:
self.p4inst.connect()
return self.p4inst
def __exit__(self, type, value, tb):
if not self.isConnected:
# Only disconnect if a connection was made in the __enter__ method
self.p4inst.disconnect()
The context manager is a very elegant way to do it, wish I’d thought of it. It’s not overkill at all, it is a nice way of avoiding manual BS that’s not part of the program flow.
I’ve been enjoying context managers a lot lately. Probably too much, as is often the case when employing newly learned techniques. But they’ve been super convenient for tasks where I want to temporarily alter the state of a maya node, like disconnecting incoming connections, or disabling all targets on a blendshape except one, or disabling all deformers in a node’s history.
So you prefer passing around instances vs setting and retrieving a single global instance in each script? Passing instances certainly offers more flexibility, as you can supply functions with connections that are using different setings; it just feels so unpythonic. Though I suppose I should take your advice and just learn to work with the p4 api as it is :).
The reason i pass instances is so I can switch between instance-sharing and connect-fire-disconnect as the situation demands - if I need to share a connection I keep it around and manage it directly, otherwise the wrappers will connect on their own. My big kick is trying to keep may local code lower level and be good about not trying to solve all future problems in advance, so I just punt on the relative merits of the two strategies
Though it’s not exactly the same since the p4inst.connect CM will always disconnect on exit (as far as I can tell), regardless of whether the instance had an active connection prior to entering the CM.