Skip to content

Commit

Permalink
Merge pull request #50 from cloudblue/feature/playground
Browse files Browse the repository at this point in the history
Inital commit of playground feature
  • Loading branch information
marcserrat authored Jun 17, 2021
2 parents f620664 + 969416d commit 72822a2
Show file tree
Hide file tree
Showing 15 changed files with 489 additions and 1 deletion.
2 changes: 1 addition & 1 deletion connect/cli/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def print_version(ctx, param, value):
ctx.exit()


@click.group()
@click.group(context_settings={'help_option_names': ['-h', '--help']})
@click.option(
'--version',
is_flag=True,
Expand Down
100 changes: 100 additions & 0 deletions connect/cli/plugins/play/commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# -*- coding: utf-8 -*-

# This file is part of the Ingram Micro Cloud Blue Connect connect-cli.
# Copyright (c) 2021 Ingram Micro. All Rights Reserved.
import os
import sys

import click

from connect.cli.core.config import pass_config
from connect.cli.plugins.play.context import Context


@click.group(name='play', short_help='Play connect scripts.')
def grp_play():
pass


class PlayOptions:
context_file = 'context.json'


class PassArgumentDecorator:
def __init__(self, arg):
self.obj = arg

def __call__(self, f):
def wrapped(*args, **kwargs):
f(self.obj, *args, **kwargs)

return wrapped


pass_arg = PassArgumentDecorator


def setup_script_command(cls):
@pass_config
@pass_arg(cls)
def cmd_play_custom(script_class, config, **kwargs):
config.validate()

Context.context_file_name = PlayOptions.context_file
ctx = Context.create(**kwargs)

if 'endpoint' not in ctx or not ctx.endpoint:
ctx.endpoint = config.active.endpoint

if 'distributor_account_token' not in ctx or not ctx.distributor_account_token:
ctx.distributor_account_token = config.active.api_key

ctx | script_class()
ctx.save()

options = cls.options()
for o in options:
cmd_play_custom = click.option(*o.args, **o.kwargs)(cmd_play_custom)

grp_play.command(name=cls.command(), short_help=cls.help())(cmd_play_custom)


def load_one_script(scripts, filename):
modname = filename[0:-3]

try:
mod = __import__(modname, globals={"__name__": __name__}, fromlist=['*'])

if not hasattr(mod, '__all__'):
print(f'Warning: {filename} has no __all__ defined', file=sys.stderr)
return

for cname in mod.__all__:
cls = getattr(mod, cname)
setup_script_command(cls)

except Exception as e:
print(f'Failed to import {scripts}/{filename}: {e}')


def load_scripts_actions():
scripts = os.environ.get('CCLI_SCRIPTS', 'scripts')
if scripts[0] != '/':
scripts = os.path.join(os.getcwd(), scripts)

if os.path.isdir(scripts):
print(f'Reading scripts library from {scripts}')
sys.path.append(scripts)

for filename in sorted(os.listdir(scripts)):
if not filename.endswith('.py') or filename[0] == '_':
continue

load_one_script(scripts, filename)


load_scripts_actions()


def get_group():
return grp_play
89 changes: 89 additions & 0 deletions connect/cli/plugins/play/context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import inspect
import json
import sys


class Context(dict):
context_file_name = None

@classmethod
def create_from_file(cls, filename=context_file_name):
ctx = cls()
try:
ctx.load(filename)
except FileNotFoundError:
pass

return ctx

@classmethod
def create(cls, args=None, filename=context_file_name, **kwargs):
ctx = cls()
try:
ctx.load(filename)
except FileNotFoundError:
pass

if args:
ctx.parse_args(args)

if kwargs:
for k, v in kwargs.items():
if v is not None:
ctx[k] = v

return ctx

def parse_args(self, args):
for k, v in [a.split('=') for a in args]:
self[k] = v

def load(self, filename=context_file_name):
if filename:
with open(filename) as f:
print(f'Loading context from {filename}', file=sys.stderr)
self.clear()
for k, v in json.load(f).items():
self[k] = v

def save(self, filename=context_file_name):
if filename:
with open(filename, 'w') as f:
print(f'Saving context into {filename}', file=sys.stderr)
json.dump(self, f, indent=4)

def __str__(self):
return json.dumps(self, indent=4)

def __getattr__(self, item):
if item in self:
return self[item]

raise KeyError(item)

def __setattr__(self, key, value):
self[key] = value

def __ior__(self, kv):
key, value = kv
if isinstance(value, dict):
if key not in self:
self[key] = {}
self[key].update(value)
else:
if not isinstance(value, list):
value = [value]

if key not in self:
self[key] = []

self[key].extend(value)

return self

def __or__(self, step):
if inspect.isclass(step):
step = step()

step.do(context=self)
return self
8 changes: 8 additions & 0 deletions connect/cli/plugins/play/save.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from connect.cli.plugins.play.context import Context
from connect.cli.plugins.play.script import Script


class Save(Script):
def do(self, filename=Context.context_file_name, context=None):
super().do(context=context)
self.context.save(filename=filename)
49 changes: 49 additions & 0 deletions connect/cli/plugins/play/script.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import re
from typing import List

from connect.cli.plugins.play.context import Context
from connect.client import ConnectClient


class OptionWrapper:
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs


class Script:
context: Context = None
endpoint: str = None

def __init__(self, context=None, **kwargs):
self.context = context if context is not None else Context()
self.context.update(kwargs)

@classmethod
def command(cls) -> str:
return str(re.sub(r'^([A-Z])', lambda x: x.group(1).lower(),
re.sub(r'([a-z])([A-Z])', lambda x: f'{x.group(1)}-{x.group(2).lower()}', cls.__name__)))

@classmethod
def help(cls) -> str:
return cls.__doc__

@classmethod
def options(cls) -> List[OptionWrapper]:
return []

def client(self, token) -> ConnectClient:
return ConnectClient(token, endpoint=self.context.endpoint, use_specs=False)

@property
def dclient(self) -> ConnectClient:
return self.client(self.context.distributor_account_token)

@property
def vclient(self) -> ConnectClient:
return self.client(self.context.vendor_account_token)

def do(self, context=None):
if context:
context.update(self.context)
self.context = context
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ ccli = 'connect.cli.ccli:main'
"product" = "connect.cli.plugins.product.commands:get_group"
"project" = "connect.cli.plugins.project.commands:get_group"
"report" = "connect.cli.plugins.report.commands:get_group"
"play" = "connect.cli.plugins.play.commands:get_group"


[tool.poetry.dependencies]
Expand Down
10 changes: 10 additions & 0 deletions tests/plugins/play/context.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"endpoint": "https://api.cnct.info/public/v1",
"program_agreement_id": "AGP-927-440-678",
"distribution_agreements": [
"AGD-199-236-391",
"AGD-669-983-907"
],
"program_contract_id": "CRP-41033-36725-76650",
"vendor_account_id": "VA-677-276"
}
Empty file.
1 change: 1 addition & 0 deletions tests/plugins/play/scripts/data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
33 changes: 33 additions & 0 deletions tests/plugins/play/scripts/script1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@

import random
import sys

from connect.cli.plugins.play.context import Context
from connect.cli.plugins.play.script import OptionWrapper, Script
from connect.cli.plugins.play.save import Save


class Script1(Script):
"""CLI help for Script1."""

@classmethod
def options(cls):
return [
OptionWrapper('--some_id', help='Script1 IDs'),
OptionWrapper('--account_token', help='Script1 account token'),
]

def do(self, context=None):
super().do(context=context)
print('--- Init Script 1 ---')


__all__ = ('Script1',)

if __name__ == '__main__':
try:
ctx = Context.create(sys.argv[1:])
ctx | Script1 | Save
print(ctx)
except Exception as e:
print(e)
32 changes: 32 additions & 0 deletions tests/plugins/play/scripts/script2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@

import random
import sys

from connect.cli.plugins.play.context import Context
from connect.cli.plugins.play.script import OptionWrapper, Script
from connect.cli.plugins.play.save import Save


class Script2(Script):
"""CLI help for Script2."""

@classmethod
def options(cls):
return [
OptionWrapper('--account_token', help='Script2 account token'),
]

def do(self, context=None):
super().do(context=context)
print('--- Init Script 2 ---')


# __all__ = ('Script2',) # intentional mistake

if __name__ == '__main__':
try:
ctx = Context.create(sys.argv[1:])
ctx | Script2 | Save
print(ctx)
except Exception as e:
print(e)
Loading

0 comments on commit 72822a2

Please sign in to comment.