Skip to content

Commit

Permalink
v4.3.0
Browse files Browse the repository at this point in the history
=====================================================================

--- Changes ------------------------------

- updated python scwrypts API to use latest pattern established in the
  nodejs library

- SCWRYPTS_ROOT__scwrypts is now supports loading with each run and
  detects managed installations vs manual installations; this now means
  SCWRYPTS_ROOT can no longer be injected to scwrypts (this was a v2
  legacy support thing and probably does not apply to you)

--- New Features -------------------------

- pypi/scwrypts )
     added 'get_generator' API to testing utilities to provide a nice
     way to include default generator options

--- Bug Fixes ----------------------------

- scwrypts groups which use a required environment name regex no longer
  load specialized static files outside of the required environments.
  • Loading branch information
wrynegade committed Apr 16, 2024
1 parent aefd575 commit 8f3e862
Show file tree
Hide file tree
Showing 13 changed files with 308 additions and 78 deletions.
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ jobs:
&& echo "source=(scwrypts.tar.gz)" >> PKGBUILD
&& echo "sha256sums=(SKIP)" >> PKGBUILD
&& makepkg --noconfirm -si
&& echo validating scwrypts version
&& scwrypts --version | grep "^scwrypts v$PKGVER$"
;
Expand Down
17 changes: 7 additions & 10 deletions py/lib/scwrypts/http/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,22 @@

from pytest import fixture

from scwrypts.test import generate
from scwrypts.test import get_generator
from scwrypts.test.character_set import uri

options = {
generate = get_generator({
'str_length_minimum': 8,
'str_length_maximum': 128,
'uuid_output_type': str,
}
})

def get_request_client_sample_data():
return {
'base_url' : generate(str, options | {'character_set': uri}),
'endpoint' : generate(str, options | {'character_set': uri}),
'method' : generate(str, options),
'response' : generate('requests_Response', options | {'depth': 4}),
'base_url' : generate(str, {'character_set': uri}),
'endpoint' : generate(str, {'character_set': uri}),
'method' : generate(str),
'response' : generate('requests_Response', {'depth': 4}),
'payload' : generate(dict, {
**options,
'depth': 1,
'data_types': { str, 'uuid' },
}),
Expand All @@ -30,13 +29,11 @@ def fixture_sample():
**get_request_client_sample_data(),

headers = generate(dict, {
**options,
'depth': 1,
'data_types': { str, 'uuid' },
}),

payload_headers = generate(dict, {
**options,
'depth': 1,
'data_types': { str, 'uuid' },
}),
Expand Down
7 changes: 3 additions & 4 deletions py/lib/scwrypts/http/directus/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@

from pytest import fixture

from scwrypts.test import generate
from scwrypts.test.character_set import uri
from ..conftest import options, get_request_client_sample_data
from ..conftest import generate, get_request_client_sample_data


@fixture(name='sample')
def fixture_sample():
return SimpleNamespace(
**get_request_client_sample_data(),
api_token = generate(str, options | {'character_set': uri}),
query = generate(str, options),
api_token = generate(str, {'character_set': uri}),
query = generate(str),
)
19 changes: 9 additions & 10 deletions py/lib/scwrypts/http/discord/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@

from pytest import fixture

from scwrypts.test import generate
from scwrypts.test.character_set import uri
from ..conftest import options, get_request_client_sample_data
from ..conftest import generate, get_request_client_sample_data

@fixture(name='sample')
def fixture_sample():
Expand All @@ -14,12 +13,12 @@ def fixture_sample():
**get_request_client_sample_data(),
'base_url': 'https://discord.com/api',
},
bot_token = generate(str, options | {'character_set': uri}),
username = generate(str, options | {'character_set': ascii_letters + digits}),
avatar_url = generate(str, options | {'character_set': uri}),
webhook = generate(str, options | {'character_set': uri}),
channel_id = generate(str, options | {'character_set': uri}),
content_header = generate(str, options),
content_footer = generate(str, options),
content = generate(str, options),
bot_token = generate(str, {'character_set': uri}),
username = generate(str, {'character_set': ascii_letters + digits}),
avatar_url = generate(str, {'character_set': uri}),
webhook = generate(str, {'character_set': uri}),
channel_id = generate(str, {'character_set': uri}),
content_header = generate(str),
content_footer = generate(str),
content = generate(str),
)
7 changes: 3 additions & 4 deletions py/lib/scwrypts/http/linear/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@

from pytest import fixture

from scwrypts.test import generate
from scwrypts.test.character_set import uri
from ..conftest import options, get_request_client_sample_data
from ..conftest import generate, get_request_client_sample_data

@fixture(name='sample')
def fixture_sample():
Expand All @@ -14,6 +13,6 @@ def fixture_sample():
**get_request_client_sample_data(),
'base_url': 'https://api.linear.app',
},
api_token = generate(str, options | {'character_set': uri}),
query = generate(str, options),
api_token = generate(str, {'character_set': uri}),
query = generate(str),
)
2 changes: 1 addition & 1 deletion py/lib/scwrypts/scwrypts/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def __init__(self, flags, env_var):

class MissingScwryptsExecutableError(EnvironmentError):
def __init__(self):
super().__init__(f'scwrypts must be installed and available on your PATH')
super().__init__('scwrypts must be installed and available on your PATH')


class BadScwryptsLookupError(ValueError):
Expand Down
48 changes: 29 additions & 19 deletions py/lib/scwrypts/scwrypts/scwrypts.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,54 @@
from .exceptions import MissingScwryptsExecutableError, BadScwryptsLookupError, MissingScwryptsGroupOrTypeError


def scwrypts(*args, patterns=None, name=None, group=None, _type=None, log_level=None):
def scwrypts(patterns=None, args=None, executable_args=None, name=None, group=None, _type=None):
'''
top-level scwrypts invoker from python
- patterns allows for pattern-based scwrypt lookup
- name/group/type allos for precise-match lookup
patterns str / list pattern-based scwrypt lookup
args str / list arguments forwarded to the invoked scwrypt
executable_args str / list arguments for the 'scwrypts' executable
(str above assumes space-delimited values)
*args should be a list of strings and is forwarded to the
invoked scwrypt
name str exact scwrypt lookup name (requires group and _type)
group str exact scwrypt lookup group
_type str exact scwrypt lookup type
SCWRYPTS_EXECUTABLE configuration variable which defines the full path to scwrypts executable
see 'scwrypts --help' for more information
'''
executable = which('scwrypts')
if executable is None:
raise MissingScwryptsExecutableError()

if patterns is None and name is None:
raise BadScwryptsLookupError()

pre_args = []
if name is not None and (group is None or _type is None):
raise MissingScwryptsGroupOrTypeError(group, _type)

executable = which(getenv('SCWRYPTS_EXECUTABLE', 'scwrypts'))

if name is None:
pre_args += patterns
else:
pre_args += ['--name', name, '--group', group, '--type', _type]
if group is None or _type is None:
raise MissingScwryptsGroupOrTypeError(group, _type)
if executable is None:
raise MissingScwryptsExecutableError()

if log_level is not None:
pre_args += ['--log-level', log_level]
lookup = _parse(patterns) if name is None else f'--name {name} --group {group} --type {_type}'

depth = getenv('SUBSCWRYPT', '')
if depth != '':
depth = int(depth) + 1

return run(
f'SUBSCWRYPT={depth} {executable} {" ".join(pre_args)} -- {" ".join(args)}',
f'SUBSCWRYPT={depth} {executable} {_parse(executable_args)} {lookup} -- {_parse(args)}',
shell=True,
executable='/bin/zsh',
check=False,
capture_output=True,
text=True,
)

def _parse(string_or_list_args):
if string_or_list_args is None:
return ''

if isinstance(string_or_list_args, list):
return ' '.join(string_or_list_args)

return str(string_or_list_args)
184 changes: 184 additions & 0 deletions py/lib/scwrypts/scwrypts/test_scwrypts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
from random import choice
from re import search
from string import ascii_letters, digits
from types import SimpleNamespace
from unittest.mock import patch

from pytest import fixture, raises

from scwrypts.test import get_generator

from .exceptions import MissingScwryptsExecutableError, BadScwryptsLookupError, MissingScwryptsGroupOrTypeError
from .scwrypts import scwrypts

#####################################################################

def test_scwrypts(sample, _scwrypts):
assert validate_scwrypts_output(sample, _scwrypts)

def test_scwrypts_finds_system_executable(sample, _scwrypts, mock_which):
mock_which.assert_called_once_with(sample.env['SCWRYPTS_EXECUTABLE'])

def test_scwrypts_uses_configured_executable_path(_scwrypts, mock_getenv):
mock_getenv.assert_any_call('SCWRYPTS_EXECUTABLE', 'scwrypts')

def test_scwrypts_uses_correct_depth(_scwrypts, mock_getenv):
mock_getenv.assert_any_call('SUBSCWRYPT', '')

def test_scwrypts_runs_subprocess(_scwrypts, mock_run):
mock_run.assert_called_once()

##########################################

def test_scwrypts_omit_optionals(sample, _scwrypts_omit_optionals):
assert validate_scwrypts_output(sample, _scwrypts_omit_optionals)

def test_scwrypts_omit_optionals_finds_system_executable(sample, _scwrypts_omit_optionals, mock_which):
mock_which.assert_called_once_with('scwrypts')

def test_scwrypts_omit_optionals_uses_configured_executable_path(_scwrypts_omit_optionals, mock_getenv):
mock_getenv.assert_any_call('SCWRYPTS_EXECUTABLE', 'scwrypts')

def test_scwrypts_omit_optionals_uses_correct_depth(_scwrypts_omit_optionals, mock_getenv):
mock_getenv.assert_any_call('SUBSCWRYPT', '')

def test_scwrypts_omit_optionals_runs_subprocess(_scwrypts_omit_optionals, mock_run):
mock_run.assert_called_once()

##########################################

def test_invalid_lookup_missing_patterns_and_name(sample):
sample.patterns = None
sample.name = None
with raises(BadScwryptsLookupError):
scwrypts(**get_scwrypts_args(sample))

def test_invalid_name_lookup_missing_group(sample):
sample.group = None
with raises(MissingScwryptsGroupOrTypeError):
scwrypts(**get_scwrypts_args(sample))

def test_invalid_name_lookup_missing_type(sample):
sample._type = None # pylint: disable=protected-access
with raises(MissingScwryptsGroupOrTypeError):
scwrypts(**get_scwrypts_args(sample))

def test_invalid_scwrypts_installation(sample, mock_which):
mock_which.return_value = None
with raises(MissingScwryptsExecutableError):
scwrypts(**get_scwrypts_args(sample))

#####################################################################

generate = get_generator({
'str_length_minimum': 8,
'str_length_maximum': 128,
'character_set': ascii_letters + digits + '/-_'
})

def _generate_str_or_list_arg():
random_arg = generate(list, {'data_types': {str}})
return random_arg if choice([str, list]) == list else ' '.join(random_arg)

@fixture(name='sample')
def fixture_sample():
sample = SimpleNamespace(
patterns = _generate_str_or_list_arg(),
args = _generate_str_or_list_arg(),
executable_args = _generate_str_or_list_arg(),

name = generate(str),
group = generate(str),
_type = generate(str),

executable = generate(str),

env = {
'SCWRYPTS_EXECUTABLE': generate(str),
'SUBSCWRYPT': str(generate(int, {'minimum': 1, 'maximum': 99})),
},

returncode = generate(int),
stdout = generate(str),
stderr = generate(str),
)

return sample

def get_scwrypts_args(sample):
return {
key: getattr(sample, key)
for key in [
'patterns',
'args',
'executable_args',
'name',
'group',
'_type',
]
}


#####################################################################

@fixture(name='mock_which', autouse=True)
def fixture_mock_which(sample):
with patch('scwrypts.scwrypts.scwrypts.which') as mock:
mock.return_value = sample.executable
yield mock

@fixture(name='mock_getenv', autouse=True)
def fixture_mock_getenv(sample):
with patch('scwrypts.scwrypts.scwrypts.getenv') as mock:
mock.side_effect = sample.env.get
yield mock

@fixture(name='mock_run', autouse=True)
def fixture_mock_run(sample):
with patch('scwrypts.scwrypts.scwrypts.run') as mock:
mock.side_effect = lambda *args, **_kwargs: SimpleNamespace(
args = args,
returncode = sample.returncode,
stdout = sample.stdout,
stderr = sample.stderr,
)
yield mock

#####################################################################

@fixture(name='_scwrypts')
def fixture_scwrypts(sample):
return scwrypts(**get_scwrypts_args(sample))

@fixture(name='_scwrypts_omit_optionals')
def fixture_scwrypts_omit_optionals(sample):
sample.args = None
sample.executable_args = None

del sample.env['SCWRYPTS_EXECUTABLE']
del sample.env['SUBSCWRYPT']

return scwrypts(**get_scwrypts_args(sample))

def validate_scwrypts_output(sample, output):
#
# I would love to use 'assert _scwrypts == SimpleNamespace(...expected...)'
# but the output.args is difficult to recreate without copying all the
# processing logic over from the scwrypts function
#
# opting for a bit of a strange equality test here, checking the args
# as closely as possible without copying parsing logic
#
run_args_reduced_to_a_single_string = len(output.args) == 1
run_args_follow_expected_form = search(
fr'^SUBSCWRYPT=.* {sample.executable} .*-- .*$',
output.args[0],
)

return all([
run_args_reduced_to_a_single_string,
run_args_follow_expected_form,
output.returncode == sample.returncode,
output.stdout == sample.stdout,
output.stderr == sample.stderr,
])
2 changes: 1 addition & 1 deletion py/lib/scwrypts/test/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
'generate',
]

from .generate import generate
from .generate import generate, get_generator

from .character_set import *
Loading

0 comments on commit 8f3e862

Please sign in to comment.