p4async 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- p4async/__init__.py +160 -0
- p4async/py.typed +0 -0
- p4async-0.1.1.dist-info/METADATA +48 -0
- p4async-0.1.1.dist-info/RECORD +6 -0
- p4async-0.1.1.dist-info/WHEEL +4 -0
- p4async-0.1.1.dist-info/licenses/LICENSE +21 -0
p4async/__init__.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import threading
|
|
3
|
+
from typing import Any, Callable, Set, Type, Awaitable, Optional, Union
|
|
4
|
+
import P4
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class P4Async(P4.P4):
|
|
8
|
+
"""
|
|
9
|
+
An async extension to P4.P4.
|
|
10
|
+
This class is a placeholder for future async methods.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
# Methods that are simply wrapped to a thread for async execution. These are methods
|
|
14
|
+
# that do not use `self.run` for execution.
|
|
15
|
+
simple_wrap_methods: Set[str] = {
|
|
16
|
+
"connect",
|
|
17
|
+
"disconnect",
|
|
18
|
+
"run_tickets",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
# Methods that are wrapped by adding `with_async=True` to the underlying
|
|
22
|
+
# call to 'self.run', leaving other processing to happen in the current Task.
|
|
23
|
+
run_wrap_methods: Set[str] = {
|
|
24
|
+
"run_submit",
|
|
25
|
+
"run_shelve",
|
|
26
|
+
"delete_shelve",
|
|
27
|
+
"run_login",
|
|
28
|
+
"run_password",
|
|
29
|
+
"run_filelog",
|
|
30
|
+
"run_print",
|
|
31
|
+
"run_resolve",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
35
|
+
super().__init__(*args, **kwargs)
|
|
36
|
+
# A lock to synchronize access to the P4 adapter.
|
|
37
|
+
# The P4 adapter can only have one operation executing at a time.
|
|
38
|
+
self.lock = threading.Lock()
|
|
39
|
+
|
|
40
|
+
async def execute(
|
|
41
|
+
self, func: Callable[..., Any], *args: Any, **kwargs: Any
|
|
42
|
+
) -> Any:
|
|
43
|
+
"""
|
|
44
|
+
Default method to run a synchronous Perforce command.
|
|
45
|
+
Override this method for customized thread scheduling.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def helper():
|
|
49
|
+
# Ensure thread safety: the P4 adapter can only have one operation at a time.
|
|
50
|
+
with self.lock:
|
|
51
|
+
return func(*args, **kwargs)
|
|
52
|
+
|
|
53
|
+
loop = asyncio.get_running_loop()
|
|
54
|
+
return await loop.run_in_executor(None, helper)
|
|
55
|
+
|
|
56
|
+
def run(
|
|
57
|
+
self, *args: Any, with_async: bool = False, **kwargs: Any
|
|
58
|
+
) -> Union[Any, Awaitable[Any]]:
|
|
59
|
+
"""
|
|
60
|
+
Run a Perforce command.
|
|
61
|
+
|
|
62
|
+
If `with_async` is True, the command will return an awaitable that can be awaited
|
|
63
|
+
to execute the command asynchronously. If `with_async` is False, the command will
|
|
64
|
+
execute directly in the current thread.
|
|
65
|
+
"""
|
|
66
|
+
if with_async:
|
|
67
|
+
# return an awaitable
|
|
68
|
+
return self.execute(self.sync_run, *args, **kwargs)
|
|
69
|
+
else:
|
|
70
|
+
# execute directly
|
|
71
|
+
return self.sync_run(*args, **kwargs)
|
|
72
|
+
|
|
73
|
+
def sync_run(self, *args: Any, **kwargs: Any) -> Any:
|
|
74
|
+
"""
|
|
75
|
+
Run a Perforce command.
|
|
76
|
+
|
|
77
|
+
A hook to call the original synchronous `run` method. Child classes who want to wrap
|
|
78
|
+
extra functionality to the underlying `run` method can override this method.
|
|
79
|
+
It will be called on a worker thread as appropriate.
|
|
80
|
+
"""
|
|
81
|
+
return super().run(*args, **kwargs)
|
|
82
|
+
|
|
83
|
+
async def arun(self, *args: Any, **kwargs: Any) -> Any:
|
|
84
|
+
"""
|
|
85
|
+
Asynchronous version of the `run` method.
|
|
86
|
+
"""
|
|
87
|
+
return await self.execute(self.sync_run, *args, **kwargs)
|
|
88
|
+
|
|
89
|
+
def __getattr__(self, name: str) -> Any:
|
|
90
|
+
if name.startswith("arun_"):
|
|
91
|
+
cmd = name[len("arun_") :]
|
|
92
|
+
return lambda *args, **kargs: self.arun(cmd, *args, **kargs)
|
|
93
|
+
elif name.startswith("adelete_"):
|
|
94
|
+
cmd = name[len("adelete_") :]
|
|
95
|
+
return lambda *args, **kargs: self.arun(cmd, "-d", *args, **kargs)
|
|
96
|
+
elif name.startswith("afetch_"):
|
|
97
|
+
cmd = name[len("afetch_") :]
|
|
98
|
+
return lambda *args, **kargs: self.__afetch(cmd, *args, **kargs)
|
|
99
|
+
elif name.startswith("asave_"):
|
|
100
|
+
cmd = name[len("asave_") :]
|
|
101
|
+
return lambda *args, **kargs: self.__asave(cmd, *args, **kargs)
|
|
102
|
+
elif name.startswith("aiterate_"):
|
|
103
|
+
cmd = name[len("aiterate_") :]
|
|
104
|
+
return lambda *args, **kargs: self.__aiterate(cmd, *args, **kargs)
|
|
105
|
+
if name.startswith("a") and name[1:] in self.simple_wrap_methods:
|
|
106
|
+
method = name[1:]
|
|
107
|
+
return lambda *args, **kwargs: self.execute(
|
|
108
|
+
getattr(self, method), *args, **kwargs
|
|
109
|
+
)
|
|
110
|
+
elif name.startswith("a") and name[1:] in self.run_wrap_methods:
|
|
111
|
+
method = name[1:]
|
|
112
|
+
return lambda *args, **kwargs: getattr(self, method)(
|
|
113
|
+
*args, with_async=True, **kwargs
|
|
114
|
+
)
|
|
115
|
+
return super().__getattr__(name)
|
|
116
|
+
|
|
117
|
+
async def __afetch(self, cmd: str, *args: Any, **kargs: Any) -> Any:
|
|
118
|
+
"""
|
|
119
|
+
Handle async versions of fetch commands.
|
|
120
|
+
"""
|
|
121
|
+
result = await self.arun(cmd, "-o", *args, **kargs)
|
|
122
|
+
for r in result:
|
|
123
|
+
if isinstance(r, tuple) or isinstance(r, dict):
|
|
124
|
+
return r
|
|
125
|
+
return result[0]
|
|
126
|
+
|
|
127
|
+
async def __aiterate(self, cmd: str, *args: Any, **kargs: Any):
|
|
128
|
+
"""
|
|
129
|
+
Handle async versions of iterate commands.
|
|
130
|
+
"""
|
|
131
|
+
if cmd not in self.specfields:
|
|
132
|
+
raise Exception("Unknown spec list command: %s", cmd)
|
|
133
|
+
|
|
134
|
+
specs = await self.arun(cmd, *args, **kargs)
|
|
135
|
+
spec = self.specfields[cmd][0]
|
|
136
|
+
field = self.specfields[cmd][1]
|
|
137
|
+
for spec in specs:
|
|
138
|
+
yield await self.arun(spec, "-o", spec[field])[0]
|
|
139
|
+
|
|
140
|
+
async def __asave(self, cmd: str, *args: Any, **kargs: Any) -> Any:
|
|
141
|
+
self.input = args[0]
|
|
142
|
+
return await self.arun(cmd, "-i", args[1:], **kargs)
|
|
143
|
+
|
|
144
|
+
async def __aenter__(self) -> "P4Async":
|
|
145
|
+
"""
|
|
146
|
+
Asynchronous context manager enter method.
|
|
147
|
+
"""
|
|
148
|
+
return self
|
|
149
|
+
|
|
150
|
+
async def __aexit__(
|
|
151
|
+
self,
|
|
152
|
+
exc_type: Optional[Type[BaseException]],
|
|
153
|
+
exc_val: Optional[BaseException],
|
|
154
|
+
exc_tb: Optional[Any],
|
|
155
|
+
) -> None:
|
|
156
|
+
"""
|
|
157
|
+
Asynchronous context manager exit method.
|
|
158
|
+
"""
|
|
159
|
+
if self.connected():
|
|
160
|
+
await self.adisconnect()
|
p4async/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: p4async
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: An async extension to p4python
|
|
5
|
+
Project-URL: Homepage, https://github.com/kristjanvalur/p4async
|
|
6
|
+
Project-URL: Issues, https://github.com/kristjanvalue/p4async/issues
|
|
7
|
+
Author-email: Kristján Valur Jónsson <sweskman@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Requires-Python: >=3.11
|
|
13
|
+
Requires-Dist: p4python>=2020.1.0
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# p4async
|
|
18
|
+
|
|
19
|
+
An extension to the [p4python](https://pypi.org/project/p4python/) module, adding async functionality to Perforce.
|
|
20
|
+
|
|
21
|
+
## Setup
|
|
22
|
+
|
|
23
|
+
Use your favorite package manager to install the module into your project
|
|
24
|
+
|
|
25
|
+
- `pip install p4async`
|
|
26
|
+
- `uv add p4async`
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from p4async import P4Async
|
|
32
|
+
p4a = P4Async()
|
|
33
|
+
await p4a.aconnect()
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
All relevant Perforce commands have async counterparts prefixed with `a`.
|
|
37
|
+
For example: `aconnect()`, `arun()`, `arun_clients()`, `afetch_change()`, etc.
|
|
38
|
+
|
|
39
|
+
Commands are executed with a lock on a worker thread. The way this is done can be
|
|
40
|
+
customized via subclassing.
|
|
41
|
+
|
|
42
|
+
## Development
|
|
43
|
+
|
|
44
|
+
- Use [uv](https://docs.astral.sh/uv/) for dependency management and virtual environments.
|
|
45
|
+
|
|
46
|
+
## License
|
|
47
|
+
|
|
48
|
+
MIT
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
p4async/__init__.py,sha256=NrnLHuSf90EoL4paHqjqFZrZZEURAz6znAB_y-ZJDDI,5686
|
|
2
|
+
p4async/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
p4async-0.1.1.dist-info/METADATA,sha256=74c6rninQ1vb20mG9rLb7sVPCLrYaALOWFHqISrZiYs,1260
|
|
4
|
+
p4async-0.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
5
|
+
p4async-0.1.1.dist-info/licenses/LICENSE,sha256=CcIqC-yRYOROxhEDJw060FuEARhYnwacb-bvbIkz_Lk,1073
|
|
6
|
+
p4async-0.1.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 YOUR_NAME_OR_ORG
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|