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 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,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -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.