PK:Omdfissle/__init__.py"""Simple boilerplate for cli scripts""" from collections import ChainMap, OrderedDict import inspect import configparser from pathlib import Path from fire import Fire from typing import NamedTuple # Facilitates importing from one location import os __version__ = '0.0.4' class ConfigFile: """Configuration file (sth.cfg) handling""" @staticmethod def is_in_module(f): return len(list(Path(f).parent.glob('__init__.py'))) @staticmethod def cfgs_gen(f): yield from Path(f).parent.glob('*.cfg') @staticmethod def find_cfg(f): f = Path(f) cfgs = list(ConfigFile.cfgs_gen(f)) if len(cfgs) == 0: if ConfigFile.is_in_module(f): return ConfigFile.find_cfg(f.parent) else: return None else: return cfgs[0] @staticmethod def read_config(_filepath='test.cfg') -> dict: filepath = Path(_filepath) if not filepath.exists(): return {} file_config = configparser.ConfigParser() file_config.read(filepath) return dict(file_config['Default']) @staticmethod def get_config_path(configuration_tuple): client_file = Path(inspect.getfile(configuration_tuple.__class__)) if hasattr(configuration_tuple, 'cwd'): p = Path(configuration_tuple._asdict()['cwd']) if not p.absolute(): p = client_file / p else: p = client_file config_file = ConfigFile.find_cfg(p) return config_file decorators_state = { 'schema': None, 'generated': None, } class Decorators: """Decorator helpers for the client interface (Configurable)""" @staticmethod def schema(cls): decorators_state['schema'] = cls() return cls @staticmethod def cli(cls): state = decorators_state def init(self, **cli_args): """Generated init""" c(cli_args, state['schema']) cls_attrs = dict( # __slots__=cls.__slots__, __init__=init, __repr__=cls.__repr__, **{k: v for k, v in cls.__dict__.items() if not k.startswith('_')} ) _Cli = type('Cli', (cls,), cls_attrs) state['generated'] = _Cli prepare(state['generated'], state['schema']) return _Cli class Configurable: """Configuration management""" configured = None def __filter_fields(self, d: dict, nt): """Excludes fields not found in the schema/namedtuple""" res = {} for k, v in d.items(): if k in nt._fields: res.update({k: v}) return res def __initialize(self, params: dict, configuration_tuple): """Chains all configuration options together""" config_file = ConfigFile.get_config_path(configuration_tuple) if config_file is not None: config_dict = self.__filter_fields(ConfigFile.read_config(config_file), configuration_tuple), else: config_dict = {} print(repr(config_dict)) self.configured = dict(ChainMap( params, config_dict, self.__filter_fields(os.environ, configuration_tuple), configuration_tuple._asdict() )) return self.configured def __call__(self, a=None, b=None): """Handles delegating method overloading acting as a decorator and initiator for the chain configuration""" is_initializing = (a is not None and b is not None) if is_initializing: return self.__initialize(params=a, configuration_tuple=b) is_decorating = (a is not None and b is None) if is_decorating: is_schema = any(_cls is tuple for _cls in inspect.getmro(a)) if is_schema: return Decorators.schema(a) else: return Decorators.cli(a) def __repr__(self): return repr(self.configured) def __getattr__(self, item): res = None if self.configured is not None: res = self.configured[item] return res c = Configurable() class Doc: """Doc string handling""" @staticmethod def wrap_method_docs(cls: object, nt): methods = [m.object for m in inspect.classify_class_attrs(cls) if m.kind == 'method' and not m.name.startswith('_')] for m in methods: Doc.prepare_doc(nt, m) @staticmethod def params_with_defs(N): """parse the source of the schema for its details""" params_with_definitions = tuple( tuple( str(arg_and_def).strip() for arg_and_def in src_line.split(':') ) for src_line in inspect.getsourcelines(N.__class__)[0][1:] if src_line.startswith(' ') ) return OrderedDict(params_with_definitions) @staticmethod def attr_map(N): """Mapping for the schemas details""" _attr_map = {} for param, _def in Doc.params_with_defs(N).items(): _type, def_desc = _def.split('=') _type = _type.strip() if '#' in def_desc: default, description = def_desc.split('#') default = default.strip() description = description.strip() else: default = def_desc.strip() description = '' _attr_map.update( { param: { 'type': _type, 'default': default, 'description': description, } } ) return _attr_map @staticmethod def prepare_doc(N, f): """Replace docstrings to include the parameters (schema)""" # TODO: fix for fire 0.2.1 ps_doc = [] for attr_name, cls in N.__annotations__.items(): attr = Doc.attr_map(N).get(attr_name) if attr is None: continue ps_doc.append(f' --{attr_name} ({attr["type"]}): {attr["description"]} (Default is {attr["default"]})') ps_doc = '\n'.join(ps_doc) doc = f.__doc__ doc = (doc if doc is not None else '') + '\nArgs:\n' + ps_doc f.__doc__ = doc def prepare_signatures(cls, nt): """Uses the implicit Configuration NamedTuple's fields as the classes methods' signatures i.e. helps fire to show up the named tuple's arguments instead of a generic "**params" for the subcommands on command line. Works in-place, returns None """ methods = { k: v for k, v in cls.__dict__.items() if not k.startswith('_') } for m_name, method in methods.items(): params = [ inspect.Parameter(name=field, kind=inspect._VAR_KEYWORD) for field in nt._fields ] sig = inspect.signature(method) method.__signature__ = sig.replace(parameters=[ inspect.Parameter(name='self', kind=inspect._VAR_KEYWORD), *params ]) def prepare(cls, nt): """Beef: prepares signatures, docstrings and initiates fire for the cli-magic""" prepare_signatures(cls, nt) Doc.wrap_method_docs(cls, nt) Fire(cls) PKiNq77fissle-0.0.4.dist-info/LICENSEThe MIT License (MIT) Copyright (c) 2019 Joni Turunen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!HPOfissle-0.0.4.dist-info/WHEEL HM K-*ϳR03rOK-J,/RH,szd&Y)r$[)T&UrPK!HB)S&"fissle-0.0.4.dist-info/METADATAYs>ōN2m8SRjb;&;##yCpDe9g"p۟o.^d1:8_zc ;+BnUq8J]Ea\_ʭ^zۺMc:͝i&Wʬ٦i0֮ٴ$,frH2`7߿y: sTsw_:}mixqj om.)'{j?jxe:"$4ecfzpzc *dT/s[9z>o//iUfUw{ ϟ?N$_*(ZUO?ZhX2ӹ+v^^֖LjtOrt1Fnνdmr6Z9G,sZ`[}kjVC{ҕdt5 jә]!+گ =7p*LzZ-ۀ vQfpF80Ӵ~n{^0r[DRUM׍NO4DvB^5/Qxd29'rrׯ&;B?&7#MኪQ41E=,_kNO?զL&Z(Q6f_lSdX٘ 2, pÕbEdJ㻶-R.EJVV6r; ygZ*vv[a/[­:H ]צc@LS:Dv)x$$0u$n3MA͐!&)2v2#]~ JuC%6- @ZUJq UK`Ifݭ-F]S~i>xg7꒨=9`--Sg ܰ:DG%\ $j葾(C#HU| acd&Gv'+G9.(~ h=r؀gI[$mD !uYkrC|tA+{D"kYd0I I+R_KvFHCȃ(E005:l~ն3Z=X9b[)q.=BNՖL$YTxPc " ~_w~kCďݲ3haBcbB{dtLZ}gvц~s cʠb步%.g@W.!SaaRםX @#G8{t Dp~:DxfE{!H8S<EpF}֖)D"%m@$[!FbQa=gI>l:̔(V>,Fk@'0") e]w;')Y?Ȋ]>!+@VE!PXzԽ`[H@]Hߎxl ~JQt\]V#,DE@㰈A]d:̓?!`juJq/9? C|f0mP``hLhNF4=?T`GÓdWY|`+Wl@Ĵ+i(B?o.訜=u^{rIJTY(ʨ" jLDFż3ڰ D 0j*;iwԃXE4wLڮ? }me#S[R _X=2SPAB[Jt |wN+z$_H== /G*m*Q7#gSo8LO5"9hC9jh;heME -џ֓6jX?J|P#\?[kAr@0o7|}zv p|w v>P|vH}? DuS#ȶөcl4m=>T;N-G` S;qjR~e= &ߺڗ\ɻ0zv'S#byx܍_[`4tE(z0#plB t*$" #),0ʲъ}Y3bYęJ{T>4+Ջ$] F~2RNhe PFG)8'ĿU- S:zіz@(Op@;ƞHcp/]cg.y {P "it"_?}W䱃.Cװ/ >Gz tvv&b,<@mT[2xQ 3>p_MtzeM3=ߠ|/#E 2]nε*^>%-fRf<ܙ&%Eh2LWDu?|5ܛǫQA]zLĥ:4 {ջ")b , Uq"a8\Mb^E-rJYX[t=6]I8iƜښ5 `m?H(1$2M`BpXDI+$(,B*[8knAShP67WB|F2CKLx=ȓLV4)e޾9Y"MYLEmEҞ MAZ%3mt~ci!jg8nhE(: &)|icMl!W 4xp 2_NܬGb׌ Iq?72{eRֱy?FƔLdOx/olNޔ/‚.p#j4 9@=d*R3;siW-7t_SP(zBW m׫Eg>W4`$Mg[(ai I2tnJ s,:qt r42҉V;`Ҡ5j2ccξn\AN-r ?w *D[T).QF8'c V^f 0ZxM74w&Q\gvٮ]B+;qn1/8)P9*e@Noz^{.mл!"-{)ǰPh ?F4Y +5l;zf=~q K]b |=@R0k@zPK!HM:Rmfissle-0.0.4.dist-info/RECORDuKr0нgI0]ta!H*Ma4Lg\J2j+ɹv`}ww|b;d ,lܺ,>`pS Z#gRWiHs%$ڢFKy@jLф0Tr:_yifhБMD4Lkff~5=T~Rx]RϽy<fgIv >!Th՚tEA*?2`G̱WPK:Omdfissle/__init__.pyPKiNq77fissle-0.0.4.dist-info/LICENSEPK!HPOZ"fissle-0.0.4.dist-info/WHEELPK!HB)S&""fissle-0.0.4.dist-info/METADATAPK!HM:Rmt1fissle-0.0.4.dist-info/RECORDPKn2