├── test ├── callbacks │ ├── __init__.py │ └── callbacks.py ├── argparse │ ├── .testmondata │ ├── argparse_dev.py │ └── test_args.py └── test_properties.py ├── .githooks ├── .gitignore └── post-commit ├── pytest.ini ├── .testmondata ├── docs ├── _static │ ├── my_theme.css │ └── pygments_adjust.css ├── install.rst ├── index.rst ├── conf.py └── Makefile ├── declarative ├── version.py ├── utilities │ ├── __init__.py │ ├── unique.py │ ├── super_base.py │ ├── interrupt_delay.py │ ├── future3.py │ ├── representations.py │ └── future_from_2.py ├── auto_version.py ├── argparse │ ├── __init__.py │ ├── annotations.py │ └── oo_argparse.py ├── bunch │ ├── __init__.py │ ├── quickbunch.py │ ├── tag_bunch.py │ ├── shadow_bunch.py │ ├── deep_bunch.py │ ├── bunch.py │ └── hdf_deep_bunch.py ├── callbacks │ ├── __init__.py │ ├── validators.py │ ├── relay.py │ ├── callbacks.py │ └── state_booleans.py ├── run.py ├── properties │ ├── __init__.py │ ├── utilities.py │ ├── bases.py │ ├── memoized_adv.py │ ├── memoized_adv_group.py │ └── memoized.py ├── __init__.py ├── overridable_object.py └── metaclass.py ├── Makefile ├── MANIFEST.in ├── .gitignore ├── setup.cfg ├── tox.ini ├── LICENSE ├── setup.py ├── setup_helper.py └── README.md /test/callbacks/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.githooks/.gitignore: -------------------------------------------------------------------------------- 1 | msurrogate/ 2 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | 2 | 3 | [pytest] 4 | norecursedirs=_old 5 | 6 | -------------------------------------------------------------------------------- /.testmondata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccullerlp/python-declarative/HEAD/.testmondata -------------------------------------------------------------------------------- /docs/_static/my_theme.css: -------------------------------------------------------------------------------- 1 | 2 | .wy-nav-content { 3 | max-width: 1200px !important; 4 | } 5 | -------------------------------------------------------------------------------- /test/argparse/.testmondata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccullerlp/python-declarative/HEAD/test/argparse/.testmondata -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | .. _install: 2 | 3 | GWINC installation 4 | ==================================================== 5 | 6 | TODO 7 | -------------------------------------------------------------------------------- /declarative/version.py: -------------------------------------------------------------------------------- 1 | """ 2 | """ 3 | from __future__ import division, print_function, unicode_literals 4 | 5 | version_info = (1, 3, 2) 6 | version = '.'.join(str(v) for v in version_info) 7 | __version__ = version 8 | 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | #mainly just recipes to help distribution 3 | .PHONY: git-package 4 | 5 | git-package: 6 | mkdir -p dist 7 | git archive --format=tar.gz master -o dist/IIRrational-`git describe --abbrev=0 --tags`.tar.gz 8 | 9 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.rst 3 | include README.md 4 | include README.ipynb 5 | include MANIFEST.in 6 | include setup.cfg 7 | include setup.py 8 | include setup_helper.py 9 | 10 | recursive-include test/ *.py 11 | 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | MANIFEST 2 | venv/ 3 | *.egg-info/ 4 | build/ 5 | dist/ 6 | site/ 7 | 8 | .tox/ 9 | 10 | TAGS 11 | .cache 12 | core.* 13 | 14 | debian 15 | 16 | *.syncthing.* 17 | *sync-conflict* 18 | *.pyc 19 | *.pyo 20 | *.swp 21 | .*.swp 22 | .#* 23 | 24 | -------------------------------------------------------------------------------- /declarative/utilities/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | """ 4 | 5 | from .super_base import SuperBase 6 | 7 | from .representations import ReprMixin 8 | 9 | from .unique import ( 10 | NOARG, 11 | unique_generator, 12 | ) 13 | 14 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | # This flag says that the code is written to work on both Python 2 and Python 3 | # 3. If at all possible, it is good practice to do this. If you cannot, you 4 | # will need to generate wheels for each Python version that you support. 5 | universal=1 6 | -------------------------------------------------------------------------------- /declarative/auto_version.py: -------------------------------------------------------------------------------- 1 | """ 2 | AUTOGENERATED by git post-commit hook. Note that these are always from the PREVIOUS commit, and so may be out of date by one log entry 3 | """ 4 | git_branch = "master" 5 | git_shorthash = "11521c7" 6 | git_longhash = "11521c7a3b8ccd0b1aa25960a2b1c1ffe3d51c5f" 7 | git_last_tag = "1.2.0" 8 | -------------------------------------------------------------------------------- /declarative/argparse/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import ( 3 | division, 4 | print_function, 5 | absolute_import, 6 | ) 7 | 8 | from .oo_argparse import ( 9 | OOArgParse, 10 | ) 11 | 12 | from .annotations import ( 13 | store_true, 14 | store_false, 15 | argument, 16 | group, 17 | command, 18 | ) 19 | -------------------------------------------------------------------------------- /declarative/utilities/unique.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | """ 4 | #make noarg a VERY unique value. Was using empty tuple, but python caches it, this should be quite unique 5 | def unique_generator(): 6 | def UNIQUE_CLOSURE(): 7 | return UNIQUE_CLOSURE 8 | return ("", UNIQUE_CLOSURE,) 9 | 10 | 11 | NOARG = unique_generator() 12 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # content of: tox.ini , put in same dir as setup.py 2 | [tox] 3 | envlist = 4 | py35 5 | py36 6 | py27 7 | py34 8 | indexserver = 9 | DEV = http://localhost:8085 10 | [testenv] 11 | setenv = 12 | PYTHONPATH = '' 13 | deps = 14 | pytest 15 | pytest-xdist 16 | pytest-benchmark 17 | numpy 18 | commands=py.test test/ -s 19 | pip_pre=True 20 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. _index: 2 | 3 | ligodtt 4 | ==================================================== 5 | 6 | 7 | 8 | Requirements 9 | ------------- 10 | 11 | .. autofunction:: ligodtt.dtt_read 12 | :members: 13 | :private-members: 14 | :special-members: 15 | 16 | 17 | .. autoclass:: ligodtt.DiagAccess 18 | :members: 19 | :private-members: 20 | :special-members: 21 | 22 | Contents 23 | ------------ 24 | 25 | .. toctree:: 26 | :maxdepth: 2 27 | 28 | 29 | -------------------------------------------------------------------------------- /declarative/bunch/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .bunch import ( 4 | Bunch, 5 | FrozenBunch, 6 | HookBunch, 7 | ) 8 | 9 | from .depbunch import ( 10 | DepBunch, 11 | depB_property, 12 | depB_value, 13 | depB_extract, 14 | depB_lambda, 15 | depBp, 16 | depBv, 17 | depBe, 18 | depBl, 19 | ) 20 | 21 | from .deep_bunch import ( 22 | DeepBunch, 23 | DeepBunchSingleAssign, 24 | ) 25 | 26 | from .shadow_bunch import ShadowBunch 27 | 28 | from .tag_bunch import TagBunch 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Lee McCuller 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /declarative/callbacks/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .callbacks import ( 4 | Callback, 5 | callbackmethod, 6 | callbackstaticmethod, 7 | singlecallbackmethod, 8 | SingleCallback, 9 | ) 10 | 11 | from .relay import ( 12 | RelayValue, 13 | RelayValueRejected, 14 | RelayValueCoerced, 15 | ) 16 | 17 | from .validators import ( 18 | min_max_validator_int, 19 | min_max_validator, 20 | ) 21 | 22 | from .state_booleans import ( 23 | RelayBool, 24 | RelayBoolNot, 25 | RelayBoolAll, 26 | RelayBoolAny, 27 | RelayBoolNotAll, 28 | RelayBoolNotAny, 29 | ) 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /declarative/run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | """ 5 | from __future__ import division, print_function, absolute_import 6 | import sys 7 | import importlib 8 | 9 | if __name__ == '__main__': 10 | script = sys.argv[1] 11 | args = sys.argv[2:] 12 | 13 | split_idx = script.rfind('.') 14 | mod = script[:split_idx] 15 | t_obj = script[split_idx+1:] 16 | print("Importing module: ", mod) 17 | module = importlib.import_module(mod) 18 | print("Getting Class: ", t_obj) 19 | 20 | cls = getattr(module, t_obj) 21 | 22 | cls.__cls_argparse__( 23 | args, 24 | __usage_prog__ = 'python -m declarative.run {0}'.format(script), 25 | ) 26 | -------------------------------------------------------------------------------- /declarative/utilities/super_base.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | """ 5 | from __future__ import ( 6 | division, 7 | print_function, 8 | absolute_import, 9 | ) 10 | #from builtins import object 11 | 12 | 13 | class SuperBase(object): 14 | """ 15 | """ 16 | def __new__(cls, *args, **kwargs): 17 | return super(SuperBase, cls).__new__(cls) 18 | 19 | def __init__(self, **kwargs): 20 | super(SuperBase, self).__init__() 21 | if kwargs: 22 | raise RuntimeError( 23 | ( 24 | "kwargs has extra items for class {0}, contains: {1}" 25 | ).format(self.__class__, kwargs) 26 | ) 27 | return 28 | 29 | 30 | -------------------------------------------------------------------------------- /declarative/properties/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | """ 4 | from __future__ import ( 5 | division, 6 | print_function, 7 | absolute_import, 8 | ) 9 | 10 | from .bases import ( 11 | PropertyTransforming, 12 | HasDeclaritiveAttributes, 13 | InnerException, 14 | PropertyAttributeError, 15 | ) 16 | 17 | from .memoized import ( 18 | memoized_class_property, 19 | mproperty, 20 | dproperty, 21 | mproperty_plain, 22 | dproperty_plain, 23 | mproperty_fns, 24 | dproperty_fns, 25 | mfunction, 26 | ) 27 | 28 | from .memoized_adv import ( 29 | mproperty_adv, 30 | dproperty_adv, 31 | ) 32 | 33 | from .memoized_adv_group import ( 34 | dproperty_adv_group, 35 | mproperty_adv_group, 36 | group_mproperty, 37 | group_dproperty, 38 | ) 39 | 40 | #because this is the critical unique object 41 | from ..utilities.unique import ( 42 | NOARG, 43 | ) 44 | -------------------------------------------------------------------------------- /test/callbacks/callbacks.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | """ 4 | from __future__ import (division, print_function, absolute_import) 5 | #from builtins import object 6 | 7 | from declarative.callbacks import (Callback, callbackmethod) 8 | 9 | from declarative import ( 10 | OverridableObject, 11 | mproperty, 12 | NOARG, 13 | ) 14 | 15 | 16 | oldprint = print 17 | print_test_list = [] 18 | def print(*args): 19 | oldprint(*args) 20 | if len(args) == 1: 21 | print_test_list.append(args[0]) 22 | else: 23 | print_test_list.append(args) 24 | 25 | 26 | def message(message): 27 | print(message) 28 | 29 | class TCallback(object): 30 | 31 | def __init__(self): 32 | self.callback = Callback() 33 | 34 | @callbackmethod 35 | def callbackmethod(self, message): 36 | print(('callbackmethod', message)) 37 | 38 | T = TCallback() 39 | T.callback.register(callback = print, key = print) 40 | T.callbackmethod.register(callback = message, key = message) 41 | 42 | T.callback('hello') 43 | T.callbackmethod('world') 44 | 45 | 46 | -------------------------------------------------------------------------------- /declarative/bunch/quickbunch.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | """ 4 | from __future__ import division, print_function, unicode_literals 5 | try: 6 | from collections.abc import Mapping as MappingABC 7 | except ImportError: 8 | from collections import Mapping as MappingABC 9 | from ..utilities.future_from_2 import str, unicode 10 | 11 | 12 | class QuickBunch(dict): 13 | def __getattr__(self, name): 14 | return self[name] 15 | 16 | def __setattr__(self, name, val): 17 | self[name] = val 18 | return 19 | 20 | def __dir__(self): 21 | dir_lst = list(super(QuickBunch, self).__dir__()) 22 | dir_lst = dir_lst + list(k for k in self.keys() if isinstance(k, (str, unicode))) 23 | dir_lst.sort() 24 | return dir_lst 25 | 26 | def __getstate__(self): 27 | return self.__dict__.copy() 28 | 29 | def __setstate__(self, state): 30 | self.__dict__.update(state) 31 | 32 | def get(self, name, *default): 33 | return super(QuickBunch, self).get(name, *default) 34 | 35 | MappingABC.register(QuickBunch) 36 | 37 | -------------------------------------------------------------------------------- /declarative/utilities/interrupt_delay.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | """ 4 | import signal 5 | import logging 6 | import sys 7 | 8 | 9 | 10 | if sys.version_info > (3,4): 11 | import threading 12 | def is_main_thread(): 13 | return threading.current_thread() == threading.main_thread() 14 | else: 15 | import threading 16 | def is_main_thread(): 17 | return isinstance(threading.current_thread(), threading._MainThread) 18 | 19 | 20 | class DelayedKeyboardInterrupt(object): 21 | def __enter__(self): 22 | self.signal_received = False 23 | self.old_handler = signal.getsignal(signal.SIGINT) 24 | #only needed from main thread, and only main thread can handle signals 25 | if is_main_thread(): 26 | signal.signal(signal.SIGINT, self.handler) 27 | 28 | def handler(self, signal, frame): 29 | self.signal_received = (signal, frame) 30 | logging.debug('SIGINT received. Delaying KeyboardInterrupt.') 31 | 32 | def __exit__(self, type, value, traceback): 33 | if is_main_thread(): 34 | signal.signal(signal.SIGINT, self.old_handler) 35 | if self.signal_received: 36 | self.old_handler(*self.signal_received) 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /.githooks/post-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## Automatically generate a file with git branch and revision info 4 | ## 5 | ## Example: 6 | ## [master]v2.0.0-beta-191(a830382) 7 | ## Install: 8 | ## cp git-create-revisioninfo-hook.sh .git/hooks/post-commit 9 | ## cp git-create-revisioninfo-hook.sh .git/hooks/post-checkout 10 | ## cp git-create-revisioninfo-hook.sh .git/hooks/post-merge 11 | ## chmod +x .git/hooks/post-* 12 | LC_ALL=C 13 | 14 | local_branch="$(git rev-parse --abbrev-ref HEAD)" 15 | 16 | valid_branch_regex="^(debian|redhat)\/[a-z0-9._-]+$" 17 | 18 | if [[ ! $local_branch =~ $valid_branch_regex ]] 19 | then 20 | #skip the hook if we are on a debian or redhat branch 21 | exit 0 22 | fi 23 | 24 | FILENAME='IIRrational/auto_version.py' 25 | 26 | #exec 1>&2 27 | branch=`git rev-parse --abbrev-ref HEAD` 28 | shorthash=`git log --pretty=format:'%h' -n 1` 29 | longhash=`git log --pretty=format:'%H' -n 1` 30 | latesttag=`git describe --tags --abbrev=0` 31 | 32 | cat << EOF > $FILENAME 33 | """ 34 | AUTOGENERATED by git post-commit hook. Note that these are always from the PREVIOUS commit, and so may be out of date by one log entry 35 | """ 36 | git_branch = "$branch" 37 | git_shorthash = "$shorthash" 38 | git_longhash = "$longhash" 39 | git_last_tag = "$latesttag" 40 | EOF 41 | 42 | git add $FILENAME 43 | -------------------------------------------------------------------------------- /test/argparse/argparse_dev.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Tests of the argparse api. Not really testing the declarative argparse library, but helpful for development of it. 5 | """ 6 | from __future__ import print_function 7 | import argparse 8 | 9 | pp = argparse.ArgumentParser( 10 | description="HHMMM", 11 | add_help = False 12 | ) 13 | spp = pp.add_subparsers(dest = 'cmd') 14 | spp1 = spp.add_parser('test', help = 'HMM') 15 | spp1.add_argument('args', nargs='*', help = 'holds arguments for other') 16 | spp1 = spp.add_parser('t2', help = 'cool') 17 | spp1.add_argument('args', nargs=argparse.REMAINDER, help = 'holds arguments for other') 18 | 19 | p = argparse.ArgumentParser( 20 | description="HHMMM", 21 | parents = [pp], 22 | ) 23 | 24 | p.add_argument('-t') 25 | 26 | g = p.add_argument_group('system') 27 | g.add_argument('-i', '--ifo', required = True) 28 | 29 | g = p.add_argument_group('xyz', 'mutually ex').add_mutually_exclusive_group() 30 | g.add_argument('-x') 31 | g.add_argument('-y') 32 | g.add_argument('-z') 33 | 34 | p.add_argument( 35 | 'pos', 36 | default=None, 37 | choices=['rock', 'paper', 'scissors'], 38 | nargs='?', 39 | help = argparse.SUPPRESS, 40 | ) 41 | p.add_argument( 42 | 'args', 43 | nargs=argparse.REMAINDER, 44 | help = argparse.SUPPRESS, 45 | ) 46 | 47 | 48 | a = p.parse_args() 49 | print(a) 50 | -------------------------------------------------------------------------------- /test/test_properties.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Test of the argparse library 5 | 6 | TODO: use automated features 7 | """ 8 | from __future__ import (division, print_function, absolute_import) 9 | 10 | from declarative import ( 11 | OverridableObject, 12 | mproperty, 13 | NOARG, 14 | ) 15 | 16 | 17 | oldprint = print 18 | print_test_list = [] 19 | def print(*args): 20 | oldprint(*args) 21 | if len(args) == 1: 22 | print_test_list.append(args[0]) 23 | else: 24 | print_test_list.append(args) 25 | 26 | 27 | class OOT(OverridableObject): 28 | """ 29 | Runs the argparse test setup 30 | """ 31 | @mproperty 32 | def A(self, val = NOARG): 33 | if val is NOARG: 34 | val = 'A' 35 | print(val) 36 | return val 37 | 38 | @mproperty 39 | def B(self, val = NOARG): 40 | if val is NOARG: 41 | val = 'B' 42 | print(val) 43 | return val 44 | 45 | 46 | def test_mproperty(): 47 | print_test_list[:] = [] 48 | test = OOT() 49 | assert(print_test_list == []) 50 | 51 | print_test_list[:] = [] 52 | test = OOT() 53 | test.A 54 | assert(print_test_list == ['A']) 55 | 56 | print_test_list[:] = [] 57 | test = OOT(B = 'BB') 58 | test.A 59 | test.B 60 | assert(print_test_list == ['A', 'BB']) 61 | 62 | 63 | class TBadAccess(OverridableObject): 64 | """ 65 | Runs the argparse test setup 66 | """ 67 | @mproperty 68 | def no_args(self): 69 | print('A') 70 | return 'A' 71 | 72 | @mproperty 73 | def bad_set(self): 74 | None.Test 75 | return 'B' 76 | 77 | 78 | if __name__ == '__main__': 79 | test_mproperty 80 | 81 | -------------------------------------------------------------------------------- /declarative/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | """ 5 | from .properties import ( 6 | HasDeclaritiveAttributes, 7 | mproperty, 8 | dproperty, 9 | mproperty_fns, 10 | dproperty_fns, 11 | mproperty_plain, 12 | dproperty_plain, 13 | mfunction, 14 | PropertyTransforming, 15 | PropertyAttributeError, 16 | group_dproperty, 17 | group_mproperty, 18 | mproperty_adv, 19 | dproperty_adv, 20 | mproperty_adv_group, 21 | dproperty_adv_group, 22 | ) 23 | 24 | from .callbacks import ( 25 | callbackmethod, 26 | Callback, 27 | RelayBool, 28 | RelayBoolAny, 29 | RelayBoolAll, 30 | RelayBoolNotAny, 31 | RelayBoolNotAll, 32 | RelayBoolNot, 33 | RelayValue, 34 | RelayValueRejected, 35 | RelayValueCoerced, 36 | min_max_validator, 37 | min_max_validator_int, 38 | ) 39 | 40 | from .bunch import ( 41 | Bunch, 42 | FrozenBunch, 43 | DeepBunch, 44 | TagBunch, 45 | DepBunch, 46 | depB_property, 47 | depB_value, 48 | depB_extract, 49 | depB_lambda, 50 | depBp, 51 | depBv, 52 | depBe, 53 | depBl, 54 | ) 55 | 56 | from .overridable_object import ( 57 | OverridableObject, 58 | ) 59 | 60 | from .metaclass import ( 61 | AutodecorateMeta, 62 | Autodecorate, 63 | AttrExpandingObject, 64 | GetSetAttrExpandingObject, 65 | ) 66 | 67 | from .utilities.unique import ( 68 | NOARG, 69 | unique_generator, 70 | ) 71 | 72 | from .version import ( 73 | version, 74 | __version__, 75 | ) 76 | 77 | def first_non_none(*args): 78 | for a in args: 79 | if a is not None: 80 | return a 81 | return None 82 | 83 | FNN = first_non_none 84 | 85 | PropertyAttributeError.__module__ == __name__ 86 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from __future__ import division, print_function, unicode_literals 4 | import sys 5 | from setuptools import find_packages, setup 6 | import setup_helper 7 | 8 | version = '1.3.2' 9 | 10 | cmdclass = setup_helper.version_checker(version, 'declarative') 11 | 12 | extra_install_requires = [] 13 | if sys.version_info < (3, 0): 14 | extra_install_requires.append('future') 15 | 16 | setup( 17 | name = 'declarative', 18 | version = version, 19 | url = 'https://github.com/mccullerlp/python-declarative', 20 | author = 'Lee McCuller', 21 | author_email = 'Lee.McCuller@gmail.com', 22 | license = 'Apache-2.0', 23 | packages = find_packages(exclude = ['docs']), 24 | description = ( 25 | 'Collection of decorators and base classes to allow a declarative style of programming. Excellent for event-loop task registration.' 26 | 'Also included are nesting attribute-access dictionaries (Bunches) as well as value storage with callbacks. Relatively Magic-Free.' 27 | ), 28 | install_requires =[] + extra_install_requires, 29 | extras_require ={ 30 | "hdf" : ["h5py"], 31 | }, 32 | tests_require=['pytest'], 33 | test_suite = 'pytest', 34 | cmdclass = cmdclass, 35 | zip_safe = True, 36 | keywords = [ 37 | 'declarative', 38 | 'oop', 39 | 'bunch', 40 | 'callback', 41 | 'attributes', 42 | 'metaclass', 43 | ], 44 | classifiers = [ 45 | 'Development Status :: 5 - Production/Stable', 46 | 'Intended Audience :: Developers', 47 | 'License :: OSI Approved :: Apache Software License', 48 | 'Operating System :: OS Independent', 49 | 'Programming Language :: Python', 50 | 'Programming Language :: Python :: 3', 51 | 'Programming Language :: Python :: 3.5', 52 | 'Programming Language :: Python :: 3.6', 53 | 'Programming Language :: Python :: 3.7', 54 | 'Programming Language :: Python :: 3.8', 55 | 'Topic :: Software Development :: Libraries :: Python Modules', 56 | ], 57 | ) 58 | 59 | -------------------------------------------------------------------------------- /declarative/properties/utilities.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | """ 4 | from __future__ import ( 5 | division, 6 | print_function, 7 | absolute_import, 8 | ) 9 | import traceback as tb_mod 10 | import sys 11 | 12 | from .bases import ( 13 | PropertyAttributeError, 14 | ) 15 | 16 | from ..utilities.future_from_2 import ( 17 | raise_from_with_traceback, 18 | ) 19 | 20 | def raise_attrerror_from_property(prop, obj, exc): 21 | """ 22 | Because the exception calls some methods for better error reporting, those methods could be inside of unstable objects, so 23 | the call itself is wrapped 24 | """ 25 | _, _, traceback = sys.exc_info() 26 | try: 27 | msg = str(exc) + " (within declarative.property named {0} of {1})".format(prop.__name__, repr(obj)) 28 | except Exception: 29 | msg = str(exc) + " (within declarative.property named {0})".format(prop.__name__) 30 | raise_from_with_traceback( 31 | PropertyAttributeError(msg), 32 | cause = exc, 33 | traceback = traceback, 34 | ) 35 | 36 | 37 | def try_name_file_line(func): 38 | """ 39 | Trashes current exception 40 | """ 41 | d = dict() 42 | try: 43 | d['filename'] = func.__code__.co_filename 44 | except Exception: 45 | d['filename'] = '' 46 | 47 | try: 48 | d['lineno'] = func.__code__.co_firstlineno 49 | except Exception: 50 | d['lineno'] = '' 51 | return d 52 | 53 | 54 | def raise_msg_from_property(msg, t_exc, prop, obj, old_exc = None, if_from_file = None, **kwargs): 55 | """ 56 | """ 57 | _, _, traceback = sys.exc_info() 58 | #print("FILE FROM: ", tb_mod.extract_tb(traceback)[-1][0]) 59 | #raise 60 | if if_from_file is not None and (tb_mod.extract_tb(traceback)[-1][0] != if_from_file): 61 | return False 62 | d = dict( 63 | name = prop.__name__ 64 | ) 65 | try: 66 | d['orepr'] = repr(obj) 67 | except Exception: 68 | d['orepr'] = ''.format(obj.__class__.__name__) 69 | d.update(**kwargs) 70 | 71 | msg = msg.format(**d) 72 | 73 | raise_from_with_traceback( 74 | t_exc(msg), 75 | cause = old_exc, 76 | traceback = traceback, 77 | ) 78 | -------------------------------------------------------------------------------- /declarative/overridable_object.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | """ 4 | import types 5 | 6 | from .properties import HasDeclaritiveAttributes 7 | 8 | from .utilities.representations import SuperBase 9 | 10 | 11 | class OverridableObject(HasDeclaritiveAttributes, SuperBase, object): 12 | """ 13 | """ 14 | _overridable_object_save_kwargs = False 15 | _overridable_object_kwargs = None 16 | 17 | def _overridable_object_inject(self, **kwargs): 18 | """ 19 | """ 20 | kwargs_unmatched = {} 21 | for key, obj in list(kwargs.items()): 22 | try: 23 | parent_desc = getattr(self.__class__, key) 24 | except AttributeError: 25 | kwargs_unmatched[key] = obj 26 | continue 27 | 28 | if isinstance( 29 | parent_desc, ( 30 | types.MethodType, 31 | staticmethod, 32 | classmethod 33 | ) 34 | ): 35 | raise ValueError( 36 | ( 37 | "Can only redefine non-method descriptors, {0} a method of class {1}" 38 | ).format(key, self.__class__.__name__) 39 | ) 40 | try: 41 | use_bd = parent_desc._force_boot_dict 42 | except AttributeError: 43 | use_bd = False 44 | 45 | if not use_bd: 46 | setattr(self, key, obj) 47 | else: 48 | self.__boot_dict__[key] = obj 49 | return kwargs_unmatched 50 | 51 | def __init__(self, **kwargs): 52 | """ 53 | """ 54 | if self._overridable_object_save_kwargs: 55 | self._overridable_object_kwargs = kwargs 56 | kwargs_unmatched = self._overridable_object_inject(**kwargs) 57 | if kwargs_unmatched: 58 | raise ValueError( 59 | ( 60 | "Can only redefine class-specified attributes, class {0} does not have elements {1}" 61 | ).format(self.__class__.__name__, list(kwargs_unmatched.keys())) 62 | ) 63 | 64 | #now run the __mid_init__ before all of the declarative arguments trigger 65 | self.__mid_init__() 66 | super(OverridableObject, self).__init__() 67 | #print("OO: ", self) 68 | return 69 | 70 | def __mid_init__(self): 71 | """ 72 | """ 73 | return 74 | 75 | -------------------------------------------------------------------------------- /declarative/utilities/future3.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | TODO: get licences from python-future 4 | A selection of cross-compatible functions for Python 2 and 3. 5 | 6 | This module exports useful functions for 2/3 compatible code: 7 | 8 | * bind_method: binds functions to classes 9 | * ``native_str_to_bytes`` and ``bytes_to_native_str`` 10 | * ``native_str``: always equal to the native platform string object (because 11 | this may be shadowed by imports from future.builtins) 12 | * lists: lrange(), lmap(), lzip(), lfilter() 13 | * iterable method compatibility: 14 | - iteritems, iterkeys, itervalues 15 | - viewitems, viewkeys, viewvalues 16 | 17 | These use the original method if available, otherwise they use items, 18 | keys, values. 19 | 20 | * types: 21 | 22 | * text_type: unicode in Python 2, str in Python 3 23 | * binary_type: str in Python 2, bythes in Python 3 24 | * string_types: basestring in Python 2, str in Python 3 25 | 26 | * bchr(c): 27 | Take an integer and make a 1-character byte string 28 | * bord(c) 29 | Take the result of indexing on a byte string and make an integer 30 | * tobytes(s) 31 | Take a text string, a byte string, or a sequence of characters taken 32 | from a byte string, and make a byte string. 33 | 34 | * raise_from() 35 | * raise_with_traceback() 36 | 37 | This module also defines these decorators: 38 | 39 | * ``python_2_unicode_compatible`` 40 | * ``with_metaclass`` 41 | * ``implements_iterator`` 42 | 43 | Some of the functions in this module come from the following sources: 44 | 45 | * Jinja2 (BSD licensed: see 46 | https://github.com/mitsuhiko/jinja2/blob/master/LICENSE) 47 | * Pandas compatibility module pandas.compat 48 | * six.py by Benjamin Peterson 49 | * Django 50 | """ 51 | from __future__ import (absolute_import, division, print_function) 52 | import sys 53 | 54 | 55 | def raise_from_with_traceback(exc, cause = None, traceback = None): 56 | """ 57 | Equivalent to: 58 | 59 | raise EXCEPTION from CAUSE 60 | 61 | on Python 3. (See PEP 3134). 62 | 63 | Raise exception with existing traceback. 64 | If traceback is not passed, uses sys.exc_info() to get traceback. 65 | """ 66 | if traceback is None: 67 | _, _, traceback = sys.exc_info() 68 | if cause is not None: 69 | raise exc.with_traceback(traceback) from cause 70 | else: 71 | raise exc.with_traceback(traceback) 72 | 73 | 74 | # listvalues and listitems definitions from Nick Coghlan's (withdrawn) 75 | # PEP 496: 76 | def listvalues(d): 77 | return list(d.values()) 78 | def listitems(d): 79 | return list(d.items()) 80 | -------------------------------------------------------------------------------- /declarative/callbacks/validators.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | """ 4 | import math 5 | 6 | from .relay import ( 7 | RelayValueCoerced, 8 | RelayValueRejected, 9 | ) 10 | 11 | 12 | def min_max_validator_int(min_val, max_val): 13 | def validator(val): 14 | #put limits on motion 15 | try: 16 | val = int(val) 17 | if val < min_val: 18 | val = min_val 19 | elif val > max_val: 20 | val = max_val 21 | if val != val: 22 | raise RelayValueCoerced(val) 23 | return 24 | #catch NaN's hitting the int 25 | except ValueError: 26 | raise RelayValueRejected() 27 | return val 28 | return validator 29 | 30 | 31 | def min_max_validator( 32 | min_val, 33 | max_val, 34 | min_ne = False, 35 | max_ne = False, 36 | allow_nan = False 37 | ): 38 | if not min_ne: 39 | if not max_ne: 40 | def validator(val): 41 | if not math.isfinite(val): 42 | if allow_nan: 43 | return 44 | else: 45 | raise RelayValueRejected() 46 | #put limits on motion 47 | if val < min_val: 48 | raise RelayValueCoerced(min_val) 49 | elif val > max_val: 50 | raise RelayValueCoerced(max_val) 51 | return val 52 | else: 53 | def validator(val): 54 | if not math.isfinite(val): 55 | if allow_nan: 56 | return 57 | else: 58 | raise RelayValueRejected() 59 | #put limits on motion 60 | if val < min_val: 61 | raise RelayValueCoerced(min_val) 62 | elif val >= max_val: 63 | raise RelayValueRejected() 64 | return val 65 | else: 66 | if not max_ne: 67 | def validator(val): 68 | if not math.isfinite(val): 69 | if allow_nan: 70 | return 71 | else: 72 | raise RelayValueRejected() 73 | #put limits on motion 74 | if val <= min_val: 75 | raise RelayValueRejected() 76 | elif val > max_val: 77 | raise RelayValueCoerced(max_val) 78 | return val 79 | else: 80 | def validator(val): 81 | if not math.isfinite(val): 82 | if allow_nan: 83 | return 84 | else: 85 | raise RelayValueRejected() 86 | #put limits on motion 87 | if val <= min_val: 88 | raise RelayValueRejected() 89 | elif val >= max_val: 90 | raise RelayValueRejected() 91 | return val 92 | return validator 93 | 94 | 95 | -------------------------------------------------------------------------------- /declarative/utilities/representations.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | .. autoclass:: ReprMixin 4 | 5 | .. autofunction:: bool_string_interpret 6 | """ 7 | from __future__ import division, print_function, unicode_literals 8 | from ..utilities.future_from_2 import repr_compat 9 | from .super_base import SuperBase 10 | 11 | 12 | class ReprMixin(SuperBase): 13 | """ 14 | A :term:`mixin` class inherited in front of/instead of object which gives 15 | any object a decent __repr__ function. The object can additionally define a list in one of 16 | __slots__ or __repr_slots__ which gives attributes for this repr function to report. 17 | 18 | If __repr_slots__ is given, it will take precedence over any __slots__. For extended inheritance 19 | heirerarchies, the attributes are stacked, with superclass attributes listed before subclass 20 | attributes. 21 | 22 | .. attribute:: __repr_slots__ 23 | 24 | This is a list of the attribute names for the repr to display. One should be placed in 25 | any subclass using this mixin that needs specific members displayed. 26 | 27 | .. automethod:: __repr__ 28 | """ 29 | __slots__ = () 30 | __repr_slots__ = () 31 | 32 | @repr_compat 33 | def __repr__(self): 34 | attr_names = [] 35 | attr_names_set = set() 36 | 37 | #find the list of attributes to use 38 | for parent_class in self.__class__.__mro__: 39 | if hasattr(parent_class, "__repr_slots__"): 40 | attr_names.extend((attrname for attrname in parent_class.__repr_slots__ if attrname not in attr_names_set)) 41 | attr_names_set.update(parent_class.__repr_slots__) 42 | elif hasattr(parent_class, "__slots__"): 43 | attr_names.extend((attrname for attrname in parent_class.__slots__ if attrname not in attr_names_set)) 44 | attr_names_set.update(parent_class.__slots__) 45 | 46 | #extract the attributes, allowing missing ones 47 | items = [] 48 | for attrname in attr_names: 49 | attr_value = self 50 | try: 51 | for subattr in attrname.split('.'): 52 | attr_value = getattr(attr_value, subattr) 53 | except AttributeError: 54 | attr_value = ':unset:' 55 | items.append((attrname, repr(attr_value))) 56 | 57 | #paste the attributes together 58 | attr_listing = ', '.join(("%s=%s" % attr_descr for attr_descr in items)) 59 | return "%s(%s)" % (self.__class__.__name__, attr_listing) 60 | 61 | 62 | def bool_string_interpret(val_str, default_value = None): 63 | """ 64 | Interprets 65 | """ 66 | if val_str.upper() in ('T', 'TRUE', '1', 'Y', 'YES', 'AFFIRMATIVE'): 67 | return True 68 | elif val_str.upper() in ('F', 'FALSE', '0', 'N', 'NO', 'NEGATORY'): 69 | return False 70 | elif default_value is None: 71 | raise ValueError('Bool Value unrecognized {0}'.format(val_str)) 72 | return default_value 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /setup_helper.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import division, print_function, unicode_literals 3 | import os 4 | import sys 5 | from os import path 6 | from distutils.command.bdist import bdist 7 | from distutils.command.sdist import sdist 8 | 9 | def version_checker(version_string, module_name): 10 | 11 | def check_versions(): 12 | import importlib 13 | print('versions checked') 14 | try: 15 | curpath = path.abspath(path.realpath(os.getcwd())) 16 | setuppath = path.abspath(path.realpath(path.split(__file__)[0])) 17 | 18 | if curpath != setuppath: 19 | sys.path.append(setuppath) 20 | 21 | module = importlib.import_module(module_name) 22 | 23 | modfile = path.abspath(path.realpath(path.split(module.__file__)[0])) 24 | mod_relpath = path.relpath(modfile, setuppath) 25 | if mod_relpath == module_name: 26 | if module.__version__ != version_string: 27 | print("WARNING: Stated module version different than setup.py version", file = sys.stderr) 28 | print(" '{0}' != '{1}'".format(module.__version__, version_string), file = sys.stderr) 29 | print("Fix version.py and setup.py for consistency") 30 | 31 | import subprocess 32 | try: 33 | git_tag = subprocess.check_output(['git', 'describe', '--tags']) 34 | git_tag = git_tag.strip() 35 | git_tag = str(git_tag.decode('utf-8')) 36 | except subprocess.CalledProcessError: 37 | pass 38 | else: 39 | if git_tag != version_string: 40 | if git_tag.startswith(version_string): 41 | print("WARNING: latex git-tag has commits since versioning", file = sys.stderr) 42 | print(" '{0}' ---> '{1}'".format(version_string, git_tag), file=sys.stderr) 43 | else: 44 | print("WARNING: latex git-tag different than setup.py version", file = sys.stderr) 45 | print(" '{0}' != '{1}'".format(version_string, git_tag), file=sys.stderr) 46 | print(" Perhaps update versions in setup.py and module.version, git commit, then git tag") 47 | print(" otherwise fix tag if not yet git pushed to remote (see DISTRIBUTION-README.md)") 48 | 49 | except ImportError: 50 | pass 51 | 52 | cmdclass = dict() 53 | 54 | class check_bdist(bdist): 55 | def run(self): 56 | bdist.run(self) 57 | check_versions() 58 | 59 | class check_sdist(sdist): 60 | def run(self): 61 | sdist.run(self) 62 | check_versions() 63 | 64 | 65 | cmdclass['bdist'] = check_bdist 66 | cmdclass['sdist'] = check_sdist 67 | 68 | try: 69 | from wheel.bdist_wheel import bdist_wheel 70 | class check_bdist_wheel(bdist_wheel): 71 | def run(self): 72 | bdist_wheel.run(self) 73 | check_versions() 74 | except ImportError: 75 | pass 76 | else: 77 | cmdclass['bdist_wheel'] = check_bdist_wheel 78 | 79 | return cmdclass 80 | 81 | -------------------------------------------------------------------------------- /declarative/utilities/future_from_2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Pulls in the future library requirement if running on python2 4 | """ 5 | from __future__ import (absolute_import, division, print_function) 6 | import sys 7 | import warnings 8 | 9 | warnings.warn("Compatibility support for python2 has been dropped", DeprecationWarning, stacklevel=2) 10 | 11 | if sys.version_info < (3, 0, 0): 12 | #requires the future library 13 | from builtins import ( 14 | super, 15 | object, 16 | ascii, 17 | bytes, 18 | chr, 19 | dict, 20 | filter, 21 | hex, 22 | input, 23 | int, 24 | map, 25 | next, 26 | oct, 27 | open, 28 | pow, 29 | range, 30 | round, 31 | str, 32 | zip, 33 | unicode, 34 | ) 35 | 36 | from future.standard_library import install_aliases 37 | install_aliases() 38 | 39 | #decode any unicode repr functions as py27 cannot handle them 40 | def repr_compat(func): 41 | def __repr__(self, *args, **kwargs): 42 | return repr(func(self, *args, **kwargs).encode('utf-8').decode('utf-8')) 43 | #wrap it 44 | __repr__.__name__ = func.__name__ 45 | __repr__.__doc__ = func.__doc__ 46 | return __repr__ 47 | else: 48 | super = super 49 | object = object 50 | ascii = ascii 51 | bytes = bytes 52 | chr = chr 53 | dict = dict 54 | filter = filter 55 | hex = hex 56 | input = input 57 | int = int 58 | map = map 59 | next = next 60 | oct = oct 61 | open = open 62 | pow = pow 63 | range = range 64 | round = round 65 | str = str 66 | zip = zip 67 | unicode = str 68 | 69 | def repr_compat(func): 70 | return func 71 | 72 | PY3 = (sys.version_info[0] == 3) 73 | 74 | if PY3: 75 | from .future3 import ( 76 | raise_from_with_traceback, 77 | ) 78 | else: 79 | raise NotImplementedError("Support for python2 compatibility has been dropped") 80 | 81 | def with_metaclass(meta, *bases): 82 | """ 83 | Function from jinja2/_compat.py (as included in python-future). License: BSD. 84 | * Jinja2 (BSD licensed: see https://github.com/mitsuhiko/jinja2/blob/master/LICENSE) 85 | 86 | Use it like this:: 87 | 88 | class BaseForm(object): 89 | pass 90 | 91 | class FormType(type): 92 | pass 93 | 94 | class Form(with_metaclass(FormType, BaseForm)): 95 | pass 96 | 97 | This requires a bit of explanation: the basic idea is to make a 98 | dummy metaclass for one level of class instantiation that replaces 99 | itself with the actual metaclass. Because of internal type checks 100 | we also need to make sure that we downgrade the custom metaclass 101 | for one level to something closer to type (that's why __call__ and 102 | __init__ comes back from type etc.). 103 | 104 | This has the advantage over six.with_metaclass of not introducing 105 | dummy classes into the final MRO. 106 | """ 107 | class metaclass(meta): 108 | __call__ = type.__call__ 109 | __init__ = type.__init__ 110 | 111 | def __new__(cls, name, this_bases, d): 112 | if this_bases is None: 113 | return type.__new__(cls, name, (), d) 114 | return meta(name, bases, d) 115 | return metaclass('temporary_class', None, {}) 116 | 117 | 118 | object 119 | 120 | if PY3: 121 | object = object 122 | -------------------------------------------------------------------------------- /declarative/properties/bases.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | """ 4 | from __future__ import ( 5 | division, 6 | print_function, 7 | absolute_import, 8 | ) 9 | #from builtins import object 10 | 11 | import abc 12 | 13 | from ..utilities import SuperBase 14 | from ..utilities.future_from_2 import with_metaclass 15 | 16 | 17 | class PropertyTransforming(with_metaclass(abc.ABCMeta, object)): 18 | @abc.abstractmethod 19 | def construct( 20 | self, 21 | parent, 22 | name, 23 | ): 24 | return 25 | 26 | 27 | #TODO rename PropertyException 28 | class InnerException(Exception): 29 | """ 30 | Exception holder class that allows one to see inside of the functional properties when they throw and error, 31 | even if outer logic is catching exceptions. 32 | 33 | These should never be caught as they always indicate bugs 34 | """ 35 | pass 36 | 37 | 38 | class PropertyAttributeError(Exception): 39 | """ 40 | Exception indicating an AttributeError inside of a declarative property. AttributeError is masked so 41 | that it is not confused with the property name itself being a missing attribute. 42 | """ 43 | pass 44 | 45 | 46 | class HasDeclaritiveAttributes(SuperBase): 47 | """ 48 | This base class allows class instances to automatically constructs instance objects declared using 49 | the :obj:`~.declarative_instantiation`. Declarations using that decorator and this subclass have an advantage 50 | in that instantiation order doesn't matter, because instantiation happens as needed via the memoization 51 | quality of declarative_instantiation attributes, and all attributes are instantiated via the qualities of 52 | this class. 53 | 54 | .. note:: Subclasses must use cooperative calls to `super().__init__` 55 | """ 56 | __boot_dict__ = None 57 | __cls_decl_attrs__ = (object,) 58 | def __init__(self, **kwargs): 59 | #TODO memoize this list 60 | super(HasDeclaritiveAttributes, self).__init__(**kwargs) 61 | attr_list = iter(self.__cls_decl_attrs__) 62 | #check that the first item is this class, if not then it must have pulled a parent's list 63 | if next(attr_list) == self.__class__: 64 | #print('attrs2', list(self.__cls_decl_attrs__)) 65 | for attr in attr_list: 66 | getattr(self, attr) 67 | else: 68 | cls = self.__class__ 69 | attr_list = [cls] 70 | attrs = dir(cls) 71 | #print('attrs', list(attrs)) 72 | for attr in attrs: 73 | acls = getattr(self.__class__, attr) 74 | #the attribute must be a descriptor whose class must have _declarative_instantiation 75 | #defined, for it to be accessed during init 76 | decl = getattr(acls.__class__, '_declarative_instantiation', None) 77 | if decl: 78 | getattr(self, attr) 79 | attr_list.append(attr) 80 | elif decl is not None and getattr(acls, '_declarative_instantiation', False): 81 | #this checks if the INSTANCE of the descriptor has had _declarative_instantiation 82 | #but only checks if the class has that item, so that the 83 | #getattr can't fail on weird objects 84 | getattr(self, attr) 85 | attr_list.append(attr) 86 | #set to indicate the memoized list of attributes 87 | cls.__cls_decl_attrs__ = tuple(attr_list) 88 | #print('attr_list', list(self.__cls_decl_attrs__)) 89 | #print("DECL: ", self) 90 | return 91 | -------------------------------------------------------------------------------- /test/argparse/test_args.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Test of the argparse library 5 | 6 | TODO: use automated features 7 | """ 8 | from __future__ import (division, print_function, absolute_import) 9 | 10 | from declarative import ( 11 | OverridableObject, 12 | mproperty, 13 | NOARG, 14 | ) 15 | 16 | import declarative.argparse as ARG 17 | 18 | import pytest 19 | 20 | 21 | oldprint = print 22 | print_test_list = [] 23 | def print(*args): 24 | oldprint(*args) 25 | if len(args) == 1: 26 | print_test_list.append(args[0]) 27 | else: 28 | print_test_list.append(args) 29 | 30 | 31 | class TArgParse(ARG.OOArgParse, OverridableObject): 32 | """ 33 | Runs the argparse test setup 34 | """ 35 | 36 | @ARG.argument(['-a', '--abc']) 37 | @mproperty 38 | def atest(self, val = NOARG): 39 | """ 40 | Test the documentation. 41 | """ 42 | if val is NOARG: 43 | val = 'default' 44 | return val 45 | 46 | @ARG.argument(['-b', '--btest'], group = '__tgroup__') 47 | @mproperty 48 | def btest(self, val = NOARG): 49 | """ 50 | Test the documentation. 51 | """ 52 | if val is NOARG: 53 | val = 'default' 54 | return val 55 | 56 | @ARG.argument(['-c', '--ctest'], group = '__tgroup_ME__') 57 | @mproperty 58 | def ctest(self, val = NOARG): 59 | """ 60 | Test the documentation. 61 | """ 62 | if val is NOARG: 63 | val = 'default' 64 | return val 65 | 66 | @ARG.argument(['-d', '--dtest'], group = '__tgroup_ME__') 67 | @mproperty 68 | def dtest(self, val = NOARG): 69 | """ 70 | Test the documentation. 71 | """ 72 | if val is NOARG: 73 | val = 'default' 74 | return val 75 | 76 | @ARG.store_true(['-e', '--etest']) 77 | @mproperty 78 | def etest(self, val = NOARG): 79 | """ 80 | Test the documentation. 81 | """ 82 | if val is NOARG: 83 | val = 'default' 84 | return val 85 | 86 | @ARG.group(name = 'Test Group') 87 | @mproperty 88 | def __tgroup__(self, val = NOARG): 89 | """ 90 | Group for testing 91 | """ 92 | 93 | @ARG.group( 94 | name = 'Test Group2', 95 | mutually_exclusive = True, 96 | ) 97 | @mproperty 98 | def __tgroup_ME__(self, val = NOARG): 99 | """ 100 | Group for testing Mutual Exclusivity 101 | """ 102 | 103 | @ARG.command(takes_arguments = True) 104 | def run2(self, args): 105 | """ 106 | Command Description 107 | """ 108 | print("run2!", args) 109 | return self 110 | 111 | def __arg_default__(self): 112 | """ 113 | Print various arguments 114 | """ 115 | print ("atest: ", self.atest) 116 | print ("btest: ", self.btest) 117 | print ("ctest: ", self.ctest) 118 | print ("dtest: ", self.dtest) 119 | return self 120 | 121 | 122 | def test_args(): 123 | print_test_list[:] = [] 124 | TArgParse.__cls_argparse__(['-a', '1234'], _sys_exit = False) 125 | assert( 126 | print_test_list == [ 127 | ("atest: ", '1234'), 128 | ("btest: ", 'default'), 129 | ("ctest: ", 'default'), 130 | ("dtest: ", 'default'), 131 | ] 132 | ) 133 | 134 | print_test_list[:] = [] 135 | TArgParse.__cls_argparse__(['-a', '1234', 'run2'], _sys_exit = False) 136 | assert( 137 | print_test_list == [ 138 | ("run2!", []), 139 | ] 140 | ) 141 | 142 | print_test_list[:] = [] 143 | TArgParse.__cls_argparse__(['-a', '1234', 'run2', 'a', '-b', 'c'], _sys_exit = False) 144 | assert( 145 | print_test_list == [ 146 | ("run2!", ['a', '-b', 'c']), 147 | ] 148 | ) 149 | 150 | print_test_list[:] = [] 151 | with pytest.raises(SystemExit): 152 | TArgParse.__cls_argparse__(['run_broke'], _sys_exit = False) 153 | 154 | return 155 | 156 | if __name__ == '__main__': 157 | TArgParse.__cls_argparse__() 158 | 159 | -------------------------------------------------------------------------------- /declarative/metaclass.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | """ 5 | from __future__ import division, print_function 6 | #from builtins import object 7 | import warnings 8 | from .utilities.future_from_2 import with_metaclass, str, unicode 9 | 10 | 11 | class AutodecorateMeta(type): 12 | def __init__(cls, name, bases, dct): 13 | super(AutodecorateMeta, cls).__init__(name, bases, dct) 14 | 15 | ad_calls = [] 16 | for base in cls.__mro__: 17 | ad_call = base.__dict__.get('__mc_autodecorators__', None) 18 | if ad_call is not None: 19 | ad_calls.append(ad_call) 20 | 21 | decorators = [] 22 | for ad_call in ad_calls: 23 | decorators = ad_call.__get__(None, cls)(decorators) 24 | 25 | for deco in decorators: 26 | cls2 = deco(cls) 27 | if cls2 is not cls: 28 | raise RuntimeError("Autodecorators cannot actually replace object, modification only") 29 | return 30 | 31 | 32 | class Autodecorate(with_metaclass(AutodecorateMeta)): 33 | 34 | ##this __init__ stub is needed or the init of 'object' is used! 35 | #def __init__(self, *args, **kwargs): 36 | # super(Autodecorate, self).__init__(*args, **kwargs) 37 | 38 | @classmethod 39 | def __mc_autodecorators__(cls, decorators): 40 | return decorators 41 | 42 | 43 | def class_expand(cls): 44 | cls._attributes_expand_units() 45 | return cls 46 | 47 | 48 | class AttrExpandingObject(Autodecorate): 49 | _cls_getsetattr_expansion = dict() 50 | _cls_getsetattr_expansion_called = None 51 | 52 | @classmethod 53 | def __mc_autodecorators__(cls, decorators): 54 | decorators.append(class_expand) 55 | return decorators 56 | 57 | @classmethod 58 | def _attributes_expand_units(cls): 59 | expansion = dict() 60 | for k in dir(cls): 61 | v = getattr(cls, k) 62 | try: 63 | expand = v.__generate_virtual_descriptors__ 64 | except AttributeError: 65 | continue 66 | expansion.update(expand()) 67 | cls._cls_getsetattr_expansion = expansion 68 | cls._cls_getsetattr_expansion_called = cls 69 | return cls 70 | 71 | if __debug__: 72 | def __init__(self, *args, **kwargs): 73 | if self._cls_getsetattr_expansion_called is not self.__class__: 74 | self._attributes_expand_units() 75 | warnings.warn("Need to add class decorator \"class_expand\"") 76 | super(AttrExpandingObject, self).__init__(*args, **kwargs) 77 | 78 | def _overridable_object_inject(self, **kwargs): 79 | kwargs = super(AttrExpandingObject, self)._overridable_object_inject(**kwargs) 80 | kwargs_remaining = dict() 81 | while kwargs: 82 | k, v = kwargs.popitem() 83 | desc = self._cls_getsetattr_expansion.get(k, None) 84 | if desc is None: 85 | kwargs_remaining[k] = v 86 | else: 87 | desc.__set__(self, v) 88 | return kwargs_remaining 89 | 90 | def __getattr__(self, name): 91 | expand = self._cls_getsetattr_expansion.get(name, None) 92 | if expand is not None: 93 | genval = expand.__get__(self, self.__class__) 94 | self.__dict__[name] = genval 95 | return genval 96 | else: 97 | return super(AttrExpandingObject, self).__getattr__(name) 98 | 99 | def __dir__(self): 100 | predir = dir(super(AttrExpandingObject, self)) 101 | predir.extend(k for k in self._cls_getsetattr_expansion.keys() if isinstance(k, (str, unicode))) 102 | predir.sort() 103 | return predir 104 | 105 | 106 | #separated because __setattr__ is so much more advanced and awful than __getattr__ overloading 107 | class GetSetAttrExpandingObject(AttrExpandingObject): 108 | def __setattr__(self, name, val): 109 | assert(isinstance(name, (str, unicode))) 110 | expand = self._cls_getsetattr_expansion.get(name, None) 111 | if expand is None: 112 | return super(GetSetAttrExpandingObject, self).__setattr__(name, val) 113 | else: 114 | return expand.__set__(self, self.__class__, val) 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /declarative/bunch/tag_bunch.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | """ 4 | try: 5 | from collections.abc import Mapping as MappingABC 6 | except ImportError: 7 | from collections import Mapping as MappingABC 8 | from ..utilities.future_from_2 import str, object, repr_compat, unicode 9 | from ..utilities.unique import NOARG 10 | 11 | from .deep_bunch import DeepBunch 12 | 13 | 14 | class TagBunch(object): 15 | """ 16 | """ 17 | __slots__ = ('_dict', '_tag_dicts',) 18 | 19 | def __init__( 20 | self, 21 | write_dict = None, 22 | gfunc = None, 23 | **kwargs 24 | ): 25 | if write_dict is None: 26 | write_dict = DeepBunch() 27 | self._dict = write_dict 28 | self._tag_dicts = kwargs 29 | if gfunc is True: 30 | self.require_tag('gfunc') 31 | elif gfunc is not None: 32 | self.set_tag('gfunc', gfunc) 33 | return 34 | 35 | @property 36 | def _gfunc(self): 37 | return self.get_tag('gfunc') 38 | 39 | def __getitem__(self, key): 40 | gfunc = self._tag_dicts.get('gfunc', None) 41 | if gfunc is not None and (key not in self._dict): 42 | gval = gfunc.get(key, None) 43 | if gval is not None: 44 | item = gval() 45 | self._dict[key] = item 46 | 47 | item = self._dict[key] 48 | if isinstance(item, MappingABC): 49 | subtags = {} 50 | for tagkey, tagdict in list(self._tag_dicts.items()): 51 | if isinstance(tagdict, MappingABC): 52 | try: 53 | subtags[tagkey] = tagdict[key] 54 | except KeyError: 55 | continue 56 | item = self.__class__(item, **subtags) 57 | return item 58 | 59 | def __getattr__(self, key): 60 | try: 61 | return self.__getitem__(key) 62 | except KeyError: 63 | raise AttributeError("'{0}' not in {1}".format(key, self)) 64 | 65 | def __setitem__(self, key, item): 66 | self._dict[key] = item 67 | 68 | def __setattr__(self, key, item): 69 | if key in self.__slots__: 70 | return super(TagBunch, self).__setattr__(key, item) 71 | return self.__setitem__(key, item) 72 | 73 | def __delitem__(self, key): 74 | del self._dict[key] 75 | 76 | def __delattr__(self, key): 77 | return self.__delitem__(key) 78 | 79 | def get(self, key, default = NOARG): 80 | try: 81 | return self[key] 82 | except KeyError: 83 | if default is not NOARG: 84 | return default 85 | raise 86 | 87 | def setdefault(self, key, default): 88 | try: 89 | return self[key] 90 | except KeyError: 91 | self[key] = default 92 | return default 93 | 94 | def get_tag(self, tagkey, default = NOARG): 95 | try: 96 | return self._tag_dicts[tagkey] 97 | except KeyError: 98 | if default is not NOARG: 99 | return default 100 | raise 101 | 102 | def has_tag(self, key): 103 | return key in self._tag_dicts 104 | 105 | def require_tag(self, tagkey): 106 | if tagkey not in self._tag_dicts: 107 | self._tag_dicts[tagkey] = DeepBunch({}) 108 | return 109 | 110 | def set_tag(self, tagkey, obj): 111 | self._tag_dicts[tagkey] = obj 112 | return 113 | 114 | def __contains__(self, key): 115 | return (key in self._dict) 116 | 117 | def has_key(self, key): 118 | return key in self 119 | 120 | def __dir__(self): 121 | items = list(k for k in self._dict.keys() if isinstance(k, (str, unicode))) 122 | items.sort() 123 | #items += dir(super(Bunch, self)) 124 | return items 125 | 126 | @repr_compat 127 | def __repr__(self): 128 | return ( 129 | '{0}({1}, {2})' 130 | ).format( 131 | self.__class__.__name__, 132 | self._dict, 133 | self._tag_dicts, 134 | ) 135 | 136 | #def __eq__(self, other): 137 | # return 138 | # 139 | #def __ne__(self, other): 140 | # return not (self == other) 141 | 142 | def __iter__(self): 143 | return iter(list(self.keys())) 144 | 145 | def __len__(self): 146 | return len(self._dict) 147 | 148 | def iterkeys(self): 149 | return iter(list(self._dict.keys())) 150 | 151 | def keys(self): 152 | return list(self._dict.keys()) 153 | 154 | def itervalues(self): 155 | for key in list(self.keys()): 156 | yield self[key] 157 | return 158 | 159 | def values(self): 160 | return list(self.values()) 161 | 162 | def iteritems(self): 163 | for key in list(self.keys()): 164 | yield key, self[key] 165 | return 166 | 167 | def items(self): 168 | return list(self.items()) 169 | 170 | 171 | MappingABC.register(TagBunch) 172 | -------------------------------------------------------------------------------- /declarative/callbacks/relay.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | """ 4 | from ..utilities.representations import ReprMixin 5 | from ..properties import mproperty 6 | import numpy as np 7 | 8 | _UNIQUE = ("UNIQUE",) 9 | 10 | 11 | class RelayValueRejected(Exception): 12 | pass 13 | 14 | 15 | class RelayValueCoerced(RelayValueRejected): 16 | def __init__(self, preferred): 17 | self.preferred = preferred 18 | 19 | 20 | class RelayValue(ReprMixin): 21 | """ 22 | """ 23 | __slots__ = ('callbacks', '_value') 24 | __repr_slots__ = ('_value',) 25 | 26 | @staticmethod 27 | def validator(val): 28 | return val 29 | 30 | def __init__( 31 | self, 32 | initial_value, 33 | validator = None, 34 | **kwargs 35 | ): 36 | """ 37 | """ 38 | super(RelayValue, self).__init__(**kwargs) 39 | self.callbacks = {} 40 | if validator is not None: 41 | self.validator = validator 42 | initial_value = self.validator(initial_value) 43 | self._value = initial_value 44 | return 45 | 46 | def register( 47 | self, 48 | key = None, 49 | callback = None, 50 | assumed_value = _UNIQUE, 51 | call_immediate = False, 52 | remove = False, 53 | ): 54 | """ 55 | """ 56 | if key is None: 57 | key = callback 58 | if key is None: 59 | raise RuntimeError("Key or Callback must be specified") 60 | if not remove: 61 | self.callbacks[key] = callback 62 | if assumed_value is not _UNIQUE: 63 | if self._value != assumed_value: 64 | callback(self._value) 65 | elif call_immediate: 66 | callback(self._value) 67 | else: 68 | if assumed_value is not _UNIQUE: 69 | if self._value != assumed_value: 70 | callback(assumed_value) 71 | del self.callbacks[key] 72 | return 73 | 74 | def validator_assign(self, validator, remove = False): 75 | if not remove: 76 | if self.validator is not self.__class__.validator: 77 | raise RuntimeError("Can not set multiple validators") 78 | self.validator = validator 79 | else: 80 | del self.validator 81 | return 82 | 83 | def put(self, val): 84 | if np.any(val != self._value): 85 | val = self.validator(val) 86 | self._value = val 87 | for cb in list(self.callbacks.values()): 88 | cb(val) 89 | return 90 | 91 | def put_exclude_cb(self, val, key): 92 | if val != self._value: 93 | val = self.validator(val) 94 | self._value = val 95 | for cb_key, cb in list(self.callbacks.items()): 96 | if cb_key is not key: 97 | cb(val) 98 | return 99 | 100 | def put_coerce(self, val): 101 | if np.any(val != self._value): 102 | try: 103 | val = self.validator(val) 104 | retval = True 105 | except RelayValueCoerced as E: 106 | val = E.preferred 107 | retval = False 108 | self._value = val 109 | for cb in list(self.callbacks.values()): 110 | cb(val) 111 | return retval 112 | return True 113 | 114 | def put_coerce_exclude_cb(self, val, key): 115 | if val != self._value: 116 | try: 117 | val = self.validator(val) 118 | retval = True 119 | except RelayValueCoerced as E: 120 | val = E.preferred 121 | retval = False 122 | self._value = val 123 | for cb_key, cb in list(self.callbacks.items()): 124 | if cb_key is not key: 125 | cb(val) 126 | return retval 127 | return True 128 | 129 | def put_valid(self, val): 130 | if val != self._value: 131 | self._value = val 132 | for cb in list(self.callbacks.values()): 133 | cb(val) 134 | return 135 | 136 | def put_valid_exclude_cb(self, val, key): 137 | if val != self._value: 138 | self._value = val 139 | for cb_key, cb in list(self.callbacks.items()): 140 | if cb_key is not key: 141 | cb(val) 142 | return 143 | 144 | @property 145 | def value(self): 146 | return self._value 147 | 148 | @value.setter 149 | def value(self, val): 150 | if np.any(val != self._value): 151 | val = self.validator(val) 152 | self._value = val 153 | for cb in list(self.callbacks.values()): 154 | cb(val) 155 | return 156 | 157 | @mproperty 158 | def shadow(self): 159 | return self.__class__(self.value, validator = self.validator) 160 | 161 | def shadow_from(self): 162 | self.put_valid(self.shadow._value) 163 | return 164 | 165 | def shadow_to(self): 166 | self.shadow.put_valid(self._value) 167 | return 168 | 169 | 170 | -------------------------------------------------------------------------------- /declarative/argparse/annotations.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | """ 4 | from __future__ import ( 5 | division, 6 | print_function, 7 | absolute_import, 8 | ) 9 | 10 | from .. import ( 11 | Bunch, 12 | NOARG 13 | ) 14 | 15 | from .oo_argparse import types 16 | 17 | 18 | def store_true(*args, **kwargs): 19 | return argument( 20 | *args, 21 | action = 'store_true', 22 | metavar = NOARG, 23 | **kwargs 24 | ) 25 | 26 | def store_false(*args, **kwargs): 27 | return argument( 28 | *args, 29 | action = 'store_false', 30 | metavar = NOARG, 31 | **kwargs 32 | ) 33 | 34 | def argument( 35 | calls = None, 36 | func = None, 37 | name = None, 38 | metavar = None, 39 | default = NOARG, 40 | description = None, 41 | order = None, 42 | group = None, 43 | **kwargs 44 | ): 45 | def annotate_argument(func): 46 | if name is None: 47 | _name = func.__name__ 48 | else: 49 | _name = name 50 | if metavar is None: 51 | _metavar = func.__name__, 52 | else: 53 | _metavar = metavar 54 | if description is None: 55 | _description = func.__doc__ 56 | else: 57 | _description = description 58 | if not calls: 59 | _calls = ['--' + _name] 60 | else: 61 | _calls = calls 62 | if callable(default): 63 | _default = default() 64 | else: 65 | _default = default 66 | if _default is not NOARG: 67 | _description = _description.replace('{default}', str(_default)) 68 | def gen_argument(parser): 69 | kw = dict(kwargs) 70 | kw.update( 71 | #name = _name, 72 | default = _default, 73 | help = _description, 74 | dest = func.__name__, 75 | ) 76 | if _metavar is not NOARG: 77 | kw['metavar'] = _metavar 78 | g = parser.add_argument(*_calls, **kw) 79 | return g 80 | func._argparse = Bunch( 81 | type = types.argument, 82 | name = _name, 83 | name_inject = func.__name__, 84 | func = gen_argument, 85 | group = group, 86 | order = order, 87 | ) 88 | return func 89 | if func is None: 90 | return annotate_argument 91 | else: 92 | return annotate_argument(func) 93 | 94 | #def arg_bool(func, ): 95 | #return 96 | 97 | def group( 98 | func = None, 99 | name = None, 100 | description = None, 101 | #group = None, 102 | mutually_exclusive = False, 103 | order = None, 104 | **kwargs 105 | ): 106 | group = None 107 | def annotate_group(func): 108 | if not descriptor_check(func): 109 | raise RuntimeError("Must be a memoized descriptor") 110 | if name is None: 111 | _name = func.__name__ 112 | else: 113 | _name = name 114 | if mutually_exclusive: 115 | _name = _name + " [Mutually Exclusive]" 116 | if description is None: 117 | _description = func.__doc__ 118 | else: 119 | _description = description 120 | def gen_group(parser): 121 | g = parser.add_argument_group( 122 | _name, 123 | description = _description, 124 | **kwargs 125 | ) 126 | if mutually_exclusive: 127 | g = g.add_mutually_exclusive_group() 128 | return g 129 | func._argparse = Bunch( 130 | type = types.group, 131 | name = _name, 132 | func = gen_group, 133 | group = group, 134 | order = order, 135 | ) 136 | return func 137 | if func is None: 138 | return annotate_group 139 | else: 140 | return annotate_group(func) 141 | 142 | def command( 143 | func = None, 144 | name = None, 145 | description = None, 146 | order = None, 147 | takes_arguments = False, 148 | **kwargs 149 | ): 150 | def annotate_command(func): 151 | if name is None: 152 | _name = func.__name__ 153 | else: 154 | _name = name 155 | if description is None: 156 | _description = func.__doc__ 157 | else: 158 | _description = description 159 | func._argparse = Bunch( 160 | type = types.command, 161 | cmd_name = _name, 162 | description = _description, 163 | run_name = func.__name__, 164 | order = order, 165 | takes_arguments = takes_arguments, 166 | ) 167 | return func 168 | if func is None: 169 | return annotate_command 170 | else: 171 | return annotate_command(func) 172 | 173 | 174 | def descriptor_check(obj): 175 | #TODO rubustify 176 | if not isinstance(obj, object): 177 | raise RuntimeError("Argparse decorator must be outermost decorator") 178 | return False 179 | return True 180 | 181 | 182 | 183 | 184 | 185 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # ligodtt documentation build configuration file, created by 5 | # sphinx-quickstart on Sat Jul 15 21:40:50 2017. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # 20 | # import os 21 | # import sys 22 | # sys.path.insert(0, os.path.abspath('.')) 23 | 24 | 25 | # -- General configuration ------------------------------------------------ 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | # 29 | # needs_sphinx = '1.0' 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be 32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 33 | # ones. 34 | extensions = [ 35 | 'sphinx.ext.autodoc', 36 | #'sphinx.ext.todo', 37 | #'sphinx.ext.coverage', 38 | 'sphinx.ext.mathjax', 39 | #'sphinx.ext.githubpages', 40 | 'sphinx.ext.napoleon', 41 | ] 42 | 43 | # Add any paths that contain templates here, relative to this directory. 44 | templates_path = ['_templates'] 45 | 46 | # The suffix(es) of source filenames. 47 | # You can specify multiple suffix as a list of string: 48 | # 49 | # source_suffix = ['.rst', '.md'] 50 | source_suffix = '.rst' 51 | 52 | # The master toctree document. 53 | master_doc = 'index' 54 | 55 | # General information about the project. 56 | project = 'ligodtt' 57 | copyright = '2021, Lee McCuller' 58 | author = 'McCuller' 59 | 60 | # The version info for the project you're documenting, acts as replacement for 61 | # |version| and |release|, also used in various other places throughout the 62 | # built documents. 63 | # 64 | # The short X.Y version. 65 | version = '1.1.0' 66 | 67 | # The language for content autogenerated by Sphinx. Refer to documentation 68 | # for a list of supported languages. 69 | # 70 | # This is also used if you do content translation via gettext catalogs. 71 | # Usually you set "language" from the command line for these cases. 72 | language = None 73 | 74 | # List of patterns, relative to source directory, that match files and 75 | # directories to ignore when looking for source files. 76 | # This patterns also effect to html_static_path and html_extra_path 77 | exclude_patterns = ['_build', ] 78 | 79 | # The name of the Pygments (syntax highlighting) style to use. 80 | #pygments_style = 'sphinx' 81 | #pygments_style = 'colorful' 82 | pygments_style = 'default' 83 | 84 | # If true, `todo` and `todoList` produce output, else they produce nothing. 85 | todo_include_todos = True 86 | 87 | 88 | # -- Options for sourcelinks 89 | 90 | srclink_project = 'https://github.com/mccullerlp/ligodtt' 91 | srclink_src_path = 'ligodtt' 92 | srclink_branch = 'master' 93 | 94 | # -- Options for HTML output ---------------------------------------------- 95 | 96 | #useful for downloading the ipynb files 97 | html_sourcelink_suffix = '' 98 | 99 | # The theme to use for HTML and HTML Help pages. See the documentation for 100 | # a list of builtin themes. 101 | # 102 | #import sphinx_rtd_theme 103 | #html_theme = "sphinx_rtd_theme" 104 | #html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 105 | 106 | #import jupyter_sphinx_theme 107 | #html_theme = "jupyter_sphinx_theme" 108 | #html_theme_path = [jupyter_sphinx_theme.get_html_theme_path()] 109 | 110 | html_theme = "alabaster" 111 | 112 | # Theme options are theme-specific and customize the look and feel of a theme 113 | # further. For a list of options available for each theme, see the 114 | # documentation. 115 | # 116 | # html_theme_options = {} 117 | 118 | # Add any paths that contain custom static files (such as style sheets) here, 119 | # relative to this directory. They are copied after the builtin static files, 120 | # so a file named "default.css" will overwrite the builtin "default.css". 121 | html_static_path = ['_static'] 122 | 123 | # Custom sidebar templates, must be a dictionary that maps document names 124 | # to template names. 125 | # 126 | # This is required for the alabaster theme 127 | # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars 128 | html_sidebars = { 129 | '**': [ 130 | 'about.html', 131 | 'navigation.html', 132 | 'relations.html', 133 | 'searchbox.html', 134 | ] 135 | } 136 | 137 | #html_sidebars = { 138 | # '**': [ 139 | # 'localtoc.html', 140 | # 'navigation.html', 141 | # 'relations.html', 142 | # 'searchbox.html', 143 | # 'srclinks.html', 144 | # ], 145 | # 'index': [ 146 | # 'globaltoc.html', 147 | # 'navigation.html', 148 | # 'relations.html', 149 | # 'searchbox.html', 150 | # 'srclinks.html', 151 | # ], 152 | #} 153 | 154 | # -- Options for HTMLHelp output ------------------------------------------ 155 | 156 | # Output file base name for HTML help builder. 157 | htmlhelp_basename = 'ligodtt' 158 | 159 | #html_logo = 'logo/' 160 | 161 | def setup(app): 162 | app.add_stylesheet('my_theme.css') 163 | app.add_stylesheet('pygments_adjust.css') 164 | 165 | 166 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = ../build/sphinx 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | 15 | IPYNB := $(shell find . -name '*.ipynb' -not -path '*/.ipynb_checkpoints/*' | sed 's/.* .*//g') 16 | IPYRST := $(patsubst %.ipynb,%.rst, $(IPYNB)) 17 | 18 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest 19 | 20 | help: 21 | @echo "Please use \`make ' where is one of" 22 | @echo " html to make standalone HTML files" 23 | @echo " livehtml to make standalone HTML files while watching the directory (requires sphinx-autobuild)" 24 | @echo " dirhtml to make HTML files named index.html in directories" 25 | @echo " singlehtml to make a single large HTML file" 26 | @echo " pickle to make pickle files" 27 | @echo " json to make JSON files" 28 | @echo " htmlhelp to make HTML files and a HTML help project" 29 | @echo " qthelp to make HTML files and a qthelp project" 30 | @echo " devhelp to make HTML files and a Devhelp project" 31 | @echo " epub to make an epub" 32 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 33 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 34 | @echo " text to make text files" 35 | @echo " man to make manual pages" 36 | @echo " changes to make an overview of all changed/added/deprecated items" 37 | @echo " linkcheck to check all external links for integrity" 38 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 39 | 40 | clean: 41 | -rm -rf $(BUILDDIR)/* 42 | 43 | livehtml: 44 | sphinx-autobuild -i .#* -i *.pyc -i .*.swp -i .*.swo -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 45 | 46 | html: 47 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 48 | @echo 49 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 50 | 51 | dirhtml: 52 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 53 | @echo 54 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 55 | 56 | singlehtml: 57 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 58 | @echo 59 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 60 | 61 | optimizer-dcc: 62 | $(SPHINXBUILD) -D master_doc="optimizer/overview" -E -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/optimizer-dcc 63 | @echo 64 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 65 | 66 | pickle: 67 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 68 | @echo 69 | @echo "Build finished; now you can process the pickle files." 70 | 71 | json: 72 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 73 | @echo 74 | @echo "Build finished; now you can process the JSON files." 75 | 76 | htmlhelp: 77 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 78 | @echo 79 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 80 | ".hhp project file in $(BUILDDIR)/htmlhelp." 81 | 82 | qthelp: 83 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 84 | @echo 85 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 86 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 87 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Pyro.qhcp" 88 | @echo "To view the help file:" 89 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Pyro.qhc" 90 | 91 | devhelp: 92 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 93 | @echo 94 | @echo "Build finished." 95 | @echo "To view the help file:" 96 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Pyro" 97 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Pyro" 98 | @echo "# devhelp" 99 | 100 | epub: 101 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 102 | @echo 103 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 104 | 105 | latex: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo 108 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 109 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 110 | "(use \`make latexpdf' here to do that automatically)." 111 | 112 | latexpdf: 113 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 114 | @echo "Running LaTeX files through pdflatex..." 115 | make -C $(BUILDDIR)/latex all-pdf 116 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 117 | 118 | text: 119 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 120 | @echo 121 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 122 | 123 | man: 124 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 125 | @echo 126 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 127 | 128 | changes: 129 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 130 | @echo 131 | @echo "The overview file is in $(BUILDDIR)/changes." 132 | 133 | linkcheck: 134 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 135 | @echo 136 | @echo "Link check complete; look for any errors in the above output " \ 137 | "or in $(BUILDDIR)/linkcheck/output.txt." 138 | 139 | doctest: 140 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 141 | @echo "Testing of doctests in the sources finished, look at the " \ 142 | "results in $(BUILDDIR)/doctest/output.txt." 143 | 144 | ipynb: $(IPYRST) 145 | 146 | svg-embed: $(SVGVIEW) 147 | 148 | %_view.svg: %_embed.svg 149 | ./inkscape/embedimage.py $< > $@ 150 | 151 | #TODO maybe use a config file for this 152 | %.rst: %.ipynb 153 | jupyter nbconvert --NbConvertApp.output_files_dir='{notebook_name}-ipynb' --to rst $< 154 | 155 | #sed -i '1s;^;#$(construction means that the dependencies between class attributes are resolved 17 | automatically. During the construction of each attribute, any required attributes are accessed and therefore 18 | constructed if they haven't already been. 19 | 20 | The price for the convenience is that construction becomes implicit and recursive. The wrappers in this library 21 | do some error checking to aid with this and to properly report AttributeError. Code also ends up somewhat more 22 | verbose with the decorator boilerplate. 23 | 24 | ## Quick Example 25 | 26 | ```python 27 | import declarative 28 | 29 | class Child(object): 30 | id = None 31 | 32 | class Parent(object): 33 | @declarative.mproperty 34 | def child_registry(self): 35 | return set() 36 | 37 | @declarative.mproperty 38 | def c1(self): 39 | print("made Parent.c1") 40 | child = Child() 41 | child.id = 1 42 | self.child_registry.add(child) 43 | return child 44 | 45 | @declarative.mproperty 46 | def c2(self): 47 | print("made Parent.c2") 48 | child = Child() 49 | child.id = 2 50 | self.child_registry.add(child) 51 | return child 52 | 53 | parent = Parent() 54 | parent.c1 55 | #>> made Parent.c1 56 | parent.c2 57 | #>> made Parent.c2 58 | print(parent.child_registry) 59 | ``` 60 | 61 | Ok, so now as the child object attributes are accessed, they are also registered. 62 | 63 | ## More automatic Example 64 | 65 | ```python 66 | import declarative 67 | 68 | class Child(declarative.OverridableObject): 69 | id = None 70 | 71 | class Parent(declarative.OverridableObject): 72 | @declarative.mproperty 73 | def child_registry(self): 74 | return set() 75 | 76 | @declarative.dproperty 77 | def c1(self, val = None): 78 | if val is None: 79 | child = Child( 80 | id = 1, 81 | ) 82 | print("made Parent.c1") 83 | else: 84 | print("Using outside c1") 85 | child = val 86 | 87 | self.child_registry.add(child) 88 | return child 89 | 90 | @declarative.dproperty 91 | def c2(self): 92 | child = Child( 93 | id = 2, 94 | ) 95 | print("made Parent.c2") 96 | self.child_registry.add(child) 97 | return child 98 | 99 | @declarative.dproperty 100 | def c2b(self): 101 | child = Child( 102 | id = self.c2.id + 0.5 103 | ) 104 | print("made Parent.c2b") 105 | self.child_registry.add(child) 106 | return child 107 | 108 | parent = Parent() 109 | #>> made Parent.c2 110 | #>> made Parent.c2b 111 | #>> made Parent.c1 112 | print(parent.child_registry) 113 | ``` 114 | 115 | Now the registry is filled instantly. 116 | 117 | Alternatively, c1 for this object can be replaced. 118 | 119 | 120 | ``` 121 | parent = Parent( 122 | c1 = Child(id = 8) 123 | ) 124 | #>> made Parent.c2 125 | #>> made Parent.c2b 126 | #>> using outside c1 127 | print(parent.child_registry) 128 | ``` 129 | 130 | No __init__ function! 131 | 132 | ## Numerical Usage 133 | 134 | This technique can be applied for memoized numerical results, particularly when you might 135 | want to canonicalize the inputs to use a numpy representation. 136 | 137 | ```python 138 | import declarative 139 | 140 | class MultiFunction(declarative.OverridableObject): 141 | @declarative.dproperty 142 | def input_A(self, val): 143 | #not providing a default makes them required keyword arguments 144 | #during construction 145 | return numpy.asarray(val) 146 | 147 | @declarative.dproperty 148 | def input_B(self, val): 149 | return numpy.asarray(val) 150 | 151 | @declarative.mproperty 152 | def output_A(self): 153 | #note usage of mproperty. This will only be computed if accessed, not at construction 154 | return self.input_A + self.input_B 155 | 156 | @declarative.mproperty 157 | def output_B(self): 158 | #note the use of incremental computing into output_A 159 | return self.input_A * self.input_B - self.output_A 160 | 161 | data = MultiFunction( 162 | input_A = [1,2,3], 163 | input_B = [4,5,6], 164 | ) 165 | print(data.output_A) 166 | ``` 167 | 168 | ## Additional Features 169 | 170 | ### Argparse interface 171 | Mentioned above. Some additional annotations and run methods can allow objects to be called and accessed from the command line, without a special interface while providing improved composition of declarative programming. 172 | 173 | ### Bunches 174 | These are dictionary objects that also allow indexing through the '.' attribute access operator. Other libraries provide these, but the ones included here are 175 | 176 | * Bunch - just a dictionary wrapper. It also wraps any dictionary's that are stored to provide a consistent interface. 177 | * DeepBunch - Allows nested access without construction of intermediate dictionary's. Extremely convenient for configuration management of hierarchical systems. 178 | * HDFDeepBunch - DeepBunch adapted so that the underlying storage are HDF5 data groups using the h5py library. Automatically converts to/from numpy arrays and unwraps values. DeepBunch's containing compatible numbers/arrays can be directly embedded into hdf. This allows configuration storage with datasets. 179 | 180 | See the Bunch page for more. 181 | 182 | ### Relays and Callbacks 183 | A number of objects are provided for reactor programming. These are RelayValue and RelayBool which store values and run callbacks upon their change. This is similar Qt's signal/socket programming but lightweight for python. 184 | 185 | ### Substrate System 186 | This is the culmination of the declarative techniques to hierarchical simulation and modeling. Child objects automatically embed into parent objects and gain access to nonlocal data and registration interfaces. It invokes considerably more "magic" than this library typically need. Currently used by the python physics/controls simulation software openLoop. 187 | 188 | ## Development 189 | This library was developed in initial form to generate the Control System and interfaces of the Holometer experiment at Fermilab. The underlying technology for that is the EPICS distributed experimental control library (developed at Argonne). 190 | 191 | RelayBool and RelayValue objects were bound to EPICS variables using the declarative construction methods of this library. Further logic cross-linked variables and interfaced to hardware. Bunches were used for configuration management. HDFDeepBunch was used for data analysis. 192 | 193 | 194 | ## Related Documentation 195 | Using multiple inheritance and mixins becomes very simple with this style of programming, but super is often needed, but forces the use of keyword arguments. Since this library forces them anyway, this site details other considerations for using super: 196 | * https://fuhm.net/super-harmful/ 197 | 198 | 199 | -------------------------------------------------------------------------------- /declarative/bunch/shadow_bunch.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | """ 4 | try: 5 | from collections.abc import Mapping as MappingABC 6 | except ImportError: 7 | from collections import Mapping as MappingABC 8 | from numbers import Number 9 | from ..utilities.future_from_2 import repr_compat, str, unicode 10 | 11 | from ..utilities.unique import unique_generator 12 | NOARG = unique_generator() 13 | 14 | 15 | class ShadowBunch(object): 16 | """ 17 | """ 18 | __slots__ = ('_dicts', '_idx', '__reduce__', '__reduce_ex__', '_abdict') 19 | _names = {} 20 | #pulls all values into the active dictionary. This shows everything that has been accessed 21 | _pull_full = False 22 | 23 | ABOUT_KEY = unique_generator() 24 | 25 | #needed to not explode some serializers since this object generally "hasattr" almost anything 26 | #__reduce_ex__ = None 27 | #__reduce__ = None 28 | __copy__ = None 29 | __deepcopy__ = None 30 | 31 | def __init__( 32 | self, 33 | dicts, 34 | abdict = None, 35 | assign_idx = 0, 36 | ): 37 | self._dicts = tuple(dicts) 38 | self._idx = assign_idx 39 | self._abdict = abdict 40 | return 41 | 42 | def __getitem__(self, key): 43 | dicts = [] 44 | anyfull = False 45 | for d in self._dicts: 46 | try: 47 | item = d[key] 48 | except KeyError: 49 | continue 50 | if not isinstance(item, MappingABC): 51 | if dicts and anyfull: 52 | #break returns the dictionaries already stored if suddenly one doesn't store a dict 53 | #it DOES not just skip that item to return further dictionaries 54 | break 55 | else: 56 | if self._pull_full: 57 | if d is not self._dicts[self._idx]: 58 | self._dicts[self._idx][key] = item 59 | return item 60 | if item: 61 | anyfull = True 62 | dicts.append(item) 63 | if self._abdict is not None: 64 | abdict = self._abdict[key] 65 | else: 66 | abdict = None 67 | return self.__class__( 68 | dicts, 69 | assign_idx = self._idx, 70 | abdict = abdict, 71 | ) 72 | 73 | def useidx(self, idx): 74 | if not isinstance(idx, Number): 75 | idx = self._names[idx] 76 | return self.__class__( 77 | self._dicts, 78 | assign_idx = idx, 79 | abdict = self._abdict, 80 | ) 81 | 82 | def extractidx(self, idx, default = NOARG): 83 | if not isinstance(idx, Number): 84 | idx = self._names[idx] 85 | try: 86 | return self._dicts[idx] 87 | except IndexError: 88 | if default is NOARG: 89 | raise 90 | return default 91 | 92 | def __getattr__(self, key): 93 | try: 94 | return super(ShadowBunch, self).__getattr__(key) 95 | except AttributeError: 96 | pass 97 | try: 98 | return self.__getitem__(key) 99 | except KeyError: 100 | raise AttributeError("'{0}' not in {1}".format(key, self)) 101 | 102 | def __setitem__(self, key, item): 103 | self._dicts[self._idx][key] = item 104 | 105 | def __setattr__(self, key, item): 106 | if key in self.__slots__: 107 | return super(ShadowBunch, self).__setattr__(key, item) 108 | return self.__setitem__(key, item) 109 | 110 | def __delitem__(self, key): 111 | del self._dicts[self._idx][key] 112 | 113 | def __delattr__(self, key): 114 | return self.__delitem__(key) 115 | 116 | def get(self, key, default = NOARG): 117 | if key in self: 118 | return self[key] 119 | elif default is not NOARG: 120 | return default 121 | raise KeyError(key) 122 | 123 | def setdefault(self, key, default, about = None): 124 | """ 125 | kwargs is special in the it sets one of the abdicts. 126 | """ 127 | dicts = [] 128 | anyfull = False 129 | if about is not None: 130 | if self._abdict is None: 131 | raise RuntimeError("abdict not specified on this ShadowBunch, so setdefault cannot have an about specifier") 132 | self._abdict[key][self.ABOUT_KEY] = about 133 | for d in self._dicts: 134 | try: 135 | item = d[key] 136 | except KeyError: 137 | continue 138 | if not isinstance(item, MappingABC): 139 | if dicts and anyfull: 140 | #break returns the dictionaries already stored if suddenly one doesn't store a dict 141 | #it DOES not just skip that item to return further dictionaries 142 | break 143 | else: 144 | if self._pull_full: 145 | if d is not self._dicts[self._idx]: 146 | self._dicts[self._idx][key] = item 147 | return item 148 | if item: 149 | anyfull = True 150 | dicts.append(item) 151 | if anyfull: 152 | if self._abdict is not None: 153 | abdict = self._abdict[key] 154 | else: 155 | abdict = None 156 | return self.__class__( 157 | dicts, 158 | assign_idx = self._idx, 159 | abdict = abdict, 160 | ) 161 | else: 162 | self[key] = default 163 | return default 164 | 165 | def __contains__(self, key): 166 | for d in self._dicts: 167 | if key in d: 168 | return True 169 | return False 170 | 171 | def has_key(self, key): 172 | return key in self 173 | 174 | def __dir__(self): 175 | items = list(super(ShadowBunch, self).__dir__()) 176 | for d in self._dicts: 177 | items.extend(k for k in d.keys() if isinstance(k, (str, unicode))) 178 | items.sort() 179 | return items 180 | 181 | @repr_compat 182 | def __repr__(self): 183 | return ( 184 | '{0}({1}, idx={2})' 185 | ).format( 186 | self.__class__.__name__, 187 | self._dicts, 188 | self._idx, 189 | ) 190 | 191 | def _repr_pretty_(self, p, cycle): 192 | if cycle: 193 | p.text(self.__class__.__name__ + '()') 194 | else: 195 | with p.group(4, self.__class__.__name__ + '([', '])'): 196 | first = True 197 | for d in self._dicts: 198 | p.pretty(d) 199 | p.breakable() 200 | if not first: 201 | p.text(',') 202 | p.breakable() 203 | return 204 | 205 | #def __eq__(self, other): 206 | # return 207 | # 208 | #def __ne__(self, other): 209 | # return not (self == other) 210 | 211 | def __iter__(self): 212 | return iter(self.keys()) 213 | 214 | def __len__(self): 215 | ks = set() 216 | for d in self._dicts: 217 | ks.update(d.keys()) 218 | return len(ks) 219 | 220 | def __bool__(self): 221 | for d in self._dicts: 222 | if d: 223 | return True 224 | else: 225 | return False 226 | 227 | def keys(self): 228 | ks = set() 229 | for d in self._dicts: 230 | ks.update(d.keys()) 231 | return iter(ks) 232 | 233 | def values(self): 234 | for key in self.keys(): 235 | yield self[key] 236 | return 237 | 238 | def items(self): 239 | for key in self.keys(): 240 | yield key, self[key] 241 | return 242 | 243 | 244 | MappingABC.register(ShadowBunch) 245 | -------------------------------------------------------------------------------- /declarative/properties/memoized_adv.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | """ 4 | from __future__ import ( 5 | division, 6 | print_function, 7 | absolute_import, 8 | ) 9 | #from builtins import object 10 | 11 | 12 | from ..utilities.unique import ( 13 | NOARG, 14 | unique_generator 15 | ) 16 | 17 | 18 | from .bases import ( 19 | InnerException, 20 | PropertyTransforming, 21 | ) 22 | 23 | from .utilities import ( 24 | raise_attrerror_from_property, 25 | raise_msg_from_property, 26 | try_name_file_line, 27 | ) 28 | 29 | from .memoized import AccessError 30 | 31 | _UNIQUE_local = unique_generator() 32 | 33 | 34 | class MemoizedAdvDescriptor(object): 35 | def __init__( 36 | self, 37 | fgenerate, 38 | name = None, 39 | doc = None, 40 | declarative = None, 41 | ): 42 | if name is None: 43 | self.__name__ = fgenerate.__name__ 44 | else: 45 | self.__name__ = name 46 | 47 | if doc is None: 48 | self.__doc__ = fgenerate.__doc__ 49 | else: 50 | self.__doc__ = doc 51 | 52 | if declarative is not None: 53 | self._declarative_instantiation = declarative 54 | 55 | self.fconstruct_warg = None 56 | self.fconstruct_narg = None 57 | 58 | fgenerate(self) 59 | 60 | if self.fconstruct_warg is None and self.fconstruct_narg is None: 61 | raise RuntimeError("Must Specify a function") 62 | return 63 | 64 | def construct(self, fconstruct): 65 | #TODO wrap the function call appropriately 66 | self.fconstruct_narg = fconstruct 67 | self.fconstruct_warg = fconstruct 68 | 69 | def construct_narg(self, fconstruct): 70 | self.fconstruct_narg = fconstruct 71 | 72 | def construct_warg(self, fconstruct): 73 | self.fconstruct_warg = fconstruct 74 | 75 | def fsetter(self, arg, arg_prev): 76 | raise RuntimeError("Setting not supported on {0}".self(__name__)) 77 | 78 | def fdeleter(self, arg_prev): 79 | raise RuntimeError("Deleting not supported on {0}".self(__name__)) 80 | 81 | def __get__(self, obj, cls): 82 | try: 83 | if obj is None: 84 | return self 85 | result = obj.__dict__.get(self.__name__, _UNIQUE_local) 86 | if result is _UNIQUE_local: 87 | #bd = obj.__boot_dict__ 88 | bd = getattr(obj, '__boot_dict__', None) 89 | if bd is not None: 90 | result = bd.pop(self.__name__, _UNIQUE_local) 91 | if not bd: 92 | del obj.__boot_dict__ 93 | if result is _UNIQUE_local: 94 | try: 95 | result = self.fconstruct_narg(obj) 96 | except TypeError as e: 97 | raise_msg_from_property( 98 | ("Property attribute {name} of {orepr} accessed with no initial value. Needs an initial value, or" 99 | " to be declared with a default argument at file:" 100 | "\n{filename}" 101 | "\nline {lineno}."), 102 | AccessError, self, obj, e, 103 | if_from_file = __file__, 104 | **try_name_file_line(self.fconstruct_narg) 105 | ) 106 | raise 107 | except AttributeError as e: 108 | raise_attrerror_from_property(self, obj, e) 109 | else: 110 | try: 111 | result = self.fconstruct_warg(obj, result) 112 | except TypeError as e: 113 | raise_msg_from_property( 114 | ("Property attribute {name} of {orepr} accessed with no initial value. Needs an initial value, or" 115 | " to be declared with a default argument at file:" 116 | "\n{filename}" 117 | "\nline {lineno}."), 118 | AccessError, self, obj, e, 119 | if_from_file = __file__, 120 | **try_name_file_line(self.fconstruct_warg) 121 | ) 122 | raise 123 | except AttributeError as e: 124 | raise_attrerror_from_property(self, obj, e) 125 | if __debug__: 126 | if result is NOARG: 127 | raise InnerException("Return result was NOARG") 128 | 129 | if isinstance(result, PropertyTransforming): 130 | result = result.construct( 131 | parent = obj, 132 | name = self.__name__, 133 | ) 134 | 135 | obj.__dict__[self.__name__] = result 136 | return result 137 | except AttributeError as e: 138 | raise RuntimeError(e) 139 | 140 | def __set__(self, obj, value): 141 | oldvalue = obj.__dict__.get(self.__name__, _UNIQUE_local) 142 | if oldvalue is _UNIQUE_local: 143 | bd = getattr(obj, '__boot_dict__', None) 144 | if bd is not None: 145 | oldv = bd.setdefault(self.__name__, value) 146 | if oldv is not value: 147 | raise RuntimeError("Initial set on object must be unique") 148 | else: 149 | obj.__boot_dict__ = {self.__name__ : value} 150 | else: 151 | #the new value shows up in the object BEFORE the exchanger is called 152 | if oldvalue is value: 153 | return 154 | if __debug__: 155 | import inspect 156 | args, _, _, _ = inspect.getargspec(self.fget) 157 | if len(args) < 3: 158 | raise RuntimeError("The memoized member ({0}) does not support value exchange".format(self.__name__)) 159 | obj.__dict__[self.__name__] = value 160 | try: 161 | revalue = self.fsetter(obj, value, oldvalue) 162 | except AttributeError as e: 163 | raise_attrerror_from_property(self, obj, e) 164 | if revalue is not NOARG: 165 | obj.__dict__[self.__name__] = revalue 166 | else: 167 | del obj.__dict__[self.__name__] 168 | return 169 | 170 | def __delete__(self, obj): 171 | oldvalue = obj.__dict__.get(self.__name__, _UNIQUE_local) 172 | if oldvalue is _UNIQUE_local: 173 | raise InnerException("Value never initialized") 174 | else: 175 | #the new value shows up in the object BEFORE the exchanger is called 176 | if __debug__: 177 | import inspect 178 | args, _, _, _ = inspect.getargspec(self.fget) 179 | if len(args) < 3: 180 | raise RuntimeError("The memoized member ({0}) does not support value exchange".format(self.__name__)) 181 | del obj.__dict__[self.__name__] 182 | try: 183 | revalue = self.fdeleter(obj, oldvalue) 184 | except AttributeError as e: 185 | raise_attrerror_from_property(self, obj, e) 186 | if revalue is not NOARG: 187 | obj.__dict__[self.__name__] = revalue 188 | return 189 | 190 | 191 | def mproperty_adv( 192 | __func = None, 193 | **kwargs 194 | ): 195 | def wrap(func): 196 | desc = MemoizedAdvDescriptor( 197 | func, **kwargs 198 | ) 199 | return desc 200 | if __func is not None: 201 | return wrap(__func) 202 | else: 203 | return wrap 204 | 205 | 206 | def dproperty_adv( 207 | __func = None, 208 | declarative = True, 209 | **kwargs 210 | ): 211 | return mproperty_adv( 212 | __func = __func, 213 | declarative = declarative, 214 | **kwargs 215 | ) 216 | 217 | -------------------------------------------------------------------------------- /declarative/argparse/oo_argparse.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | TODO, needs big documentation 4 | """ 5 | from __future__ import ( 6 | division, 7 | print_function, 8 | absolute_import, 9 | ) 10 | 11 | import sys 12 | import argparse 13 | import collections 14 | 15 | from ..bunch import Bunch 16 | from ..overridable_object import OverridableObject 17 | from ..utilities import SuperBase, NOARG 18 | from ..properties import mproperty 19 | 20 | 21 | types = Bunch( 22 | command = 'command', 23 | group = 'group', 24 | argument = 'argument', 25 | ) 26 | 27 | 28 | class OOArgParse( 29 | OverridableObject, 30 | SuperBase, 31 | object, 32 | ): 33 | @classmethod 34 | def __cls_argparse__( 35 | cls, 36 | args = None, 37 | _sys_exit = True, 38 | __usage_prog__ = None, 39 | base_kwargs = dict(), 40 | no_commands = False, 41 | **exkwargs 42 | ): 43 | arguments = dict() 44 | commands = dict() 45 | groups = dict() 46 | for attr_name in dir(cls): 47 | attr_obj = getattr(cls, attr_name) 48 | arganno = getattr(attr_obj, '_argparse', None) 49 | if arganno: 50 | if arganno.type == types.argument: 51 | #mask the argument if it was specified in an extra kwarg 52 | if attr_name not in exkwargs: 53 | arguments[attr_name] = arganno 54 | elif arganno.type == types.group: 55 | groups[attr_name] = arganno 56 | elif arganno.type == types.command: 57 | commands[attr_name] = arganno 58 | 59 | description = getattr(cls, "__arg_desc__", cls.__doc__) 60 | 61 | ap = argparse.ArgumentParser( 62 | prog = __usage_prog__, 63 | description = description, 64 | add_help = False 65 | ) 66 | ap.add_argument( 67 | '-h', '--help', '--halp!', 68 | dest = 'help', 69 | action='store_true', 70 | help = "Show this help screen" 71 | #help = argparse.SUPPRESS, 72 | ) 73 | 74 | #build groups 75 | glist = collections.deque(sorted([(v.order, k, v) for k, v in groups.items()])) 76 | gengroups = dict() 77 | gengroups[None] = ap 78 | 79 | while glist: 80 | order, name, gbunch = glist.pop() 81 | useparse = gengroups.get(gbunch.group, None) 82 | if useparse is None: 83 | if gbunch.group not in groups: 84 | raise RuntimeError("Group {0} not defined!".format(gbunch.group)) 85 | #reinsert in back 86 | glist.appendleft((name, gbunch)) 87 | continue 88 | group = gbunch.func(useparse) 89 | gengroups[name] = group 90 | 91 | default_cmd = getattr(cls, "__arg_default__", None) 92 | commands_byname = dict() 93 | for name, cbunch in commands.items(): 94 | commands_byname[cbunch.cmd_name] = cbunch 95 | 96 | cmdlist = list(k for o, k in sorted((v.order, k) for k, v in commands_byname.items())) 97 | 98 | if not no_commands: 99 | if default_cmd is None: 100 | ap.add_argument( 101 | '__command__', 102 | metavar = 'COMMAND', 103 | choices = cmdlist, 104 | help = argparse.SUPPRESS, 105 | ) 106 | else: 107 | if commands_byname: 108 | ap.add_argument( 109 | '__command__', 110 | metavar = 'COMMAND', 111 | choices = cmdlist + [""], 112 | default = None, 113 | nargs='?', 114 | help = argparse.SUPPRESS, 115 | ) 116 | 117 | if len(commands_byname) > 0: 118 | ap.add_argument( 119 | '__args__', 120 | metavar = 'args', 121 | nargs=argparse.REMAINDER, 122 | help = argparse.SUPPRESS, 123 | ) 124 | 125 | argdo = dict() 126 | for order, argname, argbunch in sorted([(v.order, k, v) for k, v in arguments.items()]): 127 | useparse = gengroups.get(argbunch.group, None) 128 | argdo[argname] = argbunch.func(useparse) 129 | 130 | if args is None: 131 | args = sys.argv[1:] 132 | 133 | print_help = False 134 | #TODO fix -h handling requiring otherwise perfect other arguments 135 | if '-h' in args or '--help' in args or '--halp!' in args: 136 | try: 137 | class BooBoo(Exception): 138 | pass 139 | def error(message): 140 | raise BooBoo(message) 141 | ap.error = error 142 | args_parsed = ap.parse_args(args) 143 | print_help = args_parsed.help 144 | except BooBoo: 145 | print_help = True 146 | else: 147 | args_parsed = ap.parse_args(args) 148 | print_help = args_parsed.help 149 | 150 | if print_help: 151 | spp = ap.add_subparsers(dest = 'command', help = "(May have subarguments, use -h after them for command specific help)") 152 | 153 | if default_cmd is not None: 154 | if default_cmd.__doc__ is not None: 155 | doc = default_cmd.__doc__ + " [Default, no arguments]" 156 | else: 157 | doc = "Default Action [command only]" 158 | spp.add_parser( 159 | '""', 160 | help = doc, 161 | ) 162 | 163 | if not no_commands: 164 | for name, cbunch in commands.items(): 165 | commands_byname[cbunch.cmd_name] = cbunch 166 | if cbunch.takes_arguments: 167 | extra = " [takes arguments]" 168 | else: 169 | extra = " [command only]" 170 | spp.add_parser( 171 | cbunch.cmd_name, 172 | help = cbunch.description + extra, 173 | ) 174 | ap.print_help() 175 | sys.exit(0) 176 | 177 | kwargs = dict(base_kwargs) 178 | for argname, argbunch in arguments.items(): 179 | val = getattr(args_parsed, argname) 180 | if val is not NOARG: 181 | kwargs[argbunch.name_inject] = val 182 | 183 | if commands_byname and not no_commands: 184 | command = getattr(args_parsed, '__command__', NOARG) 185 | if command is not None: 186 | command_idx_in_args = args.index(command) 187 | call_args = args[:command_idx_in_args] 188 | else: 189 | call_args = args[:] 190 | else: 191 | command = None 192 | call_args = args 193 | args_sub = getattr(args_parsed, '__args__', NOARG) 194 | 195 | #add in the extra kwargs that were given 196 | kwargs.update(exkwargs) 197 | kwargs['__cls_argparse_args__'] = tuple(call_args) 198 | kwargs['__cls_argparse_cmd__'] = command 199 | kwargs['__cls_argparse_cmd_args__'] = tuple(args_sub) if args_sub is not NOARG else None 200 | obj = cls(**kwargs) 201 | if no_commands: 202 | return obj 203 | 204 | if command is None: 205 | ret = obj.__arg_default__() 206 | else: 207 | cbunch = commands_byname[command] 208 | call = getattr(obj, cbunch.run_name) 209 | #TODO, check about taking additional arguments 210 | if not cbunch.takes_arguments and args_sub: 211 | raise RuntimeError("Final Command does not take additional arguments") 212 | ret = call(args_sub) 213 | if _sys_exit: 214 | sys.exit(ret) 215 | return ret 216 | 217 | @mproperty 218 | def __cls_argparse_args__(self, args = None): 219 | return args 220 | 221 | @mproperty 222 | def __cls_argparse_cmd__(self, args = None): 223 | return args 224 | 225 | @mproperty 226 | def __cls_argparse_cmd_args__(self, args = None): 227 | return args 228 | -------------------------------------------------------------------------------- /docs/_static/pygments_adjust.css: -------------------------------------------------------------------------------- 1 | .highlight .hll { background-color: #ffffcc } 2 | .highlight { background: #f8f8f8; } 3 | .highlight .c { color: #408080; font-style: italic } /* Comment */ 4 | .highlight .err { border: 1px solid #FF0000 } /* Error */ 5 | .highlight .k { color: #008000; font-weight: bold } /* Keyword */ 6 | .highlight .o { color: #666666 } /* Operator */ 7 | .highlight .ch { color: #408080; font-style: italic } /* Comment.Hashbang */ 8 | .highlight .cm { color: #408080; font-style: italic } /* Comment.Multiline */ 9 | .highlight .cp { color: #BC7A00 } /* Comment.Preproc */ 10 | .highlight .cpf { color: #408080; font-style: italic } /* Comment.PreprocFile */ 11 | .highlight .c1 { color: #408080; font-style: italic } /* Comment.Single */ 12 | .highlight .cs { color: #408080; font-style: italic } /* Comment.Special */ 13 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 14 | .highlight .ge { font-style: italic } /* Generic.Emph */ 15 | .highlight .gr { color: #FF0000 } /* Generic.Error */ 16 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 17 | .highlight .gi { color: #00A000 } /* Generic.Inserted */ 18 | .highlight .go { color: #888888 } /* Generic.Output */ 19 | .highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ 20 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 21 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 22 | .highlight .gt { color: #0044DD } /* Generic.Traceback */ 23 | .highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ 24 | .highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ 25 | .highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ 26 | .highlight .kp { color: #008000 } /* Keyword.Pseudo */ 27 | .highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ 28 | .highlight .kt { color: #B00040 } /* Keyword.Type */ 29 | .highlight .m { color: #666666 } /* Literal.Number */ 30 | .highlight .s { color: #BA2121 } /* Literal.String */ 31 | .highlight .na { color: #7D9029 } /* Name.Attribute */ 32 | .highlight .nb { color: #008000 } /* Name.Builtin */ 33 | .highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */ 34 | .highlight .no { color: #880000 } /* Name.Constant */ 35 | .highlight .nd { color: #AA22FF } /* Name.Decorator */ 36 | .highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */ 37 | .highlight .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ 38 | .highlight .nf { color: #0000FF } /* Name.Function */ 39 | .highlight .nl { color: #A0A000 } /* Name.Label */ 40 | .highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ 41 | .highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ 42 | .highlight .nv { color: #19177C } /* Name.Variable */ 43 | .highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ 44 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 45 | .highlight .mb { color: #666666 } /* Literal.Number.Bin */ 46 | .highlight .mf { color: #666666 } /* Literal.Number.Float */ 47 | .highlight .mh { color: #666666 } /* Literal.Number.Hex */ 48 | .highlight .mi { color: #666666 } /* Literal.Number.Integer */ 49 | .highlight .mo { color: #666666 } /* Literal.Number.Oct */ 50 | .highlight .sa { color: #BA2121 } /* Literal.String.Affix */ 51 | .highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ 52 | .highlight .sc { color: #BA2121 } /* Literal.String.Char */ 53 | .highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ 54 | .highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ 55 | .highlight .s2 { color: #BA2121 } /* Literal.String.Double */ 56 | .highlight .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ 57 | .highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ 58 | .highlight .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ 59 | .highlight .sx { color: #008000 } /* Literal.String.Other */ 60 | .highlight .sr { color: #BB6688 } /* Literal.String.Regex */ 61 | .highlight .s1 { color: #BA2121 } /* Literal.String.Single */ 62 | .highlight .ss { color: #19177C } /* Literal.String.Symbol */ 63 | .highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ 64 | .highlight .fm { color: #0000FF } /* Name.Function.Magic */ 65 | .highlight .vc { color: #19177C } /* Name.Variable.Class */ 66 | .highlight .vg { color: #19177C } /* Name.Variable.Global */ 67 | .highlight .vi { color: #19177C } /* Name.Variable.Instance */ 68 | .highlight .vm { color: #19177C } /* Name.Variable.Magic */ 69 | .highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ 70 | 71 | 72 | 73 | /*.highlight .hll { background-color: #e0e1e3 } 74 | .highlight { background: #efefef; color: #f8f8f2 } */ 75 | .highlight .c { color: #8d8d8d } /* Comment */ 76 | .highlight .err { color: #960050; background-color: #1e0010 } /* Error */ 77 | .highlight .k { color: #713bc5 } /* Keyword */ 78 | .highlight .l { color: #055be0 } /* Literal */ 79 | .highlight .n { color: #303030 } /* Name */ 80 | .highlight .o { color: #055be0; font-weight: bold; } /* Operator */ 81 | .highlight .p { color: #000000 } /* Punctuation */ 82 | .highlight .cm { color: #8d8d8d } /* Comment.Multiline */ 83 | .highlight .cp { color: #8d8d8d } /* Comment.Preproc */ 84 | .highlight .c1 { color: #8d8d8d } /* Comment.Single */ 85 | .highlight .cs { color: #8d8d8d } /* Comment.Special */ 86 | .highlight .gd { color: #f92672 } /* @ Generic.Deleted */ 87 | .highlight .ge { font-style: italic } /* Generic.Emph */ 88 | .highlight .gi { color: #a6e22e } /* @ Generic.Inserted */ 89 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 90 | .highlight .gu { color: #75715e } /* @ Generic.Subheading */ 91 | .highlight .kc { color: #713bc5 } /* Keyword.Constant */ 92 | .highlight .kd { color: #713bc5 } /* Keyword.Declaration */ 93 | .highlight .kn { color: #713bc5 } /* Keyword.Namespace */ 94 | .highlight .kp { color: #713bc5 } /* Keyword.Pseudo */ 95 | .highlight .kr { color: #713bc5 } /* Keyword.Reserved */ 96 | .highlight .kt { color: #713bc5 } /* Keyword.Type */ 97 | .highlight .ld { color: #055be0 } /* Literal.Date */ 98 | .highlight .m { color: #ff8132 } /* Literal.Number */ 99 | .highlight .s { color: #009e07 } /* Literal.String */ 100 | .highlight .na { color: #de143d } /* Name.Attribute */ 101 | .highlight .nb { color: #e22978 } /* Name.Builtin */ 102 | .highlight .nc { color: #e22978 } /* Name.Class */ 103 | .highlight .no { color: #66d9ef } /* @ Name.Constant */ 104 | .highlight .nd { color: #e22978 } /* Name.Decorator */ 105 | .highlight .ni { color: #f8f8f2 } /* @ Name.Entity */ 106 | .highlight .ne { color: #303030 } /* Name.Exception */ 107 | .highlight .nf { color: #e22978 } /* Name.Function */ 108 | .highlight .nl { color: #f8f8f2 } /* @ Name.Label */ 109 | .highlight .nn { color: #303030 } /* Name.Namespace */ 110 | .highlight .nx { color: #a6e22e } /* @ Name.Other */ 111 | .highlight .py { color: #f8f8f2 } /* @ Name.Property */ 112 | .highlight .nt { color: #f92672 } /* @ Name.Tag */ 113 | .highlight .nv { color: #f8f8f2 } /* @ Name.Variable */ 114 | .highlight .ow { color: #713bc5 } /* Operator.Word */ 115 | .highlight .w { color: #f8f8f2 } /* @ Text.Whitespace */ 116 | .highlight .mb { color: #ff8132 } /* Literal.Number.Bin */ 117 | .highlight .mf { color: #ff8132 } /* Literal.Number.Float */ 118 | .highlight .mh { color: #ff8132 } /* Literal.Number.Hex */ 119 | .highlight .mi { color: #ff8132 } /* Literal.Number.Integer */ 120 | .highlight .mo { color: #ff8132 } /* Literal.Number.Oct */ 121 | .highlight .sb { color: #009e07 } /* Literal.String.Backtick */ 122 | .highlight .sc { color: #009e07 } /* Literal.String.Char */ 123 | .highlight .sd { color: #009e07 } /* Literal.String.Doc */ 124 | .highlight .s2 { color: #009e07 } /* Literal.String.Double */ 125 | .highlight .se { color: #dc322f } /* Literal.String.Escape */ 126 | .highlight .sh { color: #009e07 } /* Literal.String.Heredoc */ 127 | .highlight .si { color: #009e07 } /* Literal.String.Interpol */ 128 | .highlight .sx { color: #009e07 } /* Literal.String.Other */ 129 | .highlight .sr { color: #009e07 } /* Literal.String.Regex */ 130 | .highlight .s1 { color: #009e07 } /* Literal.String.Single */ 131 | .highlight .ss { color: #009e07 } /* Literal.String.Symbol */ 132 | .highlight .bp { color: #e22978 } /* Name.Builtin.Pseudo */ 133 | .highlight .vc { color: #303030 } /* Name.Variable.Class */ 134 | .highlight .vg { color: #303030 } /* Name.Variable.Global */ 135 | .highlight .vi { color: #303030 } /* Name.Variable.Instance */ 136 | .highlight .il { color: #ff8132 } /* Literal.Number.Integer.Long */ 137 | -------------------------------------------------------------------------------- /declarative/callbacks/callbacks.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | """ 4 | import functools 5 | import threading 6 | #from builtins import object 7 | 8 | callback_lock = threading.Lock() 9 | 10 | 11 | class Callback(object): 12 | """ 13 | Callable object dispatching its call to registered callbacks 14 | 15 | :param initialcall: First call for the in the sequence, also provides docstring 16 | :param doc: (optional) can override the docstring for this object. 17 | 18 | This object provides functions transparently as extendable callback hooks. 19 | 20 | .. automethod:: register 21 | 22 | """ 23 | __slots__ = ( 24 | 'initialcall', 25 | 'callbacks', 26 | '_callbacks_edit', 27 | '_callbacks_editting', 28 | ) 29 | 30 | def __init__(self, initialcall = None, doc = None): 31 | self.initialcall = initialcall 32 | self.callbacks = {} 33 | self._callbacks_edit = self.callbacks 34 | self._callbacks_editting = 0 35 | 36 | if doc: 37 | self.__doc__ = doc 38 | else: 39 | if initialcall is not None: 40 | #TODO: Find why this is broken 41 | #self.__doc__ = initialcall.__doc__ 42 | pass 43 | return 44 | 45 | def register( 46 | self, 47 | key = None, 48 | callback = None, 49 | remove = False, 50 | throw = False 51 | ): 52 | """ 53 | Register a function into the callback 54 | 55 | :param key: registration key for callback 56 | :type key: hashable 57 | :param callback: function to be called 58 | :raises: KeyError if callback key is already present (keys must be unique) 59 | """ 60 | if key is None: 61 | key = callback 62 | if key is None: 63 | raise RuntimeError("Key or callback must be specified") 64 | with callback_lock: 65 | if ( 66 | (self._callbacks_editting > 0) and 67 | (self._callbacks_editting is self.callbacks) 68 | ): 69 | #make a copy to edit on 70 | self._callbacks_edit = dict(self.callbacks) 71 | if not remove: 72 | if key in self._callbacks_edit: 73 | raise KeyError(key) 74 | self._callbacks_edit[key] = callback 75 | else: 76 | try: 77 | del self._callbacks_edit[key] 78 | except KeyError: 79 | if throw: 80 | raise 81 | 82 | def __call__(self, *args, **kwargs): 83 | if self.initialcall is not None: 84 | self.initialcall(*args, **kwargs) 85 | self._callbacks_editting += 1 86 | for callback in list(self.callbacks.values()): 87 | callback(*args, **kwargs) 88 | with callback_lock: 89 | self._callbacks_editting -= 1 90 | if self._callbacks_editting == 0: 91 | c_edit = self._callbacks_edit 92 | if c_edit is not self.callbacks: 93 | self.callbacks = c_edit 94 | return 95 | 96 | 97 | class callbackmethod(object): 98 | """ 99 | Wraps a function into a callback 100 | 101 | :param fget: function to wrap as the default with docstring 102 | :param doc: (optional) provide this as the docstring rather than using fget's 103 | 104 | This wraps the method so that whenever it is called, other registered callbacks 105 | are called (after the instancemethod). 106 | 107 | This type is a :term:`descriptor(non-data)`. It is intended to be used via the 108 | :term:`decorator` syntax. 109 | 110 | Internally this works by constructing a callback object and memoizing it to preserve 111 | any callbacks added to it. 112 | """ 113 | 114 | def __init__(self, fget, doc=None): 115 | self.fget = fget 116 | self.__doc__ = doc if doc else fget.__doc__ 117 | self.__name__ = fget.__name__ 118 | 119 | def __get__(self, obj, cls): 120 | if obj is None: 121 | return self 122 | callback = Callback( 123 | initialcall = functools.partial(self.fget, obj) 124 | ) 125 | #obj.__dict__[self.__name__] = callback 126 | setattr(obj, self.__name__, callback) 127 | return callback 128 | 129 | 130 | class callbackstaticmethod(object): 131 | """ 132 | Like :class:`callbackmethod`, but does not provide `self` 133 | 134 | :param fget: function to wrap as the default with docstring 135 | :param doc: (optional) provide this as the docstring rather than using fget's 136 | 137 | This wraps the method so that whenever it is called, other registered callbacks 138 | are called (after the instancemethod). Note that self is **not** automatically added 139 | to the method call (so any method wrapped by this becomes like a staticmethod). 140 | 141 | This type is a :term:`descriptor(non-data)`. It is intended to be used via the 142 | :term:`decorator` syntax. 143 | 144 | Internally this works by constructing a callback object and memoizing it to preserve 145 | any callbacks added to it. 146 | 147 | """ 148 | 149 | def __init__(self, fget, doc=None): 150 | self.fget = fget 151 | self.__doc__ = doc if doc else fget.__doc__ 152 | self.__name__ = fget.__name__ 153 | 154 | def __get__(self, obj, cls): 155 | if obj is None: 156 | return self 157 | callback = Callback(initialcall = self.fget) 158 | #obj.__dict__[self.__name__] = callback 159 | setattr(obj, self.__name__, callback) 160 | return callback 161 | 162 | 163 | class SingleCallback(object): 164 | """ 165 | Provides a callback which can only be registered to once, allowing the callback 166 | to provide a return value. This type uses the __call__ interface to transparently 167 | act as the callback registered to it. 168 | 169 | :param default: a default function to call if the callback is not registered. 170 | 171 | The default argument also provides the docstring for the descriptor attribute. 172 | 173 | .. automethod:: register 174 | """ 175 | __slots__ = ('callback', 'default') 176 | 177 | def __init__(self, default = None, doc = None): 178 | #class sets the callback originally 179 | self.default = default 180 | self.callback = default 181 | 182 | if doc: 183 | self.__doc__ = doc 184 | else: 185 | if default is not None: 186 | self.__doc__ = default.__doc__ 187 | return 188 | 189 | def register(self, callback, remove = False): 190 | """ 191 | Register the function into the callback 192 | 193 | :param key: registration key for callback 194 | :type key: hashable 195 | :param callback: function to be called 196 | :raises: KeyError if callback key is already present or if removing and not present. 197 | """ 198 | if not remove: 199 | if self.callback is not self.default: 200 | raise RuntimeError('Single Callbacks may not be registered while they contain a callback') 201 | self.callback = callback 202 | else: 203 | if self.callback is self.default: 204 | raise RuntimeError('Single Callbacks must be registered when unregister is called') 205 | self.callback = self.default 206 | return 207 | 208 | def __call__(self, *args, **kwargs): 209 | self.callback(*args, **kwargs) 210 | 211 | 212 | class singlecallbackmethod(object): 213 | """ 214 | Like :class:`callbackmethod`, but is used for singlecallbacks. 215 | 216 | :param fget: function to wrap as the default with docstring 217 | :param doc: (optional) provide this as the docstring rather than using fget's 218 | 219 | Wraps the decorator function with a :class:`~.SingleCallback`, providing the wrapped 220 | function as the default (not called once the callback is set). This is mostly useful for 221 | providing docstrings to callback methods. 222 | 223 | Internally this works by constructing a callback object and memoizing it to preserve 224 | any callbacks added to it. 225 | 226 | This type is a :term:`descriptor(non-data)`. It is intended to be used via the 227 | :term:`decorator` syntax. 228 | """ 229 | 230 | def __init__(self, fget, doc=None): 231 | self.fget = fget 232 | self.__doc__ = doc if doc else fget.__doc__ 233 | self.__name__ = fget.__name__ 234 | 235 | def __get__(self, obj, cls): 236 | if obj is None: 237 | return self 238 | callback = Callback(default = functools.partial(self.fget, obj)) 239 | #obj.__dict__[self.__name__] = callback 240 | setattr(obj, self.__name__, callback) 241 | return callback 242 | 243 | 244 | -------------------------------------------------------------------------------- /declarative/properties/memoized_adv_group.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | """ 4 | from __future__ import ( 5 | division, 6 | print_function, 7 | absolute_import, 8 | ) 9 | #from builtins import object 10 | 11 | 12 | from ..utilities.unique import ( 13 | NOARG, 14 | unique_generator 15 | ) 16 | 17 | 18 | from .bases import ( 19 | InnerException, 20 | PropertyTransforming, 21 | ) 22 | 23 | from .utilities import ( 24 | raise_attrerror_from_property 25 | ) 26 | 27 | 28 | _UNIQUE_local = unique_generator() 29 | 30 | 31 | class MemoizedGroupDescriptorBase(object): 32 | def __get__(self, obj, cls): 33 | try: 34 | if obj is None: 35 | return self 36 | result = obj.__dict__.get(self.__name__, _UNIQUE_local) 37 | if result is _UNIQUE_local: 38 | bd = getattr(obj, '__boot_dict__', None) 39 | if bd is None: 40 | bd = {} 41 | obj.__boot_dict__ = bd 42 | storage = bd.get(self.root, None) 43 | if storage is None: 44 | try: 45 | self.root._build(obj, cls) 46 | except AttributeError as e: 47 | raise_attrerror_from_property(self, obj, e) 48 | except Exception as E: 49 | print(("Exception in: ", self.__name__, " of: ", obj)) 50 | raise 51 | storage = obj.__boot_dict__[self.root] 52 | 53 | bd = getattr(obj, '__boot_dict__', None) 54 | if bd is not None: 55 | result = bd.pop(self.__name__, _UNIQUE_local) 56 | if not bd: 57 | del obj.__boot_dict__ 58 | 59 | kwargs = dict() 60 | for rname, rdict in self.root.registries.items(): 61 | val = rdict.get(self.__name__, _UNIQUE_local) 62 | if val is not _UNIQUE_local: 63 | kwargs[rname] = val 64 | 65 | try: 66 | if result is _UNIQUE_local: 67 | result = self.func( 68 | obj, 69 | storage = storage, 70 | group = self.root.group, 71 | **kwargs 72 | ) 73 | else: 74 | result = self.func( 75 | obj, 76 | result, 77 | storage = storage, 78 | group = self.root.group, 79 | **kwargs 80 | ) 81 | except AttributeError as e: 82 | raise_attrerror_from_property(self, obj, e) 83 | 84 | if __debug__: 85 | if result is NOARG: 86 | raise InnerException("Return result was NOARG") 87 | 88 | if isinstance(result, PropertyTransforming): 89 | result = result.construct( 90 | parent = obj, 91 | name = self.__name__, 92 | ) 93 | 94 | obj.__dict__[self.__name__] = result 95 | 96 | return result 97 | except Exception as E: 98 | print(E) 99 | raise 100 | 101 | def __set__(self, obj, value): 102 | bd = getattr(obj, '__boot_dict__', None) 103 | if bd is None: 104 | bd = {} 105 | obj.__boot_dict__ = bd 106 | 107 | storage = bd.get(self.root, _UNIQUE_local) 108 | if storage is _UNIQUE_local: 109 | bdp = bd.get(self.root.__name__, _UNIQUE_local) 110 | if bdp is _UNIQUE_local: 111 | bdp = dict() 112 | bd[self.root.__name__] = bdp 113 | oldv = bdp.setdefault(self.__name__, value) 114 | if oldv is not value: 115 | raise RuntimeError("Initial set on object must be unique") 116 | else: 117 | raise NotImplementedError("Resetting not currently implemented") 118 | return 119 | 120 | def __delete__(self, obj): 121 | raise RuntimeError("Not Implemented") 122 | return 123 | 124 | 125 | class MemoizedGroupDescriptorRoot(MemoizedGroupDescriptorBase): 126 | def __init__( 127 | self, 128 | fgenerate, 129 | name = None, 130 | doc = None, 131 | declarative = None, 132 | ): 133 | self.registries = dict() 134 | self.group = dict() 135 | self._stems = None 136 | self.root = self 137 | 138 | if name is None: 139 | self.__name__ = fgenerate.__name__ 140 | else: 141 | self.__name__ = name 142 | 143 | if doc is None: 144 | self.__doc__ = fgenerate.__doc__ 145 | else: 146 | self.__doc__ = doc 147 | 148 | if declarative is not None: 149 | self._declarative_instantiation = declarative 150 | 151 | fgenerate(self) 152 | return 153 | 154 | def stems(self, *args): 155 | self._stems = args 156 | return 157 | 158 | def setup(self, func): 159 | #TODO wrap the function call appropriately 160 | self._setup = func 161 | 162 | def _setup(self, storage, group, **kwargs): 163 | return 164 | 165 | def default(self, func): 166 | #TODO wrap the function call appropriately 167 | self.func = func 168 | 169 | def func(self, obj, group, storage, **kwargs): 170 | return None 171 | #raise RuntimeError("Should specify") 172 | #return 173 | 174 | def __generate_virtual_descriptors__(self): 175 | return self.group 176 | 177 | def name_change(self, name): 178 | self.__name__ = name 179 | 180 | def mproperty( 181 | self, 182 | __func = None, 183 | name = None, 184 | stem = None, 185 | declarative = None, 186 | **kwargs 187 | ): 188 | def wrap(func): 189 | if name is None and stem is None: 190 | names = [func.__name__] 191 | elif stem is None: 192 | names = [name] 193 | else: 194 | if name is not None: 195 | names = [name] 196 | else: 197 | names = [] 198 | for stem_fmt in self._stems: 199 | names.append(stem.format(stem = stem_fmt, name = name, **kwargs)) 200 | principle_name = names[0] 201 | 202 | for k, v in kwargs.items(): 203 | inner = self.registries.get(k, None) 204 | if inner is None: 205 | inner = dict() 206 | self.registries[k] = inner 207 | inner[principle_name] = v 208 | 209 | if self.__name__ in names: 210 | desc = self 211 | self.func = func 212 | else: 213 | desc = MemoizedGroupDescriptor( 214 | func = func, 215 | name = principle_name, 216 | root = self.root, 217 | declarative = declarative, 218 | ) 219 | for usedname in names: 220 | self.root.group[usedname] = desc 221 | return desc 222 | if __func is not None: 223 | return wrap(__func) 224 | else: 225 | return wrap 226 | 227 | def dproperty( 228 | self, 229 | __func = None, 230 | declarative = True, 231 | **kwargs 232 | ): 233 | return self.mproperty( 234 | __func, 235 | declarative = declarative, 236 | **kwargs 237 | ) 238 | 239 | def _build(self, obj, cls): 240 | bd = getattr(obj, '__boot_dict__', None) 241 | if bd is None: 242 | bd = {} 243 | obj.__boot_dict__ = bd 244 | result = bd.get(self, _UNIQUE_local) 245 | if result is _UNIQUE_local: 246 | #bd = obj.__boot_dict__ 247 | bd = getattr(obj, '__boot_dict__', None) 248 | if bd is not None: 249 | sources = bd.pop(self.__name__, _UNIQUE_local) 250 | else: 251 | sources = _UNIQUE_local 252 | 253 | if sources is _UNIQUE_local: 254 | sources = dict() 255 | 256 | #the method should modify the sources values if it needs 257 | result = self._setup( 258 | obj, 259 | sources = sources, 260 | group = self.group, 261 | **self.registries 262 | ) 263 | 264 | #inject the values into the boot_dict 265 | if bd is None: 266 | bd = dict() 267 | obj.__boot_dict__ = bd 268 | for k, v in sources.items(): 269 | bd[k] = v 270 | 271 | if __debug__: 272 | if result is NOARG: 273 | raise InnerException("Return result was NOARG") 274 | 275 | if isinstance(result, PropertyTransforming): 276 | result = result.construct( 277 | parent = obj, 278 | name = self.__name__, 279 | ) 280 | 281 | bd = getattr(obj, '__boot_dict__', None) 282 | if bd is None: 283 | bd = {} 284 | obj.__boot_dict__ = bd 285 | bd[self] = result 286 | return 287 | 288 | 289 | class MemoizedGroupDescriptor(MemoizedGroupDescriptorBase): 290 | """ 291 | wraps a member function just as :obj:`property` but saves its value after evaluation 292 | (and is thus only evaluated once) 293 | """ 294 | _declarative_instantiation = False 295 | 296 | def __init__( 297 | self, 298 | func, 299 | root, 300 | name = None, 301 | doc = None, 302 | declarative = None, 303 | ): 304 | self.root = root 305 | self.func = func 306 | 307 | if name is None: 308 | self.__name__ = func.__name__ 309 | else: 310 | self.__name__ = name 311 | 312 | if doc is None: 313 | self.__doc__ = func.__doc__ 314 | else: 315 | self.__doc__ = doc 316 | 317 | if declarative is not None: 318 | self._declarative_instantiation = declarative 319 | return 320 | 321 | 322 | def mproperty_adv_group( 323 | __func = None, 324 | **kwargs 325 | ): 326 | def wrap(func): 327 | desc = MemoizedGroupDescriptorRoot( 328 | func, **kwargs 329 | ) 330 | return desc 331 | if __func is not None: 332 | return wrap(__func) 333 | else: 334 | return wrap 335 | 336 | 337 | def dproperty_adv_group( 338 | __func = None, 339 | declarative = True, 340 | **kwargs 341 | ): 342 | return mproperty_adv_group( 343 | __func = __func, 344 | declarative = declarative, 345 | **kwargs 346 | ) 347 | 348 | group_dproperty = dproperty_adv_group 349 | group_mproperty = mproperty_adv_group 350 | -------------------------------------------------------------------------------- /declarative/bunch/deep_bunch.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | """ 4 | from __future__ import division, print_function, unicode_literals 5 | from ..utilities.future_from_2 import repr_compat, object, str, unicode 6 | try: 7 | from collections.abc import Mapping as MappingABC 8 | except ImportError: 9 | from collections import Mapping as MappingABC 10 | 11 | #use local noarg to not conflict with others 12 | _NOARG = lambda : _NOARG 13 | NOARG = (_NOARG,) 14 | 15 | 16 | class DeepBunch(object): 17 | """ 18 | """ 19 | __slots__ = ('_dict', '_vpath',) 20 | #needed to not explode some serializers since this object generally "hasattr" almost anything 21 | __reduce_ex__ = None 22 | __reduce__ = None 23 | __copy__ = None 24 | __deepcopy__ = None 25 | 26 | def __init__( 27 | self, 28 | mydict = None, 29 | writeable = None, 30 | _vpath = None, 31 | ): 32 | if mydict is None: 33 | mydict = dict() 34 | #access through super is necessary because of the override on __setattr__ can't see these slots 35 | if not isinstance(mydict, dict): 36 | mydict = dict(mydict) 37 | super(DeepBunch, self).__setattr__('_dict', mydict) 38 | 39 | if _vpath is None: 40 | if writeable is None: 41 | writeable = True 42 | if writeable: 43 | _vpath = (), 44 | 45 | if _vpath is True: 46 | _vpath = () 47 | elif _vpath is False: 48 | pass 49 | elif _vpath is not None: 50 | _vpath = tuple(_vpath) 51 | assert(_vpath is not None) 52 | #self.__dict__['_vpath'] = vpath 53 | super(DeepBunch, self).__setattr__('_vpath', _vpath) 54 | return 55 | 56 | def _resolve_dict(self): 57 | if not self._vpath: 58 | return self._dict 59 | try: 60 | mydict = self._dict 61 | for idx, gname in enumerate(self._vpath): 62 | mydict = mydict[gname] 63 | #TODO: make better assert 64 | assert(isinstance(mydict, MappingABC)) 65 | except KeyError: 66 | if idx != 0: 67 | self._dict = mydict 68 | self._vpath = self._vpath[idx:] 69 | return None 70 | self._dict = mydict 71 | self._vpath = () 72 | return mydict 73 | 74 | @property 75 | def mydict(self): 76 | return self._resolve_dict() 77 | 78 | @property 79 | def _mydict_resolved(self): 80 | d = self._resolve_dict() 81 | if d is None: 82 | return dict() 83 | return d 84 | 85 | def _require_dict(self): 86 | if not self._vpath: 87 | return self._dict 88 | mydict = self._dict 89 | for gname in self._vpath: 90 | mydict = mydict.setdefault(gname, {}) 91 | #TODO: make better assert 92 | assert(isinstance(mydict, MappingABC)) 93 | self._vpath = () 94 | self._dict = mydict 95 | return mydict 96 | 97 | def __getitem__(self, key): 98 | mydict = self._resolve_dict() 99 | if mydict is None: 100 | if self._vpath is False: 101 | raise RuntimeError("This DeepBunch cannot index sub-dictionaries which do not exist.") 102 | return self.__class__( 103 | mydict = self._dict, 104 | _vpath = self._vpath + (key,), 105 | ) 106 | try: 107 | item = mydict[key] 108 | if isinstance(item, MappingABC): 109 | return self.__class__( 110 | mydict = item, 111 | _vpath = self._vpath, 112 | ) 113 | return item 114 | except KeyError as E: 115 | if self._vpath is not False: 116 | return self.__class__( 117 | mydict = self._dict, 118 | _vpath = self._vpath + (key,), 119 | ) 120 | if str(E).lower().find('object not found') != -1: 121 | raise KeyError("key '{0}' not found in {1}".format(key, self)) 122 | raise 123 | 124 | def __getattr__(self, key): 125 | try: 126 | return self.__getitem__(key) 127 | except KeyError: 128 | raise AttributeError("'{1}' not in {0}".format(self, key)) 129 | 130 | def __setitem__(self, key, item): 131 | mydict = self._require_dict() 132 | try: 133 | mydict[key] = item 134 | return 135 | except TypeError: 136 | raise TypeError("Can't insert {0} into {1} at key {2}".format(item, mydict, key)) 137 | 138 | def __setattr__(self, key, item): 139 | if key in self.__slots__: 140 | return super(DeepBunch, self).__setattr__(key, item) 141 | return self.__setitem__(key, item) 142 | 143 | def __delitem__(self, key): 144 | mydict = self._resolve_dict() 145 | if mydict is None: 146 | return 147 | del self._dict[key] 148 | 149 | def __delattr__(self, key): 150 | return self.__delitem__(key) 151 | 152 | def get(self, key, default = NOARG): 153 | mydict = self._resolve_dict() 154 | if mydict is None: 155 | if default is not NOARG: 156 | return default 157 | else: 158 | raise KeyError("key '{0}' not found in {1}".format(key, self)) 159 | try: 160 | return mydict[key] 161 | except KeyError: 162 | if default is not NOARG: 163 | return default 164 | raise 165 | 166 | def setdefault(self, key, default): 167 | mydict = self._require_dict() 168 | return mydict.setdefault(key, default) 169 | 170 | def __contains__(self, key): 171 | mydict = self._resolve_dict() 172 | if mydict is None: 173 | return False 174 | return key in mydict 175 | 176 | def has_key(self, key): 177 | mydict = self._resolve_dict() 178 | if mydict is None: 179 | return False 180 | return key in mydict 181 | 182 | def require_deleted(self, key): 183 | mydict = self._resolve_dict() 184 | if mydict is None: 185 | return 186 | try: 187 | del self._dict[key] 188 | except KeyError: 189 | pass 190 | return 191 | 192 | def update_recursive(self, db = None, **kwargs): 193 | if self._vpath is False: 194 | def recursive_op(to_db, from_db): 195 | for key, val in list(from_db.items()): 196 | if isinstance(val, MappingABC): 197 | try: 198 | rec_div = to_db[key] 199 | except KeyError: 200 | rec_div = dict() 201 | recursive_op(rec_div, val) 202 | if rec_div: 203 | to_db[key] = rec_div 204 | else: 205 | to_db[key] = val 206 | else: 207 | def recursive_op(to_db, from_db): 208 | for key, val in list(from_db.items()): 209 | if isinstance(val, MappingABC): 210 | recursive_op(to_db[key], val) 211 | else: 212 | to_db[key] = val 213 | 214 | if db is not None: 215 | recursive_op(self, db) 216 | 217 | if kwargs: 218 | recursive_op(self, kwargs) 219 | return 220 | 221 | @classmethod 222 | def ensure_wrap(cls, item): 223 | if isinstance(item, cls): 224 | return item 225 | return cls(item) 226 | 227 | def __dir__(self): 228 | items = [k for k in self._dict.keys() if isinstance(k, (str, unicode))] 229 | items += ['mydict'] 230 | #items.sort() 231 | #items += dir(super(Bunch, self)) 232 | return items 233 | 234 | @repr_compat 235 | def __repr__(self): 236 | if self._vpath is False: 237 | vpath = 'False' 238 | elif self._vpath == (): 239 | vpath = 'True' 240 | return ( 241 | '{0}({1})' 242 | ).format( 243 | self.__class__.__name__, 244 | self._dict, 245 | ) 246 | else: 247 | vpath = self._vpath 248 | return ( 249 | '{0}({1}, vpath={2},)' 250 | ).format( 251 | self.__class__.__name__, 252 | self._dict, 253 | vpath, 254 | ) 255 | 256 | def _repr_pretty_(self, p, cycle): 257 | if cycle: 258 | p.text(self.__class__.__name__ + '()') 259 | else: 260 | with p.group(4, self.__class__.__name__ + '(', ')'): 261 | first = True 262 | for k, v in sorted(list(self._dict.items())): 263 | if not first: 264 | p.text(',') 265 | p.breakable() 266 | else: 267 | p.breakable() 268 | first = False 269 | p.pretty(k) 270 | p.text(' = ') 271 | p.pretty(v) 272 | if not first: 273 | p.text(',') 274 | p.breakable() 275 | return 276 | 277 | def __eq__(self, other): 278 | try: 279 | return self._mydict_resolved == other._mydict_resolved 280 | except AttributeError: 281 | return False 282 | 283 | def __ne__(self, other): 284 | return not (self == other) 285 | 286 | def __iter__(self): 287 | mydict = self._resolve_dict() 288 | if mydict is None: 289 | return iter(()) 290 | return iter(mydict) 291 | 292 | def __len__(self): 293 | mydict = self._resolve_dict() 294 | if mydict is None: 295 | return 0 296 | return len(mydict) 297 | 298 | def clear(self): 299 | mydict = self._resolve_dict() 300 | if mydict is None: 301 | return 302 | for key in list(mydict.keys()): 303 | del mydict[key] 304 | return 305 | 306 | def keys(self): 307 | mydict = self._resolve_dict() 308 | if mydict is None: 309 | return iter(()) 310 | return iter(list(mydict.keys())) 311 | 312 | def values(self): 313 | mydict = self._resolve_dict() 314 | if mydict is None: 315 | return 316 | for key in list(mydict.keys()): 317 | yield self[key] 318 | return 319 | 320 | def items(self): 321 | mydict = self._resolve_dict() 322 | if mydict is None: 323 | return 324 | for key in list(mydict.keys()): 325 | yield key, self[key] 326 | return 327 | 328 | def __bool__(self): 329 | mydict = self._resolve_dict() 330 | if mydict is None: 331 | return False 332 | return bool(mydict) 333 | 334 | def __call__(self): 335 | raise RuntimeError("DeepBunch cannot be called, perhaps you are trying to call a function on something which should be contained by the parent deepbunch") 336 | 337 | MappingABC.register(DeepBunch) 338 | 339 | class DeepBunchSingleAssign(DeepBunch): 340 | def __setitem__(self, key, item): 341 | mydict = self._require_dict() 342 | try: 343 | mydict.setdefault(key, item) 344 | return 345 | except TypeError: 346 | raise TypeError("Can't insert {0} into {1} at key {2}".format(item, mydict, key)) 347 | 348 | #def ctree_through(self, obj, **kwargs): 349 | # for k, v in kwargs.iteritems(): 350 | # self[k] = v 351 | # setattr(obj, k, self[k]) 352 | # return 353 | -------------------------------------------------------------------------------- /declarative/bunch/bunch.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | """ 4 | from __future__ import (division, print_function, unicode_literals) 5 | from ..utilities.future_from_2 import str, object, dict, repr_compat, unicode 6 | 7 | from collections.abc import Mapping 8 | from collections.abc import MutableSequence 9 | 10 | import numpy as np 11 | import copy 12 | 13 | def try_remove(d, o): 14 | try: 15 | d.remove(o) 16 | except ValueError: 17 | pass 18 | 19 | 20 | def gen_func(mname): 21 | def func(self, *args, **kwargs): 22 | return getattr(self._mydict, mname)(*args, **kwargs) 23 | orig_func = getattr(dict, mname) 24 | if orig_func is None: 25 | return 26 | func.__name__ = orig_func.__name__ 27 | func.__doc__ = orig_func.__doc__ 28 | return func 29 | 30 | 31 | class Bunch(object): 32 | """ 33 | Cookbook method for creating bunches 34 | 35 | Often we want to just collect a bunch of stuff together, naming each 36 | item of the bunch; a dictionary's OK for that, but a small do-nothing 37 | class is even handier, and prettier to use. Whenever you want to 38 | group a few variables: 39 | 40 | >>> point = Bunch(datum=2, squared=4, coord=12) 41 | >>> point.datum 42 | 43 | taken from matplotlib's cbook.py 44 | 45 | By: Alex Martelli 46 | From: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52308 47 | """ 48 | def __init__(self, inner_dict = None, *args, **kwds): 49 | if inner_dict is None or args or kwds: 50 | if args: 51 | _mydict = dict(inner_dict, *args, **kwds) 52 | else: 53 | _mydict = dict(**kwds) 54 | else: 55 | _mydict = inner_dict 56 | self.__dict__['_mydict'] = _mydict 57 | return 58 | 59 | @classmethod 60 | def as_bunch(cls, data): 61 | if isinstance(data, cls): 62 | return data 63 | return cls(data) 64 | 65 | @repr_compat 66 | def __repr__(self): 67 | keys = list(self._mydict.keys()) 68 | return '{0}({1})'.format( 69 | self.__class__.__name__, 70 | ', \n '.join([''.join((str(k), '=', repr(self._mydict[k]))) for k in keys]) 71 | ) 72 | 73 | def _repr_pretty_(self, p, cycle): 74 | if cycle: 75 | p.text('Bunch()') 76 | else: 77 | with p.group(4, 'Bunch(', ')'): 78 | first = True 79 | for k, v in sorted(self._mydict.items()): 80 | if not first: 81 | p.text(',') 82 | p.breakable() 83 | else: 84 | p.breakable() 85 | first = False 86 | if isinstance(k, str): 87 | p.text(k) 88 | else: 89 | p.pretty(k) 90 | p.text(' = ') 91 | p.pretty(v) 92 | if not first: 93 | p.text(',') 94 | p.breakable() 95 | return 96 | 97 | def __dir__(self): 98 | items = [k for k in self._mydict.keys() if isinstance(k, (str, unicode))] 99 | #items += dir(super(Bunch, self)) 100 | return items 101 | 102 | def __getitem__(self, key): 103 | if isinstance(key, (slice, np.ndarray, MutableSequence)): 104 | rebuild = dict() 105 | for vkey, val in self._mydict.items(): 106 | if isinstance(val, np.ndarray): 107 | val = val[key] 108 | rebuild[vkey] = val 109 | if not rebuild: 110 | raise RuntimeError("Not holding arrays to index by {0}".format(key)) 111 | return Bunch(rebuild) 112 | else: 113 | return self._mydict[key] 114 | 115 | def domain_sort(self, key): 116 | argsort = np.argsort(self[key]) 117 | return self[argsort] 118 | 119 | def __getattr__(self, key): 120 | #item = super(Bunch, self).__getattribute__(key) 121 | try: 122 | item = self._mydict[key] 123 | except KeyError as E: 124 | raise AttributeError(E) 125 | if type(item) is dict: 126 | return self.__class__(item) 127 | return item 128 | 129 | def __setattr__(self, key, item): 130 | self._mydict[key] = item 131 | return 132 | 133 | def __delattr__(self, key): 134 | del self._mydict[key] 135 | 136 | def __deepcopy__(self, memo): 137 | return self.__class__(copy.deepcopy(self._mydict, memo)) 138 | 139 | def copy(self): 140 | return self.__class__(self._mydict.copy()) 141 | 142 | __contains__ = gen_func('__contains__') 143 | __eq__ = gen_func('__eq__') 144 | __format__ = gen_func('__format__') 145 | __ge__ = gen_func('__ge__') 146 | __gt__ = gen_func('__gt__') 147 | __iter__ = gen_func('__iter__') 148 | __le__ = gen_func('__le__') 149 | __len__ = gen_func('__len__') 150 | __lt__ = gen_func('__lt__') 151 | __ne__ = gen_func('__ne__') 152 | __setitem__ = gen_func('__setitem__') 153 | __sizeof__ = gen_func('__sizeof__') 154 | __str__ = gen_func('__str__') 155 | clear = gen_func('clear') 156 | fromkeys = gen_func('fromkeys') 157 | get = gen_func('get') 158 | items = gen_func('items') 159 | keys = gen_func('keys') 160 | pop = gen_func('pop') 161 | popitem = gen_func('popitem') 162 | setdefault = gen_func('setdefault') 163 | update = gen_func('update') 164 | values = gen_func('values') 165 | 166 | 167 | class WriteCheckBunch(object): 168 | def __setitem__(self, key, item): 169 | prev = self._mydict.setdefault(key, item) 170 | if prev != item: 171 | raise RuntimeError("Inconsistent Data") 172 | return 173 | 174 | def __setattr__(self, key, item): 175 | prev = self._mydict.setdefault(key, item) 176 | if prev != item: 177 | raise RuntimeError("Inconsistent Data") 178 | return 179 | 180 | 181 | class FrozenBunch(Bunch): 182 | """ 183 | """ 184 | def __init__(self, inner_dict = None, hash_ignore = (), *args, **kwds): 185 | if inner_dict is None or args or kwds: 186 | if args: 187 | _mydict = dict(inner_dict, *args, **kwds) 188 | else: 189 | _mydict = dict(**kwds) 190 | else: 191 | _mydict = dict(inner_dict) 192 | self.__dict__['hash_ignore'] = set(hash_ignore) 193 | self.__dict__['_mydict'] = _mydict 194 | return 195 | 196 | @classmethod 197 | def as_bunch(cls, data): 198 | if isinstance(data, cls): 199 | return data 200 | return cls(data) 201 | 202 | def __hash__(self): 203 | try: 204 | return self.__dict__['__hash'] 205 | except KeyError: 206 | pass 207 | d2 = dict(self._mydict) 208 | for k in self.hash_ignore: 209 | d2.pop(k) 210 | l = tuple(sorted(d2.items())) 211 | self.__dict__['__hash'] = hash(l) 212 | return self.__dict__['__hash'] 213 | 214 | def __pop__(self, key): 215 | raise RuntimeError("Bunch is Frozen") 216 | 217 | def __popitem__(self, key): 218 | raise RuntimeError("Bunch is Frozen") 219 | 220 | def __clear__(self, key): 221 | raise RuntimeError("Bunch is Frozen") 222 | 223 | def __setitem__(self, key, item): 224 | raise RuntimeError("Bunch is Frozen") 225 | 226 | def __setattr__(self, key, item): 227 | raise RuntimeError("Bunch is Frozen") 228 | 229 | def _insertion_hack(self, key, item): 230 | """ 231 | Allows one to insert an item even after construction. This violates the 232 | "frozen" immutability property, but allows constructing FrozenBunch's 233 | that link to each other. This should only be done immediately after 234 | construction, before hash is ever called 235 | """ 236 | self._mydict[key] = item 237 | 238 | def __delattr__(self, key): 239 | raise RuntimeError("Bunch is Frozen") 240 | 241 | def __deepcopy__(self, memo): 242 | return self.__class__(copy.deepcopy(self._mydict, memo)) 243 | 244 | 245 | class HookBunch(Bunch): 246 | 247 | def __init__( 248 | self, 249 | inner_dict = None, 250 | insert_hook = None, 251 | replace_hook = None, 252 | delete_hook = None, 253 | *args, 254 | **kwds 255 | ): 256 | super(HookBunch, self).__init__(inner_dict = inner_dict, *args, **kwds) 257 | self.__dict__['insert_hook'] = insert_hook 258 | self.__dict__['replace_hook'] = replace_hook 259 | self.__dict__['delete_hook'] = delete_hook 260 | return 261 | 262 | def __getattr__(self, key): 263 | #item = super(Bunch, self).__getattribute__(key) 264 | try: 265 | item = self[key] 266 | except KeyError as E: 267 | raise AttributeError(E) 268 | if type(item) is dict: 269 | return self.__class__(item) 270 | return item 271 | 272 | def __setattr__(self, key, item): 273 | self[key] = item 274 | return 275 | 276 | def __delattr__(self, key): 277 | del self[key] 278 | 279 | def __setitem__(self, key, item): 280 | try: 281 | prev = self._mydict[key] 282 | except KeyError: 283 | if self.insert_hook is None: 284 | raise RuntimeError("Insertion not allowed (hook not defined).") 285 | self.insert_hook(key, item) 286 | self._mydict[key] = item 287 | else: 288 | #triggers without exception 289 | if prev is not item: 290 | if self.replace_hook is None: 291 | raise RuntimeError("Replacement not allowed (hook not defined).") 292 | self.replace_hook(key, prev, item) 293 | self._mydict[key] = item 294 | return 295 | 296 | def __delitem__(self, key): 297 | if self.delete_hook is None: 298 | raise RuntimeError("Deletion not allowed (hook not defined).") 299 | self.delete_hook(key, self._mydict[key]) 300 | del self._mydict[key] 301 | 302 | def clear(self): 303 | if self.delete_hook is None: 304 | raise RuntimeError("Deletion not allowed (hook not defined).") 305 | for k, v in self._mydict.items(): 306 | self.delete_hook(k, v) 307 | self._mydict.clear() 308 | return 309 | 310 | def setdefault(self, key, value): 311 | try: 312 | prev = self._mydict[key] 313 | return prev 314 | except KeyError: 315 | if self.insert_hook is None: 316 | raise RuntimeError("Insertion not allowed (hook not defined)") 317 | self.insert_hook(key , value) 318 | self._mydict[key] = value 319 | return 320 | 321 | def pop(self, key): 322 | if self.delete_hook is None: 323 | raise RuntimeError("Deletion not allowed (hook not defined).") 324 | v = self._mydict[key] 325 | self.delete_hook(key, v) 326 | del self._mydict[key] 327 | return v 328 | 329 | def popitem(self): 330 | if self.delete_hook is None: 331 | raise RuntimeError("Deletion not allowed (hook not defined).") 332 | k, v = self._mydict.popitem() 333 | self.delete_hook(k, v) 334 | return k, v 335 | 336 | def update(self, E = None, **kwargs): 337 | if E is not None: 338 | try: 339 | #test if the keys method exists 340 | keys = E.keys 341 | except AttributeError: 342 | for k, v in E: 343 | self[k] = v 344 | else: 345 | for k in keys(): 346 | self[k] = E[k] 347 | for k, v in kwargs.items(): 348 | self[k] = v 349 | return 350 | 351 | 352 | Mapping.register(Bunch) 353 | Mapping.register(FrozenBunch) 354 | Mapping.register(WriteCheckBunch) 355 | Mapping.register(HookBunch) 356 | -------------------------------------------------------------------------------- /declarative/bunch/hdf_deep_bunch.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Requires h5py to interface 4 | """ 5 | from __future__ import print_function 6 | from ..utilities.future_from_2 import repr_compat 7 | import h5py 8 | import numpy as np 9 | try: 10 | from collections.abc import Mapping as MappingABC 11 | except ImportError: 12 | from collections import Mapping as MappingABC 13 | 14 | 15 | from ..utilities.future_from_2 import str, unicode 16 | from ..utilities.unique import NOARG 17 | from ..utilities.interrupt_delay import DelayedKeyboardInterrupt 18 | 19 | 20 | def hdf_group_is(item): 21 | return isinstance(item, (h5py.File, h5py.Group)) 22 | 23 | 24 | def hdf_data_is(item): 25 | return isinstance(item, (h5py.Dataset, str)) 26 | 27 | 28 | def hdf_assert_grouplike(hdf): 29 | if hdf_data_is(hdf): 30 | raise RuntimeError( 31 | "HDFDeepBunch virtual-path mutated in file to" 32 | "become a dataset, python can't deal with this" 33 | "dataset creation should happen before virtual paths" 34 | "can be created. Dataset: {0}".format(hdf) 35 | ) 36 | return 37 | 38 | 39 | class HDFDeepBunch(object): 40 | """ 41 | """ 42 | __slots__ = ('_hdf', '_vpath', '_overwrite') 43 | 44 | def __init__( 45 | self, 46 | hdf = None, 47 | writeable = False, 48 | overwrite = False, 49 | _vpath = None, 50 | ): 51 | if isinstance(hdf, str): 52 | if writeable: 53 | hdf = h5py.File(hdf, 'a') 54 | if _vpath is None: 55 | _vpath = True 56 | else: 57 | hdf = h5py.File(hdf, 'r') 58 | if _vpath is None: 59 | _vpath = False 60 | else: 61 | if writeable: 62 | if _vpath is None: 63 | _vpath = True 64 | else: 65 | if _vpath is None: 66 | _vpath = False 67 | 68 | #expected to be an HDF File object 69 | if hdf.file.mode not in ('r+', 'a', 'a+', 'w', 'w+'): 70 | if _vpath is not None and _vpath is not False: 71 | raise RuntimeError("Can't open file for writing, virtual paths should not be used") 72 | assert(_vpath is not None) 73 | 74 | #access through super is necessary because of the override on __setattr__ can't see these slots 75 | super(HDFDeepBunch, self).__setattr__('_hdf', hdf) 76 | super(HDFDeepBunch, self).__setattr__('_overwrite', overwrite) 77 | 78 | if _vpath is True: 79 | _vpath = () 80 | elif _vpath is not False: 81 | _vpath = tuple(_vpath) 82 | #self.__dict__['_vpath'] = _vpath 83 | super(HDFDeepBunch, self).__setattr__('_vpath', _vpath) 84 | return 85 | 86 | def _resolve_hdf(self): 87 | """ 88 | Returns the hdf object stored at the currently-referenced group. 89 | If the current reference is virtual, then the hdf is indexed and may throw if the groups do not exist. 90 | """ 91 | if not self._vpath: 92 | return self._hdf 93 | try: 94 | hdf = self._hdf 95 | for idx in range(len(self._vpath)): 96 | gname = self._vpath[idx] 97 | hdf = hdf[gname] 98 | hdf_assert_grouplike(hdf) 99 | except KeyError: 100 | if idx != 0: 101 | self._hdf = hdf 102 | self._vpath = self._vpath[idx:] 103 | return None 104 | self._hdf = hdf 105 | self._vpath = () 106 | return hdf 107 | 108 | @property 109 | def hdf(self): 110 | return self._resolve_hdf() 111 | 112 | @property 113 | def overwrite(self): 114 | return self.__class__( 115 | hdf = self._hdf, 116 | _vpath = self._vpath, 117 | overwrite = True, 118 | ) 119 | 120 | @property 121 | def safewrite(self): 122 | return self.__class__( 123 | hdf = self._hdf, 124 | _vpath = self._vpath, 125 | overwrite = False, 126 | ) 127 | 128 | def _require_hdf(self): 129 | if not self._vpath: 130 | return self._hdf 131 | hdf = self._hdf 132 | for gname in self._vpath: 133 | hdf = hdf.require_group(gname) 134 | hdf_assert_grouplike(hdf) 135 | self._vpath = () 136 | self._hdf = hdf 137 | return hdf 138 | 139 | def __getitem__(self, key): 140 | hdf = self._resolve_hdf() 141 | if hdf is None: 142 | #TODO, better error message when _vpath is False 143 | if self._vpath is False: 144 | raise RuntimeError("HDFDeepBunch not set up for virtual paths, groups must exist in the file to access using __getitem__ / '[]'") 145 | return self.__class__( 146 | hdf = self._hdf, 147 | _vpath = self._vpath + (key,), 148 | overwrite = self._overwrite, 149 | ) 150 | try: 151 | item = hdf[key] 152 | if hdf_group_is(item): 153 | return self.__class__( 154 | hdf = item, 155 | _vpath = self._vpath, 156 | overwrite = self._overwrite, 157 | ) 158 | elif isinstance(item, (h5py.Dataset)): 159 | arr = np.asarray(item) 160 | if item.dtype.kind == 'V': 161 | return arr 162 | if arr.shape == (): 163 | return arr.item() 164 | return arr 165 | if item == '': 166 | return None 167 | return item 168 | except KeyError as E: 169 | if self._vpath is not False: 170 | return self.__class__( 171 | hdf = self._hdf, 172 | _vpath = self._vpath + (key,), 173 | overwrite = self._overwrite, 174 | ) 175 | if str(E).lower().find('object not found') != -1: 176 | raise KeyError("key '{0}' not found in {1}".format(key, self)) 177 | raise 178 | 179 | def __getattr__(self, key): 180 | try: 181 | return self.__getitem__(key) 182 | except KeyError: 183 | raise AttributeError("'{1}' not in {0}".format(self, key)) 184 | 185 | def __setitem__(self, key, item): 186 | hdf = self._require_hdf() 187 | try: 188 | if item is None: 189 | hdf[key] = '' 190 | else: 191 | hdf[key] = item 192 | return 193 | except TypeError: 194 | #print((item, type(item))) 195 | raise TypeError("Can't insert {0} into {1} at key {2}".format(item, hdf, key)) 196 | except (RuntimeError, ValueError) as E: 197 | if str(E).lower().find('name already exists') != -1 and self._overwrite: 198 | del hdf[key] 199 | hdf[key] = item 200 | else: 201 | raise TypeError("Can't insert {0} into {1} at key {2} error: {3}".format(item, hdf, key, E)) 202 | 203 | def __setattr__(self, key, item): 204 | if key in self.__slots__: 205 | return super(HDFDeepBunch, self).__setattr__(key, item) 206 | return self.__setitem__(key, item) 207 | 208 | def update_recursive( 209 | self, 210 | data_dict, 211 | groups_overwrite = False, 212 | hdf_internal_link = False, 213 | hdf_external_link = False, 214 | hdf_copy = False, 215 | ): 216 | """ 217 | Provide a recursive dictionary or collections.Mapping compatible object as the first argument. This dictionary is "injected" into the hdf file. 218 | Sub-mappings become groups and non-mappings become data arrays. 219 | 220 | If the bunch is in overwrite-mode, then data may be overwritten with new data or groups. 221 | 222 | If the groups_overwrite is specified, then even groups may be overwritten 223 | 224 | If the dictionary provided contains some elements which are themselves HDF Files, Groups or other HDFDeepBunch objects, then the keywords 225 | hdf_internal_link = False 226 | hdf_external_link = False 227 | hdf_copy = False 228 | are used to determine if links should be used or if the data should be copied. 229 | hdf_internal_link acts if the referenced file is the same as the one receiving the update_from_dict_recursive call. 230 | Otherwise hdf_external_link will create a file link (currently not implemented and throws NotImplementedError). 231 | If hdf_copy is specified and the appropriate link option is false, then the hdf groups are copied. 232 | """ 233 | self._require_hdf() 234 | def apply_hdf(subref, key, hdf): 235 | refhdf = subref._require_hdf() 236 | try: 237 | subval = refhdf[key] 238 | if hdf_group_is(subval): 239 | if not groups_overwrite: 240 | raise RuntimeError("Won't Overwrite groups") 241 | else: 242 | del refhdf[key] 243 | else: 244 | if not self._overwrite: 245 | raise RuntimeError("Won't Overwrite data") 246 | else: 247 | del refhdf[key] 248 | except KeyError: 249 | pass 250 | if (refhdf.file == hdf.file): 251 | if hdf_internal_link: 252 | refhdf[key] = hdf 253 | elif hdf_copy: 254 | refhdf.copy(hdf, key) 255 | else: 256 | raise RuntimeError("Object provided is an internal HDF group or bunch, but neither hdf_copy nor hdf_internal_link specified") 257 | else: 258 | if hdf_external_link: 259 | raise NotImplementedError() 260 | elif hdf_copy: 261 | refhdf.copy(hdf, key) 262 | else: 263 | raise RuntimeError("Object provided is an external HDF group or bunch, but neither hdf_copy nor hdf_external_link specified") 264 | 265 | def recursive_action(subref, data_dict): 266 | for key, value in list(data_dict.items()): 267 | if isinstance(value, MappingABC): 268 | if groups_overwrite: 269 | self.require_deleted(key) 270 | subsubref = subref[key] 271 | else: 272 | subsubref = subref[key] 273 | if not isinstance(subsubref, HDFDeepBunch): 274 | if self._overwrite: 275 | del subref[key] 276 | subsubref = subref[key] 277 | else: 278 | raise RuntimeError("Won't Overwrite data") 279 | recursive_action(subsubref, value) 280 | elif hdf_group_is(value): 281 | apply_hdf(subref, key, value) 282 | elif isinstance(value, HDFDeepBunch): 283 | value = value.hdf 284 | apply_hdf(subref, key, value) 285 | else: 286 | subref[key] = value 287 | with DelayedKeyboardInterrupt(): 288 | recursive_action(self, data_dict) 289 | return 290 | 291 | def __delitem__(self, key): 292 | hdf = self._resolve_hdf() 293 | if hdf is None: 294 | return 295 | del self._hdf[key] 296 | 297 | def __delattr__(self, key): 298 | return self.__delitem__(key) 299 | 300 | def get(self, key, default = NOARG): 301 | try: 302 | return self[key] 303 | except KeyError: 304 | if default is not NOARG: 305 | return default 306 | raise 307 | 308 | def setdefault(self, key, default): 309 | try: 310 | return self[key] 311 | except KeyError: 312 | self[key] = default 313 | return default 314 | 315 | def __contains__(self, key): 316 | hdf = self._resolve_hdf() 317 | if hdf is None: 318 | return False 319 | return key in hdf 320 | 321 | def has_key(self, key): 322 | hdf = self._resolve_hdf() 323 | if hdf is None: 324 | return False 325 | return key in hdf 326 | 327 | def require_deleted(self, key): 328 | hdf = self._resolve_hdf() 329 | if hdf is None: 330 | return 331 | try: 332 | del self._hdf[key] 333 | except KeyError: 334 | pass 335 | return 336 | 337 | @classmethod 338 | def ensure_wrap(cls, item): 339 | if isinstance(item, cls): 340 | return item 341 | return cls(item) 342 | 343 | def __dir__(self): 344 | items = list(k for k in self._hdf.keys() if isinstance(k, (str, unicode))) 345 | items += ['overwrite', 'safewrite', 'hdf'] 346 | #items.sort() 347 | #items += dir(super(Bunch, self)) 348 | return items 349 | 350 | @repr_compat 351 | def __repr__(self): 352 | return ( 353 | '{0}({1}, overwrite={2}, vpath={3})' 354 | ).format( 355 | self.__class__.__name__, 356 | self._hdf, 357 | self._overwrite, 358 | self._vpath, 359 | ) 360 | 361 | def __iter__(self): 362 | hdf = self._resolve_hdf() 363 | if hdf is None: 364 | return iter(()) 365 | return iter(hdf) 366 | 367 | def __len__(self): 368 | hdf = self._resolve_hdf() 369 | if hdf is None: 370 | return 0 371 | return len(hdf) 372 | 373 | def clear(self): 374 | hdf = self._resolve_hdf() 375 | if hdf is None: 376 | return 377 | for key in list(hdf.keys()): 378 | del hdf[key] 379 | return 380 | 381 | def keys(self): 382 | hdf = self._resolve_hdf() 383 | if hdf is None: 384 | return iter(()) 385 | return iter(list(hdf.keys())) 386 | 387 | def values(self): 388 | hdf = self._resolve_hdf() 389 | if hdf is None: 390 | return 391 | for key in list(hdf.keys()): 392 | yield self[key] 393 | return 394 | 395 | def items(self): 396 | hdf = self._resolve_hdf() 397 | if hdf is None: 398 | return 399 | for key in list(hdf.keys()): 400 | yield key, self[key] 401 | return 402 | 403 | MappingABC.register(HDFDeepBunch) 404 | -------------------------------------------------------------------------------- /declarative/properties/memoized.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | """ 4 | from __future__ import ( 5 | division, 6 | print_function, 7 | absolute_import, 8 | ) 9 | #from builtins import object 10 | 11 | 12 | from functools import partial 13 | 14 | from ..utilities.unique import ( 15 | NOARG, 16 | unique_generator 17 | ) 18 | 19 | from .utilities import ( 20 | raise_attrerror_from_property, 21 | raise_msg_from_property, 22 | try_name_file_line, 23 | ) 24 | 25 | from .bases import ( 26 | InnerException, 27 | PropertyTransforming, 28 | ) 29 | 30 | _UNIQUE_local = unique_generator() 31 | 32 | 33 | class AccessError(Exception): 34 | pass 35 | 36 | 37 | class ClassMemoizedDescriptor(object): 38 | 39 | """ 40 | Works like a combination of :obj:`property` and :obj:`classmethod` as well as :obj:`~.memoized_property` 41 | """ 42 | def __init__(self, fget, doc=None): 43 | self.fget = fget 44 | self.__doc__ = doc or fget.__doc__ 45 | self.__name__ = fget.__name__ 46 | 47 | def __get__(self, obj, cls): 48 | if obj is None: 49 | return self 50 | result = self.fget(cls) 51 | setattr(cls, self.__name__, result) 52 | return result 53 | 54 | 55 | memoized_class_property = ClassMemoizedDescriptor 56 | 57 | 58 | class MemoizedDescriptor(object): 59 | """ 60 | wraps a member function just as :obj:`property` but saves its value after evaluation 61 | (and is thus only evaluated once) 62 | """ 63 | _declarative_instantiation = False 64 | transforming = True 65 | simple_delete = False 66 | 67 | def __init__( 68 | self, 69 | fget, 70 | name = None, 71 | doc = None, 72 | declarative = None, 73 | transforming = None, 74 | simple_delete = False, 75 | original_callname = None, 76 | ): 77 | self.fget = fget 78 | if name is None: 79 | self.__name__ = fget.__name__ 80 | else: 81 | self.__name__ = name 82 | if doc is None: 83 | self.__doc__ = fget.__doc__ 84 | else: 85 | self.__doc__ = doc 86 | if declarative is not None: 87 | self._declarative_instantiation = declarative 88 | if transforming is not None: 89 | self.transforming = transforming 90 | if simple_delete: 91 | self.simple_delete = simple_delete 92 | if original_callname is not None: 93 | self.original_callname = original_callname 94 | else: 95 | self.original_callname = self.__class__.__name__ 96 | return 97 | 98 | def __get__(self, obj, cls): 99 | if obj is None: 100 | return self 101 | result = obj.__dict__.get(self.__name__, _UNIQUE_local) 102 | if result is _UNIQUE_local: 103 | #bd = obj.__boot_dict__ 104 | bd = getattr(obj, '__boot_dict__', None) 105 | if bd is not None: 106 | result = bd.pop(self.__name__, _UNIQUE_local) 107 | if not bd: 108 | del obj.__boot_dict__ 109 | if result is _UNIQUE_local: 110 | try: 111 | result = self.fget(obj) 112 | except TypeError as e: 113 | raise_msg_from_property( 114 | ("Property attribute {name} of {orepr} accessed with no initial value. Needs an initial value, or" 115 | " to be declared with a default argument at file:" 116 | "\n{filename}" 117 | "\nline {lineno}."), 118 | AccessError, self, obj, e, 119 | if_from_file = __file__, 120 | **try_name_file_line(self.fget) 121 | ) 122 | raise 123 | except AttributeError as e: 124 | raise_attrerror_from_property(self, obj, e) 125 | else: 126 | try: 127 | result = self.fget(obj, result) 128 | except TypeError as e: 129 | raise_msg_from_property( 130 | ("Property attribute {name} of {orepr} accessed with an initial value. Needs no initial value, or" 131 | "to be declared taking an argument at file:" 132 | "\n{filename}" 133 | "\nline {lineno}."), 134 | AccessError, self, obj, e, 135 | if_from_file = __file__, 136 | **try_name_file_line(self.fget) 137 | ) 138 | raise 139 | except AttributeError as e: 140 | raise_attrerror_from_property(self, obj, e) 141 | 142 | if __debug__: 143 | if result is NOARG: 144 | raise InnerException("Return result was NOARG (usu)") 145 | 146 | if self.transforming and isinstance(result, PropertyTransforming): 147 | try: 148 | result = result.construct( 149 | parent = obj, 150 | name = self.__name__, 151 | ) 152 | except Exception as e: 153 | raise 154 | raise_msg_from_property(( 155 | "Property attribute {name} of {orepr} failed constructing a Transforming object"), 156 | RuntimeError, self, obj, e, 157 | ) 158 | 159 | #print("SET Value for attr ({0}) in {1}".format(self.__name__, id(obj))) 160 | obj.__dict__[self.__name__] = result 161 | return result 162 | 163 | def __set__(self, obj, value): 164 | oldvalue = obj.__dict__.get(self.__name__, _UNIQUE_local) 165 | if oldvalue is _UNIQUE_local: 166 | bd = getattr(obj, '__boot_dict__', None) 167 | if bd is not None: 168 | oldv = bd.setdefault(self.__name__, value) 169 | if oldv is not value: 170 | d = dict( 171 | name = self.__name__, 172 | ) 173 | try: 174 | d['orepr'] = repr(obj) 175 | except Exception: 176 | d['orepr'] = ''.format(obj.__class__.__name__) 177 | raise RuntimeError( 178 | "Initial set for attribute {name} on object {orepr} must be unique".format(**d) 179 | ) 180 | else: 181 | obj.__boot_dict__ = {self.__name__ : value} 182 | else: 183 | #the new value shows up in the object BEFORE the exchanger is called 184 | if oldvalue is value: 185 | return 186 | obj.__dict__[self.__name__] = value 187 | try: 188 | revalue = self.fget(obj, value, oldvalue) 189 | except TypeError as e: 190 | raise_msg_from_property( 191 | ("Property attribute {name} of {orepr} set, replacing an initial value with a new. Either setting not allowed, or" 192 | "must be declared taking 2 arguments at file:" 193 | "\n{filename}" 194 | "\nline {lineno}."), 195 | AccessError, self, obj, e, 196 | if_from_file = __file__, 197 | **try_name_file_line(self.fget) 198 | ) 199 | raise 200 | if revalue is not NOARG: 201 | obj.__dict__[self.__name__] = revalue 202 | else: 203 | del obj.__dict__[self.__name__] 204 | return 205 | 206 | def __delete__(self, obj): 207 | oldvalue = obj.__dict__.get(self.__name__, _UNIQUE_local) 208 | if oldvalue is _UNIQUE_local: 209 | raise InnerException("Value for attr ({0}) in {1}({2}) never initialized before delete".format(self.__name__, obj, id(obj))) 210 | else: 211 | if self.simple_delete: 212 | del obj.__dict__[self.__name__] 213 | else: 214 | #the new value shows up in the object BEFORE the exchanger is called 215 | del obj.__dict__[self.__name__] 216 | try: 217 | revalue = self.fget(obj, NOARG, oldvalue) 218 | except TypeError as e: 219 | raise_msg_from_property( 220 | ("Property attribute {name} of {orepr} deleted, replacing an initial value with NOARG. Either deleting not allowed, or" 221 | "must be declared taking 2 arguments at file:" 222 | "\n{filename}" 223 | "\nline {lineno}."), 224 | AccessError, self, obj, e, 225 | if_from_file = __file__, 226 | **try_name_file_line(self.fget) 227 | ) 228 | raise 229 | if revalue is not NOARG: 230 | obj.__dict__[self.__name__] = revalue 231 | return 232 | 233 | 234 | class MemoizedDescriptorFNoSet(object): 235 | """ 236 | wraps a member function just as :obj:`property` but saves its value after evaluation 237 | (and is thus only evaluated once) 238 | """ 239 | _declarative_instantiation = False 240 | _force_boot_dict = True 241 | 242 | def __init__( 243 | self, 244 | fget, 245 | name = None, 246 | doc = None, 247 | declarative = None, 248 | original_callname = None, 249 | ): 250 | self.fget = fget 251 | if name is None: 252 | self.__name__ = fget.__name__ 253 | else: 254 | self.__name__ = name 255 | if doc is None: 256 | self.__doc__ = fget.__doc__ 257 | else: 258 | self.__doc__ = doc 259 | if declarative is not None: 260 | self._declarative_instantiation = declarative 261 | if original_callname is not None: 262 | self.original_callname = original_callname 263 | else: 264 | self.original_callname = self.__class__.__name__ 265 | return 266 | 267 | def __get__(self, obj, cls): 268 | if obj is None: 269 | return self 270 | result = obj.__dict__.get(self.__name__, _UNIQUE_local) 271 | if result is _UNIQUE_local: 272 | #bd = obj.__boot_dict__ 273 | bd = getattr(obj, '__boot_dict__', None) 274 | if bd is not None: 275 | result = bd.pop(self.__name__, _UNIQUE_local) 276 | if not bd: 277 | del obj.__boot_dict__ 278 | if result is _UNIQUE_local: 279 | try: 280 | result = self.fget(obj) 281 | except TypeError as e: 282 | raise_msg_from_property( 283 | ("Property attribute {name} of {orepr} accessed with no initial value. Needs an initial value, or" 284 | "to be declared with a default argument at file:" 285 | "\n{filename}" 286 | "\nline {lineno}."), 287 | AccessError, self, obj, e, 288 | if_from_file = __file__, 289 | **try_name_file_line(self.fget) 290 | ) 291 | raise 292 | except AttributeError as e: 293 | raise_attrerror_from_property(self, obj, e) 294 | else: 295 | try: 296 | result = self.fget(obj, result) 297 | except TypeError as e: 298 | raise_msg_from_property( 299 | ("Property attribute {name} of {orepr} accessed with an initial value. Needs no initial value, or" 300 | "to be declared taking an argument at file:" 301 | "\n{filename}" 302 | "\nline {lineno}."), 303 | AccessError, self, obj, e, 304 | if_from_file = __file__, 305 | **try_name_file_line(self.fget) 306 | ) 307 | raise 308 | except AttributeError as e: 309 | raise_attrerror_from_property(self, obj, e) 310 | 311 | if __debug__: 312 | if result is NOARG: 313 | raise InnerException("Return result was NOARG") 314 | #obj.__dict__[self.__name__] = result 315 | 316 | if isinstance(result, PropertyTransforming): 317 | result = result.construct( 318 | parent = obj, 319 | name = self.__name__, 320 | ) 321 | 322 | #use standard (or overloaded! setter since this will assign to __dict__ by default 323 | #when this descriptor object is missing __set__) 324 | setattr(obj, self.__name__, result) 325 | #in case the setattr transformed the object 326 | result = getattr(obj, self.__name__) 327 | return result 328 | 329 | 330 | def mproperty( 331 | __func = None, 332 | original_callname = 'mproperty', 333 | **kwargs 334 | ): 335 | def wrap(func): 336 | desc = MemoizedDescriptor( 337 | func, 338 | original_callname = original_callname, 339 | **kwargs 340 | ) 341 | return desc 342 | if __func is not None: 343 | return wrap(__func) 344 | else: 345 | return wrap 346 | 347 | 348 | def dproperty( 349 | __func = None, 350 | declarative = True, 351 | original_callname = 'dproperty', 352 | **kwargs 353 | ): 354 | return mproperty( 355 | __func = __func, 356 | declarative = declarative, 357 | original_callname = original_callname, 358 | **kwargs 359 | ) 360 | 361 | 362 | def mproperty_plain( 363 | __func = None, 364 | original_callname = 'mproperty_plain', 365 | **kwargs 366 | ): 367 | def wrap(func): 368 | desc = MemoizedDescriptorFNoSet( 369 | func, 370 | original_callname = original_callname, 371 | **kwargs 372 | ) 373 | return desc 374 | if __func is not None: 375 | return wrap(__func) 376 | else: 377 | return wrap 378 | 379 | 380 | def dproperty_plain( 381 | __func = None, 382 | declarative = True, 383 | original_callname = 'dproperty_plain', 384 | **kwargs 385 | ): 386 | return mproperty_plain( 387 | __func = __func, 388 | declarative = declarative, 389 | original_callname = original_callname, 390 | **kwargs 391 | ) 392 | 393 | 394 | if __debug__: 395 | mproperty_fns = mproperty 396 | dproperty_fns = dproperty 397 | else: 398 | mproperty_fns = mproperty_plain 399 | dproperty_fns = dproperty_plain 400 | 401 | 402 | class MemoizeFunction(object): 403 | """cache the return value of a method 404 | 405 | This class is meant to be used as a decorator of methods. The return value 406 | from a given method invocation will be cached on the instance whose method 407 | was invoked. All arguments passed to a method decorated with memoize must 408 | be hashable. 409 | 410 | If a memoized method is invoked directly on its class the result will not 411 | be cached. Instead the method will be invoked like a static method: 412 | class Obj(object): 413 | @memoize 414 | def add_to(self, arg): 415 | return self + arg 416 | Obj.add_to(1) # not enough arguments 417 | Obj.add_to(1, 2) # returns 3, result is not cached 418 | 419 | from Daniel Miller on Active State 420 | http://code.activestate.com/recipes/577452-a-memoize-decorator-for-instance-methods/ 421 | MIT Licenced 422 | """ 423 | 424 | def __init__(self, func): 425 | self.func = func 426 | 427 | def __get__(self, obj, objtype=None): 428 | if obj is None: 429 | return self.func 430 | return partial(self, obj) 431 | 432 | def __call__(self, *args, **kw): 433 | obj = args[0] 434 | try: 435 | cache = obj.__cache 436 | except AttributeError: 437 | cache = obj.__cache = {} 438 | key = (self.func, args[1:], frozenset(list(kw.items()))) 439 | try: 440 | res = cache[key] 441 | except KeyError: 442 | res = cache[key] = self.func(*args, **kw) 443 | return res 444 | 445 | mfunction = MemoizeFunction 446 | -------------------------------------------------------------------------------- /declarative/callbacks/state_booleans.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | """ 4 | from __future__ import (print_function, absolute_import) 5 | from ..utilities.representations import ReprMixin 6 | from ..utilities.unique import unique_generator 7 | from ..properties import mproperty 8 | _debug_loud = False 9 | 10 | 11 | _UNIQUE = unique_generator() 12 | 13 | 14 | class RelayBoolBase(ReprMixin): 15 | """ 16 | Base class for the relay bools and their interfaces. 17 | 18 | All subclasses should implement :obj:`__nonzero__` 19 | 20 | .. automethod:: __init__ 21 | 22 | .. automethod:: register 23 | 24 | .. autoattribute:: is_set 25 | """ 26 | __slots__ = ('callbacks_ontoggle', '_assign_protect') 27 | __repr_slots__ = ('is_set', 'name') 28 | 29 | def __init__(self, name = '', **kwargs): 30 | """ 31 | """ 32 | super(RelayBoolBase, self).__init__(**kwargs) 33 | self._assign_protect = None 34 | self.name = name 35 | self.callbacks_ontoggle = {} 36 | return 37 | 38 | @property 39 | def is_set(self): 40 | """ 41 | Returns T/F status of the bool 42 | """ 43 | return bool(self) 44 | 45 | @property 46 | def value(self): 47 | return bool(self) 48 | 49 | def __nonzero__(self): 50 | return self.__bool__() 51 | 52 | def __bool__(self): 53 | raise NotImplementedError() 54 | 55 | def register( 56 | self, 57 | key = None, 58 | callback = None, 59 | call_immediate = False, 60 | assumed_value = _UNIQUE, 61 | remove = False, 62 | ): 63 | """ 64 | """ 65 | if key is None: 66 | key = callback 67 | if key is None: 68 | raise RuntimeError("Key or Callback must be specified") 69 | if self._assign_protect is not None: 70 | raise RuntimeError("Assign Assigned during assign!") 71 | if not remove: 72 | ocb = self.callbacks_ontoggle.setdefault(key, callback) 73 | if ocb is not callback: 74 | raise RuntimeError( 75 | ("Same key registered to multiple callbacks key: {0}, callback {1}" 76 | ).format(key, callback) 77 | ) 78 | if assumed_value is not _UNIQUE: 79 | if bool(self) != assumed_value: 80 | callback(bool(self)) 81 | elif call_immediate: 82 | callback(bool(self)) 83 | else: 84 | if assumed_value is not _UNIQUE: 85 | if bool(self) != assumed_value: 86 | callback(assumed_value) 87 | self.callbacks_ontoggle.pop(key) 88 | return 89 | 90 | def register_via(self, reg_call, callback, key = None, remove = False, **kwargs): 91 | if key is None: 92 | key = callback 93 | if not remove: 94 | def meta_cb(bstate): 95 | reg_call(callback = callback, remove = not bstate, **kwargs) 96 | self.register( 97 | key = key, 98 | callback = meta_cb, 99 | assumed_value = False, 100 | ) 101 | else: 102 | self.register( 103 | key = key, 104 | assumed_value = False, 105 | remove = True, 106 | ) 107 | return 108 | 109 | @mproperty 110 | def notted(self): 111 | return RelayBoolNot(self) 112 | 113 | 114 | class RelayBool(RelayBoolBase): 115 | """ 116 | This is a raw Relay Bool value, explicitely settable 117 | 118 | .. automethod:: __init__ 119 | 120 | .. automethod:: assign 121 | 122 | .. automethod:: toggle 123 | """ 124 | __slots__ = ('state',) 125 | 126 | def __init__(self, initial_state, **kwargs): 127 | """ 128 | :param initial_state: initial boolean state held 129 | """ 130 | super(RelayBool, self).__init__(**kwargs) 131 | self.state = initial_state 132 | return 133 | 134 | def __bool__(self): 135 | return self.state 136 | 137 | def assign(self, value): 138 | return self.put(value) 139 | 140 | def put(self, value): 141 | """ 142 | Set the value held by this instance 143 | """ 144 | #python has no xor! 145 | if (value and not self.state) or (not value and self.state): 146 | self.state = not self.state 147 | #print repr(self.callbacks_ontoggle) 148 | if self._assign_protect is not None: 149 | raise RuntimeError("Assign Assigned during assign!") 150 | self._assign_protect = self.state 151 | try: 152 | for callback in list(self.callbacks_ontoggle.values()): 153 | callback(self.state) 154 | finally: 155 | self._assign_protect = None 156 | return 157 | 158 | def put_valid(self, val): 159 | return self.put(val) 160 | 161 | def put_exclude_cb(self, value, key): 162 | """ 163 | Set the value held by this instance 164 | """ 165 | #python has no xor! 166 | if (value and not self.state) or (not value and self.state): 167 | self.state = not self.state 168 | #print repr(self.callbacks_ontoggle) 169 | if self._assign_protect is not None: 170 | raise RuntimeError("Assign Assigned during assign!") 171 | self._assign_protect = self.state 172 | try: 173 | for cb_key, callback in list(self.callbacks_ontoggle.items()): 174 | if key != cb_key: 175 | callback(self.state) 176 | finally: 177 | self._assign_protect = None 178 | return 179 | 180 | def put_valid_exclude_cb(self, val, key): 181 | return self.put_exclude_cb(val, key) 182 | 183 | @property 184 | def value(self): 185 | return bool(self) 186 | 187 | @value.setter 188 | def value(self, val): 189 | return self.put(val) 190 | 191 | def assign_on(self): 192 | """ 193 | Set the value held by this instance 194 | """ 195 | #python has no xor! 196 | if not self.state: 197 | self.state = True 198 | #print repr(self.callbacks_ontoggle) 199 | if self._assign_protect is not None: 200 | raise RuntimeError("Assign Assigned during assign!") 201 | self._assign_protect = self.state 202 | try: 203 | for callback in list(self.callbacks_ontoggle.values()): 204 | callback(self.state) 205 | finally: 206 | self._assign_protect = None 207 | return 208 | 209 | def assign_off(self): 210 | """ 211 | Set the value held by this instance 212 | """ 213 | #python has no xor! 214 | if self.state: 215 | self.state = False 216 | #print repr(self.callbacks_ontoggle) 217 | if self._assign_protect is not None: 218 | raise RuntimeError("Assign Assigned during assign!") 219 | self._assign_protect = self.state 220 | try: 221 | for callback in list(self.callbacks_ontoggle.values()): 222 | callback(self.state) 223 | finally: 224 | self._assign_protect = None 225 | return 226 | 227 | def assign_toggle(self): 228 | """ 229 | Toggle the Value held by this instance 230 | """ 231 | self.state = not self.state 232 | #print repr(self.callbacks_ontoggle) 233 | if self._assign_protect is not None: 234 | raise RuntimeError("Assign Assigned during assign!") 235 | self._assign_protect = self.state 236 | try: 237 | for callback in list(self.callbacks_ontoggle.values()): 238 | callback(self.state) 239 | finally: 240 | self._assign_protect = None 241 | return 242 | 243 | 244 | class RelayBoolNot(RelayBoolBase): 245 | """ 246 | Is true iff state_label's state is the state given 247 | 248 | .. automethod:: __init__ 249 | 250 | .. automethod:: register 251 | """ 252 | __slots__ = ('sub_bool',) 253 | 254 | def __init__(self, sub_bool, **kwargs): 255 | """ 256 | :param sub_bool: RelayBool this value takes the negation of 257 | """ 258 | super(RelayBoolNot, self).__init__(**kwargs) 259 | self.sub_bool = sub_bool 260 | return 261 | 262 | def __bool__(self): 263 | return not self.sub_bool 264 | 265 | def _monitor_callback(self, value): 266 | if self._assign_protect is not None: 267 | raise RuntimeError("Assign Assigned during assign!") 268 | self._assign_protect = not self.sub_bool 269 | try: 270 | for callback in list(self.callbacks_ontoggle.values()): 271 | callback(not value) 272 | finally: 273 | self._assign_protect = None 274 | return 275 | 276 | def register( 277 | self, 278 | key = None, 279 | callback = None, 280 | call_immediate = False, 281 | assumed_value = _UNIQUE, 282 | remove = False 283 | ): 284 | """ 285 | Register a function for callback when this bool's state changes. 286 | 287 | :param callback: function to register 288 | :param call_immediate: call as the function is added 289 | :param remove: (False) set to remove a callback 290 | :raises: :exc:`KeyError` on missing callback 291 | 292 | The function should have the signature 293 | 294 | .. function:: callback(bool_state): 295 | 296 | :param bool_state: state of the RelayBool's **new** value 297 | 298 | """ 299 | if not remove: 300 | if not self.callbacks_ontoggle: 301 | self.sub_bool.register(self, self._monitor_callback) 302 | super(RelayBoolNot, self).register( 303 | key = key, 304 | callback = callback, 305 | assumed_value = assumed_value, 306 | call_immediate = call_immediate, 307 | remove = remove 308 | ) 309 | else: 310 | super(RelayBoolNot, self).register( 311 | key = key, 312 | callback = callback, 313 | assumed_value = assumed_value, 314 | call_immediate = call_immediate, 315 | remove = remove 316 | ) 317 | if not self.callbacks_ontoggle: 318 | self.sub_bool.register(self, self._monitor_callback, remove = True) 319 | return 320 | 321 | 322 | class RelayBoolGate(RelayBoolBase, ReprMixin): 323 | """ 324 | Base class to implement the logic needed for syncronous action of logic gates of RelayBools. 325 | 326 | These gates are all based on monitoring sub-bools using a count. Subclasses set the values of 327 | 328 | .. attribute:: _input_not 329 | 330 | .. attribute:: _output_not 331 | 332 | .. automethod:: register 333 | """ 334 | __slots__ = ('monitored_bools_count', 'monitored_bools') 335 | 336 | _input_not = False 337 | _output_not = False 338 | 339 | def __init__(self, relay_bools = (), **kwargs): 340 | """ 341 | :param relay_bools: iterable of Relays Bools to set up initial gate value 342 | """ 343 | super(RelayBoolGate, self).__init__(**kwargs) 344 | #None means monitoring is off 345 | self.monitored_bools_count = None 346 | self.monitored_bools = dict() 347 | iitems = None 348 | try: 349 | iitems = iter(list(relay_bools.items())) 350 | except AttributeError: 351 | pass 352 | if iitems: 353 | for b, name in iitems: 354 | self.monitored_bools[b] = [name] 355 | else: 356 | for b in relay_bools: 357 | self.monitored_bools[b] = [None] 358 | 359 | #self._monitor_value_start() 360 | 361 | def __contains__(self, subbool): 362 | return subbool in self.monitored_bools 363 | 364 | def __bool__(self): 365 | if not self._output_not: 366 | if self.monitored_bools_count is None: 367 | return bool(self._compute_count()) 368 | else: 369 | return bool(self.monitored_bools_count) 370 | else: 371 | if self.monitored_bools_count is None: 372 | return (not self._compute_count()) 373 | else: 374 | return (not self.monitored_bools_count) 375 | #can't reach 376 | raise 377 | 378 | def _compute_count(self): 379 | if not self._input_not: 380 | return sum(bool(monbool) for monbool in self.monitored_bools) 381 | else: 382 | return sum((not monbool) for monbool in self.monitored_bools) 383 | 384 | def bool_register(self, relay_bool, name = None, remove = False): 385 | if remove: 386 | return self.bool_unregister(relay_bool) 387 | name_list = self.monitored_bools.setdefault(relay_bool, []) 388 | if name_list: 389 | #Already a relay bool, just adding a name to it 390 | name_list.append(name) 391 | return 392 | #wasn't already inserted, so add the name and register it through the callbacks 393 | name_list.append(name) 394 | if self.monitored_bools_count is not None: 395 | relay_bool.register(self, self._monitor_callback_deb(relay_bool, name)) 396 | if not self._input_not: 397 | add_val = bool(relay_bool) 398 | else: 399 | add_val = not bool(relay_bool) 400 | if add_val: 401 | self.monitored_bools_count += 1 402 | if self.monitored_bools_count == 1: 403 | if self._assign_protect is not None: 404 | raise RuntimeError("Assign Assigned during assign!") 405 | self._assign_protect = bool(self) 406 | try: 407 | for callback in list(self.callbacks_ontoggle.values()): 408 | callback(not self._output_not) 409 | finally: 410 | self._assign_protect = None 411 | return 412 | 413 | def bool_unregister(self, relay_bool, name = None): 414 | if relay_bool not in self.monitored_bools: 415 | raise RuntimeError("Missing Bool Registry!") 416 | name_list = self.monitored_bools[relay_bool] 417 | name_list.remove(name) 418 | if name_list: 419 | #still registered bools for this name, so don't delete it 420 | return 421 | del self.monitored_bools[relay_bool] 422 | if self.monitored_bools_count is not None: 423 | relay_bool.register(self, None, remove = True) 424 | if not self._input_not: 425 | sub_val = bool(relay_bool) 426 | else: 427 | sub_val = not bool(relay_bool) 428 | if sub_val: 429 | self.monitored_bools_count -= 1 430 | if self.monitored_bools_count == 0: 431 | if self._assign_protect is not None: 432 | raise RuntimeError("Assign Assigned during assign!") 433 | self._assign_protect = bool(self) 434 | try: 435 | for callback in list(self.callbacks_ontoggle.values()): 436 | callback(self._output_not) 437 | finally: 438 | self._assign_protect = None 439 | return 440 | 441 | def _monitor_callback_deb(self, rbool, bname): 442 | if __debug__ and _debug_loud: 443 | print(("has added {0} - {1}, {2}".format( 444 | repr(self), 445 | repr(rbool), 446 | bname, 447 | ))) 448 | def deb(value): 449 | print(("{0} at count {cnt}, {1}, {2}, {3}".format( 450 | repr(self), 451 | repr(rbool), 452 | bname, 453 | value, 454 | cnt = self.monitored_bools_count, 455 | ))) 456 | self._monitor_callback(value) 457 | return deb 458 | else: 459 | return self._monitor_callback 460 | 461 | def _monitor_callback(self, value): 462 | #xor with the input not 463 | if (self._input_not and not value) or (not self._input_not and value): 464 | self.monitored_bools_count += 1 465 | if self.monitored_bools_count == 1: 466 | #no longer are all of them on 467 | if self._assign_protect is not None: 468 | raise RuntimeError(self) 469 | self._assign_protect = bool(self) 470 | try: 471 | for callback in list(self.callbacks_ontoggle.values()): 472 | callback(not self._output_not) 473 | finally: 474 | self._assign_protect = None 475 | else: 476 | self.monitored_bools_count -= 1 477 | if self.monitored_bools_count == 0: 478 | #no longer are all of them off 479 | if self._assign_protect is not None: 480 | raise RuntimeError() 481 | self._assign_protect = bool(self) 482 | try: 483 | for callback in list(self.callbacks_ontoggle.values()): 484 | callback(self._output_not) 485 | finally: 486 | self._assign_protect = None 487 | return 488 | 489 | def _monitor_value_start(self): 490 | """ 491 | """ 492 | assert(self.monitored_bools_count is None) 493 | self.monitored_bools_count = 0 494 | for monbool, bname in list(self.monitored_bools.items()): 495 | #monbool.register(self, self._monitor_callback) 496 | monbool.register(self, self._monitor_callback_deb(monbool, bname)) 497 | #add the xor 498 | self.monitored_bools_count += ((not self._input_not and bool(monbool)) or (self._input_not and not bool(monbool))) 499 | 500 | return 501 | 502 | def _monitor_value_stop(self): 503 | """ 504 | """ 505 | assert(self.monitored_bools_count is not None) 506 | for monbool in self.monitored_bools: 507 | monbool.register(key = self, callback = self._monitor_callback, remove = True) 508 | self.monitored_bools_count = None 509 | return 510 | 511 | def register( 512 | self, 513 | key = None, 514 | callback = None, 515 | assumed_value = _UNIQUE, 516 | call_immediate = False, 517 | remove = False 518 | ): 519 | """ 520 | Register a function for callback when this bool's state changes. 521 | 522 | :param callback: function to register 523 | :param call_immediate: call as the function is added 524 | :param remove: (False) set to remove a callback 525 | :raises: :exc:`KeyError` on missing callback 526 | 527 | The function should have the signature 528 | 529 | .. function:: callback(bool_state): 530 | 531 | :param bool_state: state of the RelayBool's **new** value 532 | 533 | """ 534 | if not remove: 535 | try: 536 | super(RelayBoolGate, self).register( 537 | key = key, 538 | callback = callback, 539 | call_immediate = call_immediate, 540 | assumed_value = assumed_value, 541 | ) 542 | except: 543 | raise 544 | else: 545 | if self.monitored_bools_count is None: 546 | self._monitor_value_start() 547 | if call_immediate: 548 | callback(bool(self)) 549 | else: 550 | try: 551 | super(RelayBoolGate, self).register( 552 | key = key, 553 | callback = callback, 554 | assumed_value = assumed_value, 555 | remove = True 556 | ) 557 | except: 558 | raise 559 | else: 560 | if ( 561 | (not self.callbacks_ontoggle) and 562 | (self.monitored_bools_count is not None) 563 | ): 564 | self._monitor_value_stop() 565 | return 566 | 567 | 568 | class RelayBoolAny(RelayBoolGate): 569 | """ 570 | Or/Any gate for a collection of RelayBools 571 | """ 572 | _input_not = False 573 | _output_not = False 574 | 575 | 576 | class RelayBoolAll(RelayBoolGate): 577 | """ 578 | And/All gate for a collection of RelayBools 579 | """ 580 | _input_not = True 581 | _output_not = True 582 | 583 | 584 | class RelayBoolNotAny(RelayBoolGate): 585 | """ 586 | Nor/Not Any gate for a collection of RelayBools 587 | """ 588 | _input_not = False 589 | _output_not = True 590 | 591 | 592 | class RelayBoolNotAll(RelayBoolGate): 593 | """ 594 | Nand/Not All gate for a collection of RelayBools 595 | """ 596 | _input_not = True 597 | _output_not = False 598 | 599 | 600 | --------------------------------------------------------------------------------