xpipe_api 0.1.26__py3-none-any.whl → 0.1.27__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
xpipe_api/clients.py CHANGED
@@ -19,7 +19,7 @@ class Client:
19
19
  base_url: str
20
20
  raise_errors: bool
21
21
  session: Optional[str] = None
22
- min_version: Version = Version("10.1-12")
22
+ min_version: Version = Version("14.0")
23
23
 
24
24
  def __init__(
25
25
  self, token: Optional[str] = None, base_url: Optional[str] = None, ptb: bool = False, raise_errors: bool = True
@@ -36,7 +36,7 @@ class Client:
36
36
  except PermissionError:
37
37
  raise NoTokenFoundException("Bad permissions on xpipe_auth: is the daemon running as another user?")
38
38
  except Exception as e:
39
- raise NoTokenFoundException(f"No auth provided and couldn't load xpipe_auth: {e!r}")
39
+ raise NoTokenFoundException(f"No auth provided and couldn't load xpipe_auth: {e!r}. Is the XPipe daemon running?")
40
40
 
41
41
  if not base_url:
42
42
  base_url = "http://127.0.0.1:21722" if ptb else "http://127.0.0.1:21721"
@@ -57,11 +57,12 @@ class Client:
57
57
  session = response.get("sessionToken", None)
58
58
  if session:
59
59
  self.session = session
60
+ daemon_version = self.daemon_version()["version"]
61
+ assert (
62
+ daemon_version == "dev" or Version(daemon_version) >= self.min_version
63
+ ), f"xpipe_api requires XPipe of at least {self.min_version}"
60
64
  else:
61
65
  raise AuthFailedException(json.dumps(response))
62
- assert (
63
- Version(self.daemon_version()["version"]) >= self.min_version
64
- ), f"xpipe_api requires XPipe of at least {self.min_version}"
65
66
 
66
67
  def _post(self, *args, **kwargs) -> requests.Response:
67
68
  if not self.session:
@@ -124,12 +125,20 @@ class Client:
124
125
  response = self.post(endpoint, json=data)
125
126
  return json.loads(response).get("infos", [])
126
127
 
127
- def connection_add(self, name: str, conn_data: dict, validate: bool = False) -> str:
128
+ def connection_add(self, name: str, conn_data: dict, validate: bool = False, category: str = None) -> str:
128
129
  endpoint = f"{self.base_url}/connection/add"
129
130
  data = {"name": name, "data": conn_data, "validate": validate}
131
+ if category:
132
+ data["category"] = category
130
133
  response = self.post(endpoint, json=data)
131
134
  return json.loads(response)["connection"]
132
135
 
136
+ def category_add(self, name: str, parent: str) -> str:
137
+ endpoint = f"{self.base_url}/category/add"
138
+ data = {"name": name, "parent": parent}
139
+ response = self.post(endpoint, json=data)
140
+ return json.loads(response)["category"]
141
+
133
142
  def connection_remove(self, uuids: Union[str, List[str]]):
134
143
  endpoint = f"{self.base_url}/connection/remove"
135
144
  if not isinstance(uuids, list):
@@ -316,12 +325,20 @@ class AsyncClient(Client):
316
325
  response = await self.post(endpoint, json=data)
317
326
  return json.loads(response).get("infos", [])
318
327
 
319
- async def connection_add(self, name: str, conn_data: dict, validate: bool = False) -> str:
328
+ async def connection_add(self, name: str, conn_data: dict, validate: bool = False, category: str = None) -> str:
320
329
  endpoint = f"{self.base_url}/connection/add"
321
330
  data = {"name": name, "data": conn_data, "validate": validate}
331
+ if category:
332
+ data["category"] = category
322
333
  response = await self.post(endpoint, json=data)
323
334
  return json.loads(response)["connection"]
324
335
 
336
+ async def category_add(self, name: str, parent: str) -> str:
337
+ endpoint = f"{self.base_url}/category/add"
338
+ data = {"name": name, "parent": parent}
339
+ response = await self.post(endpoint, json=data)
340
+ return json.loads(response)["category"]
341
+
325
342
  async def connection_remove(self, uuids: Union[str, List[str]]):
326
343
  endpoint = f"{self.base_url}/connection/remove"
327
344
  if not isinstance(uuids, list):
@@ -0,0 +1,246 @@
1
+ Metadata-Version: 2.1
2
+ Name: xpipe_api
3
+ Version: 0.1.27
4
+ Summary: Client for the XPipe API
5
+ Home-page: https://github.com/xpipe-io/xpipe-python-api
6
+ License: MIT
7
+ Author: Clint Olson
8
+ Author-email: coandco@gmail.com
9
+ Requires-Python: >=3.10,<4.0
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Requires-Dist: aiohttp-requests (>=0.2.4,<0.3.0)
16
+ Requires-Dist: packaging (>=24.1,<25.0)
17
+ Requires-Dist: requests (>=2.32.3,<3.0.0)
18
+ Project-URL: Repository, https://github.com/xpipe-io/xpipe-python-api
19
+ Description-Content-Type: text/markdown
20
+
21
+ # XPipe Python API
22
+
23
+ [![GitHub license](https://img.shields.io/github/license/xpipe-io/xpipe-python-api.svg)](https://github.com/xpipe-io/xpipe-python-api/blob/master/LICENSE)
24
+ [![PyPI version](https://img.shields.io/pypi/v/xpipe_api)](https://pypi.org/project/xpipe_api/)
25
+
26
+ Python client for the XPipe API. This library is a wrapper for the raw [HTTP API](https://github.com/xpipe-io/xpipe/blob/master/openapi.yaml) and intended to make working with it more convenient. You can find the raw API reference at http://localhost:21721 if your XPipe desktop application is running. XPipe contains a local webserver that includes the API docs as well.
27
+
28
+ ## Installation
29
+
30
+ ```
31
+ python3 -m pip install xpipe_api
32
+ ```
33
+
34
+ ## Connecting to the XPipe application
35
+
36
+ To start out, you need to enable the API access in the XPipe settings. You can find that under **Settings** -> **API**. For only a local API access, you don't need to worry about the API key in the settings menu.
37
+
38
+ ```python
39
+ from xpipe_api import Client
40
+
41
+ # By default, Client() will read an access key from the file xpipe_auth on the local filesystem
42
+ # and talk to the XPipe HTTP server on localhost.
43
+ client = Client()
44
+
45
+ # If you want to connect to a PTB build of XPipe, you have to configure the client with that information
46
+ # as the PTB releases use a different port
47
+ client = Client(ptb=True)
48
+
49
+ # To connect to a remote instance with an API key, use
50
+ client = Client(token="foo", base_url = "http://servername:21721")
51
+ ```
52
+
53
+ There's also an async version of the client that can be accessed as AsyncClient. All calls are run asynchronously with that client. This intro will only cover the sync client though.
54
+
55
+ ```python
56
+ import asyncio
57
+ from xpipe_api import AsyncClient
58
+
59
+ async def main():
60
+ client = AsyncClient()
61
+
62
+ # Do your stuff async
63
+
64
+ if __name__ == "__main__":
65
+ asyncio.run(main())
66
+ ```
67
+
68
+ ## Querying connections
69
+
70
+ A connection reference is just a UUID string. You can query one or multiple connections stored in XPipe based on various filters.
71
+
72
+ **API reference**: http://localhost:21721/#query-connections
73
+
74
+ ```python
75
+ from xpipe_api import Client
76
+
77
+ client = Client()
78
+
79
+ # connection_query accepts glob-based filters on the category, connection name, and connection type
80
+ connections = client.connection_query(categories="Default/**")
81
+
82
+ # Find only ssh config host connections in the "MyCategory" category that has the word "MyCompany" in its name
83
+ # - Searching for strings is case-insensitive here
84
+ # - This will not include subcategories of "MyCategory"
85
+ # - You can find all the available connection type names by just querying all connections and taking a look add the individual type names of the connections.
86
+ connections = client.connection_query(categories="MyCategory", types="sshConfigHost", connections="*MyCompany*")
87
+
88
+ # Query a specific connection by name in a category
89
+ # This will only return one element, so we can just access that
90
+ connection = client.connection_query(categories="MyCategory", connections="MyConnectionName")[0]
91
+
92
+ # Query the local machine connection
93
+ connection = client.connection_query(connections="Local Machine")[0]
94
+
95
+ # A connection reference is just a UUID string, so you can also specify it fixed
96
+ # If you have the HTTP API setting enabled in XPipe, you can copy the API UUID of a connection in the context menu
97
+ connection = "f0ec68aa-63f5-405c-b178-9a4454556d6b"
98
+ ```
99
+
100
+ ## Getting connection information
101
+
102
+ Since each connection reference is just a UUID string, you have to retrieve information for it using another API call.
103
+
104
+ **API Reference**: http://localhost:21721/#connection-information
105
+
106
+ ```python
107
+ from xpipe_api import Client
108
+
109
+ client = Client()
110
+
111
+ connections = client.connection_query(categories="Default/**")
112
+
113
+ # Works in bulk, so it expects an array of connections
114
+ infos = client.connection_info(connections)
115
+ # Get the first info
116
+ info = infos[0]
117
+
118
+ # The connection UUID
119
+ uuid = info["connection"]
120
+ # This is an array containing all category hierarchy names
121
+ category = info["category"]
122
+ # This is an array containing all connection hierarchy names
123
+ name = info["name"]
124
+ # This is the connection type name that you can use in the query
125
+ type = info["type"]
126
+ # There is also some other data for internal state management, e.g. if a tunnel is running for example
127
+ ```
128
+
129
+ ## GUI actions
130
+
131
+ You can perform some actions for the desktop application from the CLI as well.
132
+
133
+ **API Reference**: http://localhost:21721/#open-connection-in-file-browser
134
+
135
+ ```python
136
+ from xpipe_api import Client
137
+
138
+ client = Client()
139
+
140
+ connection = "f0ec68aa-63f5-405c-b178-9a4454556d6b"
141
+
142
+ # Opens the file browser in a specified starting directory for a connection
143
+ client.connection_browse(connection, "/etc")
144
+
145
+ # Opens a terminal session in a specified starting directory for a connection
146
+ client.connection_terminal(connection, "/etc")
147
+
148
+ # Toggles the session state for a connection. If this connection is a tunnel, then this operation will start or stop the tunnel
149
+ client.connection_toggle(connection, True)
150
+
151
+ # Refreshes a connection. This operation is the same as for validating a connection when creating it by attempting whether the connection is functioning.
152
+ client.connection_refresh(connection)
153
+ ```
154
+
155
+ ## Shell operations
156
+
157
+ You can open remote shell sessions to systems and run arbitrary commands in them.
158
+
159
+ **API Reference**: http://localhost:21721/#start-shell-connection
160
+
161
+ ```python
162
+ from xpipe_api import Client
163
+ import re
164
+
165
+ client = Client()
166
+
167
+ connection = "f0ec68aa-63f5-405c-b178-9a4454556d6b"
168
+
169
+ # This will start a shell session for the connection
170
+ # Note that this session is non-interactive, meaning that password prompts are not supported
171
+ shell_info = client.shell_start(connection)
172
+
173
+ # The shell dialect of the shell. For example cmd, powershell, bash, etc.
174
+ dialect = shell_info["shellDialect"]
175
+ # The operating system type of the system. For example, windows, linux, macos, bsd, solaris
176
+ osType = shell_info["osType"]
177
+ # The display name of the operating system. For example Windows 10 Home 22H2
178
+ osName = shell_info["osName"]
179
+
180
+ # Prints {'exitCode': 0, 'stdout': 'hello world', 'stderr': ''}
181
+ print(client.shell_exec(connection, "echo hello world"))
182
+
183
+ # Prints {'exitCode': 0, 'stdout': '<user>', 'stderr': ''}
184
+ print(client.shell_exec(connection, "echo $USER"))
185
+
186
+ # Prints {'exitCode': 127, 'stdout': 'invalid: command not found', 'stderr': ''}
187
+ print(client.shell_exec(connection, "invalid"))
188
+
189
+ # Extract ssh version from system
190
+ version_format = re.compile(r'[0-9.]+')
191
+ ret = client.shell_exec(connection, "ssh -V")
192
+ ssh_version = version_format.findall(ret["stderr"])[0]
193
+
194
+ # Clean up after ourselves by stopping the shell session
195
+ client.shell_stop(connection)
196
+ ```
197
+
198
+ ## File system operations
199
+
200
+ You can interact with the file system of any remote shell as well.
201
+
202
+ **API reference**: http://localhost:21721/#store-a-raw-blob-to-be-used-later
203
+
204
+ ```python
205
+ from xpipe_api import Client
206
+
207
+ client = Client()
208
+
209
+ connection = "f0ec68aa-63f5-405c-b178-9a4454556d6b"
210
+
211
+ # We need a shell for the file system
212
+ client.shell_start(connection)
213
+
214
+ # You are responsible for decoding files in the correct file encoding
215
+ file_bytes = client.fs_read(connection, "~/.ssh/config")
216
+ file_string = file_bytes.decode('utf-8')
217
+
218
+ # Writing files can be done via blobs
219
+ file_write_content = "test\nfile"
220
+ blob_id = client.fs_blob(file_write_content)
221
+ client.fs_write(connection, blob_id, "~/test_file.txt")
222
+
223
+ # You can do the same with script files as well
224
+ # This will create an executable script in the system's temp directory
225
+ script_write_content = "echo hello\necho world"
226
+ blob_script_id = client.fs_blob(file_write_content)
227
+ script_path = client.fs_script(connection, blob_script_id)
228
+
229
+ # You can then use this script for your commands
230
+ # Prints {'exitCode': 0, 'stdout': 'hello\nworld', 'stderr': ''}
231
+ print(client.shell_exec(connection, "\"" + script_path + "\""))
232
+
233
+ # Clean up after ourselves by stopping the shell session
234
+ client.shell_stop(connection)
235
+ ```
236
+
237
+ ## Development
238
+
239
+ To run the test suite, you'll need to define the XPIPE_APIKEY env var. This will allow the two "log in with the ApiKey
240
+ rather than Local method" tests to work. Here's the recommended method for running the tests with poetry:
241
+
242
+ ```commandline
243
+ cd /path/to/xpipe-python-api
244
+ poetry install
245
+ XPIPE_APIKEY=<api_key> poetry run pytest
246
+ ```
@@ -0,0 +1,7 @@
1
+ xpipe_api/__init__.py,sha256=XLv-a-mt7OcGS9OQbDBHKBsI4mO-4lgWDA13nZ0njFY,108
2
+ xpipe_api/clients.py,sha256=N5CCrQJTIHAwTedPTpol21IQcVmECwVMSE_8XRSMW80,19563
3
+ xpipe_api/exceptions.py,sha256=FdkNKLV1gOH8mOuNM9wH_q3hYNFEdMbQwAEZUX4yZQU,410
4
+ xpipe_api-0.1.27.dist-info/LICENSE,sha256=hWd_i4lSck0lBXcxe-2P4VCUyPAecKW-X1oOiXv21wE,1089
5
+ xpipe_api-0.1.27.dist-info/METADATA,sha256=FW1cTa8fBTcqjXBAbM_cxvXkNe98FERz6tRqx7bTmnI,8925
6
+ xpipe_api-0.1.27.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
7
+ xpipe_api-0.1.27.dist-info/RECORD,,
@@ -1,103 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: xpipe_api
3
- Version: 0.1.26
4
- Summary: Client for the XPipe API
5
- Home-page: https://github.com/xpipe-io/xpipe-python-api
6
- License: MIT
7
- Author: Clint Olson
8
- Author-email: coandco@gmail.com
9
- Requires-Python: >=3.10,<4.0
10
- Classifier: License :: OSI Approved :: MIT License
11
- Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.10
13
- Classifier: Programming Language :: Python :: 3.11
14
- Classifier: Programming Language :: Python :: 3.12
15
- Requires-Dist: aiohttp-requests (>=0.2.4,<0.3.0)
16
- Requires-Dist: packaging (>=24.1,<25.0)
17
- Requires-Dist: requests (>=2.32.3,<3.0.0)
18
- Project-URL: Repository, https://github.com/xpipe-io/xpipe-python-api
19
- Description-Content-Type: text/markdown
20
-
21
- # XPipe Python API
22
-
23
- [![GitHub license](https://img.shields.io/github/license/xpipe-io/xpipe-python-api.svg)](https://github.com/coandco/python_xpipe_api/blob/master/LICENSE)
24
- [![PyPI version](https://img.shields.io/pypi/v/xpipe_api)](https://pypi.org/project/xpipe_api/)
25
-
26
- Python client for the XPipe API. This library is a wrapper for the raw [HTTP API](https://github.com/xpipe-io/xpipe/blob/master/openapi.yaml) and intended to make working with it more convenient.
27
-
28
- ## Installation
29
- ```
30
- python3 -m pip install xpipe_api
31
- ```
32
-
33
- ## Usage
34
-
35
- ```python
36
- from xpipe_api import Client
37
-
38
- # By default, Client() will read an access key from the file xpipe_auth on the local filesystem
39
- # and talk to the XPipe HTTP server on localhost. To connect to a remote instance with an API
40
- # key, use Client(token="foo", base_url = "http://servername:21721")
41
- client = Client()
42
-
43
- # connection_query accepts glob-based filters on the category, connection name, and connection type
44
- all_connections = client.connection_query()
45
-
46
- # Each connection includes uuid, category, connection, and type information
47
- first_connection_uuid = all_connections[0]["uuid"]
48
-
49
- # Before any shell commands can be run, a shell session must be started on a connection
50
- client.shell_start(first_connection_uuid)
51
-
52
- # Prints {'exitCode': 0, 'stdout': 'hello world', 'stderr': ''}
53
- print(client.shell_exec(first_connection_uuid, "echo hello world"))
54
-
55
- # Clean up after ourselves by stopping the shell session
56
- client.shell_stop(first_connection_uuid)
57
- ```
58
-
59
- There's also an async version of the client that can be accessed as AsyncClient:
60
-
61
- ```python
62
- import asyncio
63
- from xpipe_api import AsyncClient
64
-
65
-
66
- async def main():
67
- # By default, Client() will read an access key from the file xpipe_auth on the local filesystem
68
- # and talk to the XPipe HTTP server on localhost. To connect to a remote instance with an API
69
- # key, use Client(token="foo", base_url = "http://servername:21721")
70
- client = AsyncClient()
71
-
72
- # connection_query accepts glob-based filters on the category, connection name, and connection type
73
- all_connections = await client.connection_query()
74
-
75
- # Each connection includes uuid, category, connection, and type information
76
- first_connection_uuid = all_connections[0]["uuid"]
77
-
78
- # Before any shell commands can be run, a shell session must be started on a connection
79
- await client.shell_start(first_connection_uuid)
80
-
81
- # Prints {'exitCode': 0, 'stdout': 'hello world', 'stderr': ''}
82
- print(await client.shell_exec(first_connection_uuid, "echo hello world"))
83
-
84
- # Clean up after ourselves by stopping the shell session
85
- await client.shell_stop(first_connection_uuid)
86
-
87
-
88
- if __name__ == "__main__":
89
- asyncio.run(main())
90
- ```
91
-
92
- This is only a short summary of the library. You can find more supported functionalities in the source itself.
93
-
94
- ## Tests
95
-
96
- To run the test suite, you'll need to define the XPIPE_APIKEY env var. This will allow the two "log in with the ApiKey
97
- rather than Local method" tests to work. Here's the recommended method for running the tests with poetry:
98
-
99
- ```commandline
100
- cd /path/to/python_xpipe_api
101
- poetry install
102
- XPIPE_APIKEY=<api_key> poetry run pytest
103
- ```
@@ -1,7 +0,0 @@
1
- xpipe_api/__init__.py,sha256=XLv-a-mt7OcGS9OQbDBHKBsI4mO-4lgWDA13nZ0njFY,108
2
- xpipe_api/clients.py,sha256=Pg0jyrJpjv8bB0Xo4vdQUiqK3RsLdlAmJXorJLNBwhw,18740
3
- xpipe_api/exceptions.py,sha256=FdkNKLV1gOH8mOuNM9wH_q3hYNFEdMbQwAEZUX4yZQU,410
4
- xpipe_api-0.1.26.dist-info/LICENSE,sha256=hWd_i4lSck0lBXcxe-2P4VCUyPAecKW-X1oOiXv21wE,1089
5
- xpipe_api-0.1.26.dist-info/METADATA,sha256=EMi4uQvxYDAw6jGDTK_ViCmUzgb9BNOZVeqyJbwIGvY,3900
6
- xpipe_api-0.1.26.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
7
- xpipe_api-0.1.26.dist-info/RECORD,,