v2.8.0
===================================================================== --- Changes ------------------------------ - python library functions moved to `py/lib` - python scwrypts renamed in kebob-case to help prevent import - __name__ == '__main__' enforced on all python scwrypts --- New Features ------------------------- - `__override` variables now allow values to be force-overwritten - py.lib.http.client provides a slim `requests.request` wrapper --- New Scripts -------------------------- py/data/convert ) quick data converters - csv-to-json - csv-to-yaml - json-to-csv - json-to-yaml - yaml-to-csv - yaml-to-json py/linear ) uses the linear.app graphql API for PM tasks - comment --- Bug Fixes ---------------------------- - `scwrypts` handles arguments with quotes and special characters
This commit is contained in:
0
py/lib/__init__.py
Normal file
0
py/lib/__init__.py
Normal file
0
py/lib/data/__init__.py
Normal file
0
py/lib/data/__init__.py
Normal file
76
py/lib/data/converter.py
Normal file
76
py/lib/data/converter.py
Normal file
@ -0,0 +1,76 @@
|
||||
import csv
|
||||
import json
|
||||
import yaml
|
||||
|
||||
from py.lib.data.io import get_stream
|
||||
|
||||
|
||||
def convert(input_file, input_type, output_file, output_type):
|
||||
if input_type == output_type:
|
||||
raise ValueError('input type and output type are the same')
|
||||
|
||||
with get_stream(input_file) as input_stream:
|
||||
data = convert_input(input_stream, input_type)
|
||||
|
||||
with get_stream(output_file, 'w+') as output_stream:
|
||||
_write_output(output_stream, output_type, data)
|
||||
|
||||
|
||||
def convert_input(stream, input_type):
|
||||
supported_input_types = {'csv', 'json', 'yaml'}
|
||||
|
||||
if input_type not in supported_input_types:
|
||||
raise ValueError(f'input_type "{input_type}" not supported; must be one of {supported_input_types}')
|
||||
|
||||
return {
|
||||
'csv': _read_csv,
|
||||
'json': _read_json,
|
||||
'yaml': _read_yaml,
|
||||
}[input_type](stream)
|
||||
|
||||
def _write_output(stream, output_type, data):
|
||||
supported_output_types = {'csv', 'json', 'yaml'}
|
||||
|
||||
if output_type not in supported_output_types:
|
||||
raise ValueError(f'output_type "{output_type}" not supported; must be one of {supported_output_types}')
|
||||
|
||||
return {
|
||||
'csv': _write_csv,
|
||||
'json': _write_json,
|
||||
'yaml': _write_yaml,
|
||||
}[output_type](stream, data)
|
||||
|
||||
#####################################################################
|
||||
|
||||
def _read_csv(stream):
|
||||
return [dict(line) for line in csv.DictReader(stream)]
|
||||
|
||||
def _write_csv(stream, data):
|
||||
writer = csv.DictWriter(stream, fieldnames=list({
|
||||
key
|
||||
for dictionary in data
|
||||
for key in dictionary.keys()
|
||||
}))
|
||||
|
||||
writer.writeheader()
|
||||
|
||||
for value in data:
|
||||
writer.writerow(value)
|
||||
|
||||
#####################################################################
|
||||
|
||||
def _read_json(stream):
|
||||
data = json.loads(stream.read())
|
||||
return data if isinstance(data, list) else [data]
|
||||
|
||||
def _write_json(stream, data):
|
||||
stream.write(json.dumps(data))
|
||||
|
||||
#####################################################################
|
||||
|
||||
def _read_yaml(stream):
|
||||
data = yaml.safe_load(stream)
|
||||
return data if isinstance(data, list) else [data]
|
||||
|
||||
def _write_yaml(stream, data):
|
||||
yaml.dump(data, stream, default_flow_style=False)
|
51
py/lib/data/io.py
Normal file
51
py/lib/data/io.py
Normal file
@ -0,0 +1,51 @@
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
from sys import stdin, stdout, stderr
|
||||
|
||||
from py.lib.scwrypts.getenv import getenv
|
||||
|
||||
|
||||
@contextmanager
|
||||
def get_stream(filename=None, mode='r', encoding='utf-8', verbose=False, **kwargs):
|
||||
allowed_modes = {'r', 'w', 'w+'}
|
||||
|
||||
if mode not in allowed_modes:
|
||||
raise ValueError(f'mode "{mode}" not supported modes (must be one of {allowed_modes})')
|
||||
|
||||
is_read = mode == 'r'
|
||||
|
||||
if filename is not None:
|
||||
|
||||
if verbose:
|
||||
print(f'opening file {filename} for {"read" if is_read else "write"}', file=stderr)
|
||||
|
||||
if filename[0] not in {'/', '~'}:
|
||||
filename = Path(f'{getenv("EXECUTION_DIR")}/{filename}').resolve()
|
||||
with open(filename, mode=mode, encoding=encoding, **kwargs) as stream:
|
||||
yield stream
|
||||
|
||||
else:
|
||||
if verbose:
|
||||
print('using stdin for read' if is_read else 'using stdout for write', file=stderr)
|
||||
|
||||
yield stdin if is_read else stdout
|
||||
|
||||
|
||||
def add_io_arguments(parser, toggle_input=True, toggle_output=True):
|
||||
if toggle_input:
|
||||
parser.add_argument(
|
||||
'-i', '--input-file',
|
||||
dest = 'input_file',
|
||||
default = None,
|
||||
help = 'path to input file; omit for stdin',
|
||||
required = False,
|
||||
)
|
||||
|
||||
if toggle_output:
|
||||
parser.add_argument(
|
||||
'-o', '--output-file',
|
||||
dest = 'output_file',
|
||||
default = None,
|
||||
help = 'path to output file; omit for stdout',
|
||||
required = False,
|
||||
)
|
1
py/lib/http/__init__.py
Normal file
1
py/lib/http/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from py.lib.http.client import get_request_client
|
20
py/lib/http/client.py
Normal file
20
py/lib/http/client.py
Normal file
@ -0,0 +1,20 @@
|
||||
from requests import request
|
||||
|
||||
|
||||
def get_request_client(base_url, headers=None):
|
||||
if headers is None:
|
||||
headers = {}
|
||||
|
||||
return lambda method, endpoint, **kwargs: request(
|
||||
method = method,
|
||||
url = f'{base_url}/{endpoint}',
|
||||
headers = {
|
||||
**headers,
|
||||
**kwargs.get('headers', {}),
|
||||
},
|
||||
**{
|
||||
key: value
|
||||
for key, value in kwargs.items()
|
||||
if key != 'headers'
|
||||
},
|
||||
)
|
1
py/lib/linear/__init__.py
Normal file
1
py/lib/linear/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from py.lib.linear.client import request, graphql
|
13
py/lib/linear/client.py
Normal file
13
py/lib/linear/client.py
Normal file
@ -0,0 +1,13 @@
|
||||
from py.lib.http import get_request_client
|
||||
from py.lib.scwrypts import getenv
|
||||
|
||||
|
||||
request = get_request_client(
|
||||
base_url = 'https://api.linear.app',
|
||||
headers = {
|
||||
'Authorization': f'bearer {getenv("LINEAR__API_TOKEN")}',
|
||||
}
|
||||
)
|
||||
|
||||
def graphql(query):
|
||||
return request('POST', 'graphql', json={'query': query})
|
1
py/lib/redis/__init__.py
Normal file
1
py/lib/redis/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
|
15
py/lib/redis/client.py
Normal file
15
py/lib/redis/client.py
Normal file
@ -0,0 +1,15 @@
|
||||
from redis import StrictRedis
|
||||
|
||||
from py.lib.scwrypts import getenv
|
||||
|
||||
|
||||
class RedisClient(StrictRedis):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
host = getenv('REDIS_HOST'),
|
||||
port = getenv('REDIS_PORT'),
|
||||
password = getenv('REDIS_AUTH', required=False),
|
||||
decode_responses = True,
|
||||
)
|
||||
|
||||
Client = RedisClient()
|
3
py/lib/scwrypts/__init__.py
Normal file
3
py/lib/scwrypts/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
from py.lib.scwrypts.getenv import getenv
|
||||
from py.lib.scwrypts.interactive import interactive
|
||||
from py.lib.scwrypts.run import run
|
3
py/lib/scwrypts/exceptions.py
Normal file
3
py/lib/scwrypts/exceptions.py
Normal file
@ -0,0 +1,3 @@
|
||||
class MissingVariableError(Exception):
|
||||
def init(self, name):
|
||||
super().__init__(f'Missing required environment variable "{name}"')
|
16
py/lib/scwrypts/getenv.py
Normal file
16
py/lib/scwrypts/getenv.py
Normal file
@ -0,0 +1,16 @@
|
||||
from os import getenv as os_getenv
|
||||
|
||||
from py.lib.scwrypts.exceptions import MissingVariableError
|
||||
from py.lib.scwrypts.run import run
|
||||
|
||||
|
||||
def getenv(name, required=True):
|
||||
value = os_getenv(name, None)
|
||||
|
||||
if value == None:
|
||||
run('zsh/scwrypts/environment/stage-variables', name)
|
||||
|
||||
if required and not value:
|
||||
raise MissingVariableError(name)
|
||||
|
||||
return value
|
11
py/lib/scwrypts/interactive.py
Normal file
11
py/lib/scwrypts/interactive.py
Normal file
@ -0,0 +1,11 @@
|
||||
from bpython import embed
|
||||
|
||||
|
||||
def interactive(function):
|
||||
def main(*args, **kwargs):
|
||||
print('preparing interactive environment...')
|
||||
local_vars = function(*args, **kwargs)
|
||||
print('environment ready; user, GO! :)')
|
||||
embed(local_vars)
|
||||
|
||||
return main
|
21
py/lib/scwrypts/run.py
Normal file
21
py/lib/scwrypts/run.py
Normal file
@ -0,0 +1,21 @@
|
||||
from os import getenv
|
||||
from pathlib import Path
|
||||
from subprocess import run as subprocess_run
|
||||
|
||||
|
||||
def run(scwrypt_name, *args):
|
||||
DEPTH = int(getenv('SUBSCWRYPT', '0'))
|
||||
DEPTH += 1
|
||||
|
||||
SCWRYPTS_EXE = Path(__file__).parents[2] / 'scwrypts'
|
||||
ARGS = ' '.join([str(x) for x in args])
|
||||
|
||||
print(f'\n {"--"*DEPTH} ({DEPTH}) BEGIN SUBSCWRYPT : {Path(scwrypt_name).name}')
|
||||
subprocess_run(
|
||||
f'SUBSCWRYPT={DEPTH} {SCWRYPTS_EXE} {scwrypt_name} -- {ARGS}',
|
||||
shell=True,
|
||||
executable='/bin/zsh',
|
||||
check=False,
|
||||
)
|
||||
|
||||
print(f' {"--"*DEPTH} ({DEPTH}) END SUBSCWRYPT : {Path(scwrypt_name).name}\n')
|
Reference in New Issue
Block a user