Skip to content

Commit 46a2dd7

Browse files
committed
updated readthedocs file
1 parent 3880ea3 commit 46a2dd7

File tree

5 files changed

+351
-350
lines changed

5 files changed

+351
-350
lines changed
File renamed without changes.

docs/source/conf.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
#
1515
import os
1616
import sys
17-
sys.path.insert(0, os.path.abspath('../../../'))
17+
sys.path.insert(0, os.path.abspath('../..'))
18+
1819

1920
# -- Project information -----------------------------------------------------
2021

pystackql/__init__.py

+2-349
Original file line numberDiff line numberDiff line change
@@ -1,349 +1,2 @@
1-
from .stackql_magic import load_ipython_extension
2-
3-
from .util import (
4-
_get_package_version,
5-
_get_platform,
6-
_get_download_dir,
7-
_get_binary_name,
8-
_setup,
9-
_get_version,
10-
_format_auth,
11-
_execute_queries_in_parallel
12-
)
13-
import sys, subprocess, json, os, asyncio, functools, psycopg2
14-
from concurrent.futures import ProcessPoolExecutor
15-
from psycopg2.extras import RealDictCursor
16-
17-
class StackQL:
18-
"""A class representing an instance of the StackQL query engine.
19-
20-
:param platform: the operating system platform (read only)
21-
:type platform: str
22-
23-
:param parse_json: whether to parse the output as JSON, defaults to `False`
24-
unless overridden by setting `output` to `csv`, `table` or `text` as a `kwarg` in the `StackQL` object constructor (read only)
25-
:type parse_json: bool
26-
27-
:param params: a list of command-line parameters passed to the StackQL executable, populated by the class constructor (read only)
28-
:type params: list
29-
30-
:param download_dir: the download directory for the StackQL executable - defaults to site.getuserbase() unless overridden in the `StackQL` object constructor (read only)
31-
:type download_dir: str
32-
33-
:param bin_path: the full path of the StackQL executable (read only)
34-
:type bin_path: str
35-
36-
:param version: the version number of the StackQL executable (read only)
37-
:type version: str
38-
39-
:param package_version: the version number of the pystackql Python package (read only)
40-
:type package_version: str
41-
42-
:param sha: the commit (short) sha for the installed `stackql` binary build (read only)
43-
:type sha: str
44-
45-
:param auth: StackQL provider authentication object supplied using the class constructor (read only)
46-
:type auth: dict
47-
48-
:param server_mode: Connect to a stackql server - defaults to `False` unless overridden in the `StackQL` object constructor (read only)
49-
:type server_mode: bool
50-
51-
:param server_address: The address of the stackql server - defaults to `0.0.0.0` unless overridden in the `StackQL` object constructor (read only), only used if `server_mode` (read only)
52-
:type auth: str
53-
54-
:param server_port: The port of the stackql server - defaults to `5466` unless overridden in the `StackQL` object constructor (read only), only used if `server_mode` (read only)
55-
:type auth: int
56-
"""
57-
58-
def _connect_to_server(self):
59-
"""Establishes a connection to the server using psycopg.
60-
61-
Returns:
62-
Connection object if successful, or None if an error occurred.
63-
"""
64-
try:
65-
conn = psycopg2.connect(
66-
dbname='stackql',
67-
user='stackql',
68-
host=self.server_address,
69-
port=self.server_port
70-
)
71-
return conn
72-
except psycopg2.OperationalError as oe:
73-
print(f"OperationalError while connecting to the server: {oe}")
74-
except Exception as e:
75-
# Catching all other possible psycopg2 exceptions (and possibly other unexpected exceptions).
76-
# You might want to log this or handle it differently in a real-world scenario.
77-
print(f"Unexpected error while connecting to the server: {e}")
78-
return None
79-
80-
def _run_server_query(self, query):
81-
"""
82-
Runs a query against the server using psycopg2.
83-
84-
:param query: SQL query to be executed on the server.
85-
:type query: str
86-
:return: List of result rows if the query fetches results; empty list if there are no results.
87-
:rtype: list
88-
:raises: psycopg2.ProgrammingError for issues related to the SQL query,
89-
unless the error is "no results to fetch", in which case an empty list is returned.
90-
"""
91-
conn = self._connect_to_server()
92-
try:
93-
cur = conn.cursor(cursor_factory=RealDictCursor)
94-
cur.execute(query)
95-
rows = cur.fetchall()
96-
cur.close()
97-
return rows
98-
except psycopg2.ProgrammingError as e:
99-
if str(e) == "no results to fetch":
100-
return []
101-
else:
102-
raise
103-
104-
def _run_query(self, query):
105-
"""
106-
Internal method to execute a StackQL query using a subprocess.
107-
108-
The method spawns a subprocess to run the StackQL binary with the specified query and parameters.
109-
It waits for the subprocess to complete and captures its stdout as the output. This approach ensures
110-
that resources like pipes are properly cleaned up after the subprocess completes.
111-
112-
:param query: The StackQL query string to be executed.
113-
:type query: str
114-
115-
:return: The output result of the query, which can either be the actual query result or an error message.
116-
:rtype: str
117-
118-
Possible error messages include:
119-
- Indications that the StackQL binary wasn't found.
120-
- Generic error messages for other exceptions encountered during the query execution.
121-
122-
:raises FileNotFoundError: If the StackQL binary isn't found.
123-
:raises Exception: For any other exceptions during the execution, providing a generic error message.
124-
"""
125-
local_params = self.params.copy()
126-
local_params.insert(1, query)
127-
try:
128-
with subprocess.Popen([self.bin_path] + local_params,
129-
stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as iqlPopen:
130-
stdout, _ = iqlPopen.communicate()
131-
return stdout.decode('utf-8')
132-
except FileNotFoundError:
133-
return "ERROR %s not found" % self.bin_path
134-
except Exception as e:
135-
return "ERROR %s %s" % (str(e), e.__doc__)
136-
137-
def __init__(self, **kwargs):
138-
"""Constructor method
139-
"""
140-
# get platform and set property
141-
self.platform, this_os = _get_platform()
142-
143-
# get each kwarg and set property
144-
self.parse_json = True
145-
self.params = ["exec"]
146-
output_set = False
147-
for key, value in kwargs.items():
148-
self.params.append("--%s" % key)
149-
if key == "output":
150-
output_set = True
151-
if value != "json":
152-
self.parse_json = False
153-
if key == "auth":
154-
authobj, authstr = _format_auth(value)
155-
value = authstr
156-
self.auth = authobj
157-
if key == "download_dir":
158-
self.download_dir = value
159-
self.params.append(value)
160-
if not output_set:
161-
self.params.append("--output")
162-
self.params.append("json")
163-
164-
# set fq path
165-
binary = _get_binary_name(this_os)
166-
# if download_dir not set, use site.getuserbase()
167-
if not hasattr(self, 'download_dir'):
168-
self.download_dir = _get_download_dir()
169-
self.bin_path = os.path.join(self.download_dir, binary)
170-
171-
# get and set version
172-
if os.path.exists(self.bin_path):
173-
self.version, self.sha = _get_version(self.bin_path)
174-
else:
175-
_setup(self.download_dir, this_os)
176-
self.version, self.sha = _get_version(self.bin_path)
177-
178-
# get package version
179-
self.package_version = _get_package_version("pystackql")
180-
181-
# server_mode props, connects to a server via the postgres wire protocol
182-
self.server_mode = kwargs.get("server_mode", False)
183-
if self.server_mode:
184-
self.server_address = kwargs.get("server_address", "0.0.0.0")
185-
self.server_port = kwargs.get("server_port", 5466)
186-
# establish the connection
187-
self._conn = self._connect_to_server()
188-
189-
def properties(self):
190-
"""
191-
Retrieves the properties of the StackQL instance.
192-
193-
This method collects all the attributes of the StackQL instance and
194-
returns them in a dictionary format.
195-
196-
Returns:
197-
dict: A dictionary containing the properties of the StackQL instance.
198-
199-
Example:
200-
::
201-
202-
{
203-
"platform": "Darwin x86_64 (macOS-12.0.1-x86_64-i386-64bit), Python 3.10.9",
204-
"parse_json": True,
205-
...
206-
}
207-
"""
208-
props = {}
209-
for var in vars(self):
210-
props[var] = getattr(self, var)
211-
return props
212-
213-
def upgrade(self, showprogress=True):
214-
"""
215-
Upgrades the StackQL binary to the latest version available.
216-
217-
This method initiates an upgrade of the StackQL binary. Post-upgrade,
218-
it updates the `version` and `sha` attributes of the StackQL instance
219-
to reflect the newly installed version.
220-
221-
Parameters:
222-
showprogress (bool, optional): Indicates if progress should be displayed
223-
during the upgrade. Defaults to True.
224-
225-
Prints:
226-
str: A message indicating the new version of StackQL post-upgrade.
227-
228-
Example:
229-
stackql upgraded to version v0.5.396
230-
"""
231-
_setup(self.download_dir, self.platform, showprogress)
232-
self.version, self.sha = _get_version(self.bin_path)
233-
print("stackql upgraded to version %s" % (self.version))
234-
235-
def executeStmt(self, query):
236-
"""Executes a query using the StackQL instance and returns the output as a string.
237-
This is intended for operations which do not return a result set, for example a mutation
238-
operation such as an `INSERT` or a `DELETE` or life cycle method such as an `EXEC` operation.
239-
240-
This method determines the mode of operation (server_mode or local execution) based
241-
on the `server_mode` attribute of the instance. If `server_mode` is True, it runs the query
242-
against the server. Otherwise, it executes the query using a subprocess.
243-
244-
:param query: The StackQL query string to be executed.
245-
:type query: str
246-
247-
:return: The output result of the query in string format. If in `server_mode`, it
248-
returns a JSON string representation of the result.
249-
:rtype: str
250-
251-
Note: In `server_mode`, the method internally converts the result from the server to a
252-
JSON string before returning.
253-
"""
254-
if self.server_mode:
255-
# Use server mode
256-
result = self._run_server_query(query)
257-
return json.dumps(result)
258-
else:
259-
return self._run_query(query)
260-
261-
def execute(self, query):
262-
"""Executes a query using the StackQL instance and returns the output as a string
263-
or JSON object depending on the value of `parse_json` property.
264-
265-
Depending on the `server_mode` attribute of the instance, this method either runs the
266-
query against the StackQL server or executes it locally using a subprocess.
267-
268-
If the `parse_json` attribute is set to True, the method tries to return the result
269-
as a JSON object. If parsing fails (in local execution), it returns an error message
270-
within a JSON string.
271-
272-
:param query: The StackQL query string to be executed.
273-
:type query: str
274-
275-
:return: The output result of the query. Depending on the `parse_json` attribute and
276-
the mode of execution, the result can be a JSON object, a JSON string, or a
277-
raw string.
278-
:rtype: str or dict
279-
280-
Note: If `server_mode` is enabled and `parse_json` is True, the result is directly
281-
returned as a JSON object.
282-
"""
283-
if self.server_mode:
284-
# Use server mode
285-
result = self._run_server_query(query)
286-
if self.parse_json:
287-
return result # Directly return the parsed result as a JSON object
288-
else:
289-
return json.dumps(result) # Convert it into a string and then return
290-
else:
291-
output = self._run_query(query)
292-
if self.parse_json:
293-
try:
294-
return json.loads(output)
295-
except ValueError:
296-
return '[{"error": "%s"}]' % output.strip()
297-
return output
298-
299-
async def _execute_queries_async(self, queries_list):
300-
loop = asyncio.get_event_loop()
301-
302-
# Use functools.partial to bind the necessary arguments
303-
func = functools.partial(_execute_queries_in_parallel, self, queries_list)
304-
305-
with ProcessPoolExecutor() as executor:
306-
results = await loop.run_in_executor(executor, func)
307-
308-
# Process results based on their type:
309-
combined = []
310-
for res in results:
311-
if isinstance(res, str):
312-
combined.extend(json.loads(res))
313-
elif isinstance(res, list):
314-
combined.extend(res)
315-
else:
316-
# Optionally handle other types, or raise an error.
317-
pass
318-
319-
return combined
320-
321-
def executeQueriesAsync(self, queries):
322-
"""
323-
Executes multiple StackQL queries asynchronously using the current StackQL instance.
324-
325-
This method utilizes an asyncio event loop to concurrently run a list of provided
326-
StackQL queries. Each query is executed independently, and the combined results of
327-
all the queries are returned as a list of JSON objects.
328-
329-
Note: The order of the results in the returned list may not necessarily correspond
330-
to the order of the queries in the input list due to the asynchronous nature of execution.
331-
332-
:param queries: A list of StackQL query strings to be executed concurrently.
333-
:type queries: list[str], required
334-
335-
:return: A list of results corresponding to each query. Each result is a JSON object.
336-
:rtype: list[dict]
337-
338-
Example:
339-
>>> queries = [
340-
>>> "SELECT '%s' as region, instanceType, COUNT(*) as num_instances FROM aws.ec2.instances WHERE region = '%s' GROUP BY instanceType" % (region, region)
341-
>>> for region in regions ]
342-
>>> res = stackql.executeQueriesAsync(queries)
343-
"""
344-
345-
loop = asyncio.new_event_loop()
346-
asyncio.set_event_loop(loop)
347-
combined_results = loop.run_until_complete(self._execute_queries_async(queries))
348-
loop.close()
349-
return combined_results
1+
from .stackql import StackQL
2+
from .stackql_magic import load_ipython_extension
File renamed without changes.

0 commit comments

Comments
 (0)