Skip to content

Commit bf7146a

Browse files
2024.1 release code drop
This release of P4Python supports build against P4API 2024.1
1 parent 87045d5 commit bf7146a

16 files changed

+380
-417
lines changed

LICENSE.txt

Lines changed: 12 additions & 320 deletions
Large diffs are not rendered by default.

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ include Version
55
include p4test.py
66
include job_trigger.py
77
include tools/*.py
8+
include pyproject.toml

P4.py

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
This uses the Python type P4API.P4Adapter, which is a wrapper for the
88
Perforce ClientApi object.
99
10-
$Id: //depot/main/p4-python/P4.py#110 $
10+
$Id: //depot/main/p4-python/P4.py#112 $
1111
1212
#*******************************************************************************
1313
# Copyright (c) 2007-2010, Perforce Software, Inc. All rights reserved.
@@ -43,30 +43,43 @@
4343
See accompanying LICENSE.txt including for redistribution permission.
4444
"""
4545

46-
import sys, datetime
46+
import sys, datetime, time
4747
import re
4848
import shutil
4949
from contextlib import contextmanager
5050
import uuid, tempfile
5151
import os, os.path, platform
5252
import subprocess
53+
import threading
5354

5455
# P4Exception - some sort of error occurred
5556
class P4Exception(Exception):
5657
"""Exception thrown by P4 in case of Perforce errors or warnings"""
57-
58+
5859
def __init__(self, value):
59-
Exception.__init__(self)
60-
60+
super().__init__(value)
6161
if isinstance(value, (list, tuple)) and len(value) > 2:
62-
self.value = value[0]
63-
self.errors = value[1]
64-
self.warnings = value[2]
62+
if len(value[1]) > 0 or len(value[2]) > 0:
63+
self.value = value[0]
64+
self.errors = value[1]
65+
self.warnings = value[2]
66+
else:
67+
self.value = value[0]
68+
self.errors = [re.sub(r'\[.*?\] ', '', str(self.value).split("\n")[0])]
69+
self.warnings = value[2]
6570
else:
6671
self.value = value
6772

6873
def __str__(self):
69-
return str(self.value)
74+
if self.errors:
75+
return str(self.errors[0])
76+
elif self.warnings:
77+
return str(self.warnings[0])
78+
else:
79+
return re.sub(r'\[.*?\] ', '', str(self.value).split("\n")[0])
80+
81+
def __repr__(self):
82+
return f"{self.__class__.__name__}({str(self)!r})"
7083

7184
def __reduce__(self):
7285
if hasattr(self, 'errors'):
@@ -1103,6 +1116,42 @@ def __check_version(pathToFile):
11031116
raise Exception("{} must be at least 2015.1, not {}".format(program, version))
11041117

11051118
raise Exception("Unknown P4 output : {}".format(output) )
1119+
1120+
1121+
class PyKeepAlive:
1122+
def __init__(self):
1123+
self.__thread = None
1124+
self.__alive = 1
1125+
1126+
def isAlive(self):
1127+
return self.__alive
1128+
1129+
# private member function
1130+
def __poll(self):
1131+
while True:
1132+
if self.isAlive() == 0:
1133+
self.__alive = 0
1134+
return 0
1135+
time.sleep(0.5)
1136+
1137+
# private member function
1138+
def __executeAll(self):
1139+
if self.__thread and self.__thread.is_alive():
1140+
pass
1141+
1142+
elif self.__thread and not self.__thread.is_alive():
1143+
self.__thread.join()
1144+
1145+
else:
1146+
self.__thread = threading.Thread(target=self.__poll)
1147+
self.__thread.daemon = True
1148+
self.__thread.start()
1149+
1150+
return self.__alive
1151+
1152+
def __call__(self):
1153+
return self.__executeAll()
1154+
11061155

11071156
if __name__ == "__main__":
11081157
p4 = P4()

P4API.cpp

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
2828
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2929
*
30-
* $Id: //depot/main/p4-python/P4API.cpp#63 $
30+
* $Id: //depot/main/p4-python/P4API.cpp#64 $
3131
*
3232
* Build instructions:
3333
* Use Distutils - see accompanying setup.py
@@ -57,6 +57,7 @@
5757
#include "PythonMessage.h"
5858
#include "PythonTypes.h"
5959
#include "debug.h"
60+
#include "PythonKeepAlive.h"
6061

6162
// #include <alloca.h>
6263

@@ -586,6 +587,31 @@ static PyObject * P4Adapter_getTunable(P4Adapter * self, PyObject *args)
586587
return NULL;
587588
}
588589

590+
// ==================
591+
// ==== SetBreak ====
592+
// ==================
593+
594+
static PyObject* P4Adapter_setBreak(P4Adapter* self, PyObject* args) {
595+
PyObject* py_callable;
596+
597+
// Parse the arguments
598+
if (!PyArg_ParseTuple(args, "O", &py_callable)) {
599+
return NULL;
600+
}
601+
602+
// Check if the parsed object is callable
603+
if (!PyCallable_Check(py_callable)) {
604+
PyErr_SetString(PyExc_TypeError, "parameter must be callable");
605+
return NULL;
606+
}
607+
608+
// Create an object pointer of PythonKeepAlive and pass py_callable
609+
PythonKeepAlive* cb = new PythonKeepAlive(py_callable);
610+
self->clientAPI->SetBreak(cb);
611+
612+
Py_RETURN_NONE;
613+
}
614+
589615
#if PY_MAJOR_VERSION >= 3
590616

591617
static PyObject * P4Adapter_convert(P4Adapter * self, PyObject *args)
@@ -631,6 +657,8 @@ static PyMethodDef P4Adapter_methods[] = {
631657
"Sets a tunable to the specified value"},
632658
{"get_tunable", (PyCFunction)P4Adapter_getTunable, METH_VARARGS,
633659
"Returns the value for this tunable or 0"},
660+
{"setbreak", (PyCFunction)P4Adapter_setBreak, METH_VARARGS,
661+
"Set the break callback"},
634662

635663
#if PY_MAJOR_VERSION >= 3
636664
{"__convert", (PyCFunction)P4Adapter_convert, METH_VARARGS,

PythonClientAPI.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2525
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
2626
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2727
28-
$Id: //depot/main/p4-python/PythonClientAPI.cpp#78 $
28+
$Id: //depot/main/p4-python/PythonClientAPI.cpp#79 $
2929
*******************************************************************************/
3030

3131
#include <Python.h>
@@ -1266,3 +1266,12 @@ void PythonClientAPI::RunCmd(const char *cmd, ClientUser *ui, int argc, char * c
12661266
SetCmdRun();
12671267

12681268
}
1269+
1270+
void PythonClientAPI::SetBreak(PythonKeepAlive* cb)
1271+
{
1272+
if( IsConnected() ) {
1273+
debug.debug(P4PYDBG_COMMANDS, "[P4] Establish the callback" );
1274+
client.SetBreak(cb);
1275+
}
1276+
1277+
}

PythonClientAPI.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
2727
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2828
*
29-
* $Id: //depot/main/p4-python/PythonClientAPI.h#43 $
29+
* $Id: //depot/main/p4-python/PythonClientAPI.h#44 $
3030
*
3131
* Build instructions:
3232
* Use Distutils - see accompanying setup.py
@@ -38,6 +38,8 @@
3838
#ifndef PYTHON_CLIENT_API_H
3939
#define PYTHON_CLIENT_API_H
4040

41+
#include "PythonKeepAlive.h"
42+
4143
class Enviro;
4244
class PythonClientAPI
4345
{
@@ -232,6 +234,9 @@ class PythonClientAPI
232234
void Except( const char *func, Error *e );
233235
void Except( const char *func, const char *msg, const char *cmd );
234236

237+
// SetBreak
238+
void SetBreak( PythonKeepAlive* cb );
239+
235240
public:
236241
// setter/getter methods and attributes
237242

PythonKeepAlive.cpp

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*******************************************************************************
2+
3+
Copyright (c) 2024, Perforce Software, Inc. All rights reserved.
4+
5+
Redistribution and use in source and binary forms, with or without
6+
modification, are permitted provided that the following conditions are met:
7+
8+
1. Redistributions of source code must retain the above copyright
9+
notice, this list of conditions and the following disclaimer.
10+
11+
2. Redistributions in binary form must reproduce the above copyright
12+
notice, this list of conditions and the following disclaimer in the
13+
documentation and/or other materials provided with the distribution.
14+
15+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18+
ARE DISCLAIMED. IN NO EVENT SHALL PERFORCE SOFTWARE, INC. BE LIABLE FOR ANY
19+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25+
26+
*******************************************************************************/
27+
28+
/*******************************************************************************
29+
* Name : PythonKeepAlive.cpp
30+
*
31+
* Author : Himanshu Jain <[email protected]>
32+
*
33+
* Description : C++ subclass for KeepAlive used by Python KeepAlive.
34+
* Allows Perforce API to implement a customized IsAlive function
35+
*
36+
* $Id: //depot/main/p4-python/PythonKeepAlive.cpp#1 $
37+
*
38+
******************************************************************************/
39+
40+
#include <Python.h>
41+
#include <keepalive.h>
42+
#include "PythonThreadGuard.h"
43+
#include "PythonKeepAlive.h"
44+
45+
PythonKeepAlive::PythonKeepAlive(PyObject* callable) : py_callable(callable) {
46+
// Increment the reference count for the callable
47+
Py_XINCREF(py_callable);
48+
}
49+
50+
PythonKeepAlive::~PythonKeepAlive() {
51+
// Decrement the reference count for the callable
52+
Py_XDECREF(py_callable);
53+
}
54+
55+
int PythonKeepAlive::IsAlive() {
56+
57+
EnsurePythonLock guard;
58+
59+
if (py_callable && PyCallable_Check(py_callable)) {
60+
PyObject* result = PyObject_CallObject(py_callable, NULL);
61+
if (result != NULL && PyLong_Check(result) && PyLong_AsLong(result) == 0) {
62+
Py_DECREF(result);
63+
return( 0 ); // To terminate the connection
64+
}
65+
66+
return( 1 );
67+
}
68+
69+
return( 1 ); // Default to true if callable is not set or failed
70+
}

PythonKeepAlive.h

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* PythonKeepAlive. Subclass of P4::KeepAlive
3+
*
4+
* Copyright (c) 2024, Perforce Software, Inc. All rights reserved.
5+
*
6+
* Redistribution and use in source and binary forms, with or without
7+
* modification, are permitted provided that the following conditions are met:
8+
*
9+
* 1. Redistributions of source code must retain the above copyright
10+
* notice, this list of conditions and the following disclaimer.
11+
*
12+
* 2. Redistributions in binary form must reproduce the above copyright
13+
* notice, this list of conditions and the following disclaimer in the
14+
* documentation and/or other materials provided with the distribution.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+
* ARE DISCLAIMED. IN NO EVENT SHALL PERFORCE SOFTWARE, INC. BE LIABLE FOR ANY
20+
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21+
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22+
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23+
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25+
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26+
*
27+
* $Id: //depot/main/p4-python/PythonKeepAlive.h#1 $
28+
*
29+
*/
30+
31+
#ifndef PYTHON_KEEP_ALIVE_H
32+
#define PYTHON_KEEP_ALIVE_H
33+
34+
class PythonKeepAlive : public KeepAlive {
35+
public:
36+
PythonKeepAlive(PyObject* callable);
37+
~PythonKeepAlive();
38+
39+
virtual int IsAlive() override;
40+
virtual int PollMs() override { return 0; }
41+
42+
private:
43+
PyObject* py_callable;
44+
};
45+
46+
#endif // PYTHON_KEEP_ALIVE_H

0 commit comments

Comments
 (0)